Spring AOP
概念
- 面向切面编程,扩展功能不需修改源代码
- AOP采用横向抽取机制,取代了纵向抽取机制
- 在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想
AOP和OOP的关系
- AOP面向切面编程,而OOP面向对象编程
- AOP是对OOP的一种补充,而不是对立关系
AOP的演变
从纵向抽取机制–>横向抽取机制
AOP底层使用动态代理方式实现
第一种情况:有接口,使用JDK动态代理方式创建接口实现类的代理对象
第二种情况:没有接口,使用cglib创建类的子类代理对象
总结
- 函数本身只需要实现业务逻辑即可
- 散落在各个功能方法上却和业务逻辑无关的功能可以封装起来,形成一个横切关注点(横切面)
AOP术语
- 连接点:类中可以被增强的方法,这些方法称为连接点
- 切入点:实际增强的方法称为切入点
- Advice(通知/增强):增强的逻辑称为增强,比如扩展日志功能,日志功能的逻辑就称为增强
分类 | 含义 |
前置通知 | 在方法之前执行 |
后置通知 | 在方法之后执行 |
异常通知 | 方法出现异常 |
最终通知 | 在后置之后执行 |
环绕通知 | 在方法之前和之后执行 |
- 切面:把具体增强的逻辑应用到具体执行的方法上面的过程,称为切面
把增强用到切入点的过程
Spring实现AOP的操作
在Spring中进行aop操作,需要依赖aspectJ实现
基于aspectJ实现aop通过xml配置方式
一. 步骤
- 导入aop相关的jar
- 创建核心配置文件,导入AOP约束
- 准备增强类和被增强类(指定切入点和通知)
- 使用表达式配置切入点
二. 常用的表达式(使用表达式目的是配置切入点,完成方法的增强)
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>),
- 访问修饰符:可以是public或者private 通常情况可以指定*,代表是任意的访问修饰符
- 方法的全路径:是增强方法的全路径,表示对这个路径下的类中的方法做增强,可以在类的后面加上*,表示对所有方法做增强
eg:execution(* com.hpe.aop.Book.* (…))
也可以匹配以XX开头的方法做增强execution(* save* (…)):以save开头的方法都能做增强
三. 使用xml配置切面,实现前置通知
Book.java
// 被增强类
public class Book {
// 切入点
public void add(){
System.out.println("add...");
}
// 连接点(也是切入点)
public void delete(){
System.out.println("delete...");
}
}
MyAdvice.java
// 增强类
public class MyAdvice {
// 前置通知
public void doBefore(){
System.out.println("前置增强...");
}
// 后置通知
public void doAfter(){
System.out.println("后置增强...");
}
// 环绕通知 ProceedingJoinPoint:用来调用被增强的方法
public void doAround(ProceedingJoinPoint p) throws Throwable{
// 方法之前
System.out.println("方法之前...");
// 执行被增强的方法
p.proceed();
// 方法之后
System.out.println("方法之后...");
}
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1.配置对象(被增强类和增强类) -->
<bean id="book" class="com.hpe.aop.Book"></bean>
<bean id="myAdvice" class="com.hpe.aop.MyAdvice"></bean>
<!-- 2.配置aop操作 -->
<aop:config>
<!-- 2.1配置切入点(指定对哪些方法做增强) -->
<aop:pointcut expression="execution(* com.hpe.aop.Book.add(..))" id="pointcut1"/>
<aop:pointcut expression="execution(* com.hpe.aop.Book.delete(..))" id="pointcut2"/>
<!-- 2.2配置切面(把增强应用到具体的切入点) ref:增强的对象-->
<aop:aspect ref="myAdvice">
<!-- 增强的类型 method:增强使用哪个方法作为前置通知 pointcut-ref:把增强应用到哪个切入点上面 -->
<aop:before method="doBefore" pointcut-ref="pointcut1"/>
<!-- 后置通知 -->
<aop:after-returning method="doAfter" pointcut-ref="pointcut1"/>
<!-- 环绕通知 -->
<aop:around method="doAround" pointcut-ref="pointcut2"/>
</aop:aspect>
</aop:config>
</beans>
测试类
// 使用XML配置方式实现前置、后置通知做方法增强
@Test
public void test2(){
// 1.读取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.获得对象
Book book = (Book) context.getBean("book");
// 3.访问方法
book.add();
}
// 使用XML配置方式实现环绕通知做方法增强
@Test
public void test3(){
// 1.读取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.获得对象
Book book = (Book) context.getBean("book");
// 3.访问方法
book.delete();
}
基于aspectJ实现aop通过注解方式
AOP相关的注解
注解 | 含义 |
@Aspect | 注解在类上面,声明一个切面 |
@Pointcut | 注解在方法上面,声明一个切入点,况且要指定aspect表达式 |
@Before | 前置通知 |
@After | @AfterReturning后置通知,@AfterThrow异常通知 |
@Round | 环绕通知 |
代码实例
被增强类Person.java
// 被增强类(被代理对象)
@Component(value="person")
public class Person {
public void eat(){
System.out.println("eat...");
}
public void drink(){
System.out.println("drink...");
}
}
增强类MyAspect.java
// 增强类
// 3.在增强类上使用注解
@Component
@Aspect
public class MyAspect {
// 在方法上面使用注解来配置通知
// Before:前置通知;value:切入点(表达式)
@Before(value="execution(* com.hpe.aop.Person.eat(..))")
public void doBefore(){
System.out.println("前置增强...");
}
@After(value="execution(* com.hpe.aop.Person.drink(..))")
public void doAfter(){
System.out.println("后置增强...");
}
}
applicationContext2.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1.创建对象 -->
<!-- <bean id="person" class="com.hpe.aop.Person"></bean>
<bean id="myAspect" class="com.hpe.aop.MyAspect"></bean> -->
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.hpe.aop"></context:component-scan>
<!-- 2.使用注解的方式开启AOP的操作:开启注解扫描 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
Test.java
// 使用注解方式实现环绕通知做方法增强
@Test
public void test4(){
// 1.读取配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml");
// 2.获得对象
Person person = (Person) context.getBean("person");
// 3.访问方法
person.eat();
person.drink();
}