Spring AOP是 Spring最核心的能力,那到底什么是AOP呢,今天了不起带大家了解一下。
AOP是什么
AOP(Aspect Oriented Programming):面向切面编程,是OOP(面向对象编程)的一个延续,其和OOP一样,也是一种编程思想,不过AOP是一种横向开发模式。
OOP ,面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
AOP ,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为"切面 "(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证.日志.事务处理。
AOP实现的关键在于代理模式 ,AOP代理主要分为静态代理 和动态代理 。静态代理的代表为AspectJ ;动态代理则以Spring AOP为代表。
- AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
- Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
- JDK动态代理只提供接口代理,不支持类代理,核心
InvocationHandler
接口和Proxy
类,InvocationHandler
通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起,Proxy利用InvocationHandler
动态创建一个符合某一接口的的实例, 生成目标类的代理对象。 - 如果代理类没有实现
InvocationHandler
接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。 - 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
InvocationHandler invoke(Object proxy,Method method,Object[] args)
:proxy
是最终生成的代理实例; method 是被代理目标实例的某个具体方法;args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
AOP使用场景
AOP的主要作用就是减少代码量,提高代码的可重用性,有利于未来的可操作性与可维护性。
主要操作就是将所有模块中共同拥有的代码,单独抽取出来,放在一块地方,在主代码运行之前或之后,或主程序运行的其他时间点执行这块代码。也可以理解成把这些单独抽出来的代码封装成一个个单独的方法,但是这些方法的执行不需要我们在程序中进行显示地调用,而是通过动态代理的方式来帮助我们执行这些方法。AOP的主要应用场景是一些相似性代码比较高的场景,比如:权限认证,日志,事务等。
AOP术语
- 连接点(Joinepoint):是程序执行的某个特定位置,如类开始初始化前、类初始化后、类的某个方法调用前/后、方法抛出异常后。一个类或者一段程序代码拥有一些具有边界性质的特定点,这些特定点就称为 "连接点" 。这边有个注意的点就是spring的链接点只支持方法,也就是只能是方法在调用前,调用后,抛出异常后。
- 切点(Pointcut): 切点就是能够定位到特定连接点的点。也就是能够通过切点知道程序在哪个连接点之前或之后执行。
- 增强(Advice): 增强说白了就是你想要在切点上执行的代码逻辑,也就是在连接点之前或之后执行的那段代码。
- 目标对象(Target): 就是你要插入代码的类,也就是连接点所在的类。
- 引介(Introduction): 是一种特殊的增强,为类添加一些属性和方法。
- 织入(Weaving):是一个过程,就是将增强添加到目标类具体连接点上的过程。
- 代理(Proxy): 就是融合了目标类和增强逻辑后生成的那个对象
- 切面(Aspect): 由切点和增强组成,包含了切点的定义与增强的代码逻辑的定义
AOP使用入门样例
- 引入依赖
powershell
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 定义一个Controller作为测试
powershell
public class AopController {
@RequestMapping("/hello")
public String sayHello(){
System.out.println("hello");
return "hello";
}
}
- 定义切面类
Spring采用@AspectJ
注解对POJO进行标注,该注解表明该类不仅仅是一个POJO,还是一个切面。切面是切点和通知的结合,那么定义一个切面就需要编写切点和通知。
@Pointcut
注解可以在一个切面内定义可重用的切点。
Spring切面粒度最小是达到方法级别,execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且实际开发中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution
表达式的使用是最为广泛的。
powershell
@Aspect
@Component
public class AopAdvice {
@Pointcut("execution (* com.xxx.aop.controller.*.*(..))")
public void test() {
}
}
切点通过@Pointcut注解已经定义好,那接下来就需要定义通知。
AOP通知有五种类型,分别是:
前置通知(@Before):在目标方法调用之前调用通知
后置通知(@After):在目标方法完成之后调用通知
环绕通知(@Around):在被通知的方法调用之前和调用之后执行自定义的方法
返回通知(@AfterReturning):在目标方法成功执行之后调用通知
异常通知(@AfterThrowing):在目标方法抛出异常之后调用通知
代码中定义了三种类型的通知,使用@Before
注解标识前置通知,打印"beforeAdvice...",使用@After
注解标识后置通知,打印"AfterAdvice...",使用@Around
注解标识环绕通知,在方法执行前和执行之后分别打印"before"和"after"。
powershell
@Before("test()")
public void beforeAdvice() {
System.out.println("beforeAdvice...");
}
@After("test()")
public void afterAdvice() {
System.out.println("afterAdvice...");
}
@Around("test()")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("before");
try {
proceedingJoinPoint.proceed();
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println("after");
}
- 结果
运行AopApplication,在浏览器访问http://localhost:8080/hello,不出意外,控制台输出如图所示:
AOP总结
纸上得来终觉浅,绝知此事要躬行,大家实践一下,也就知道具体的AOP是什么了,只要实践后,一切都变容易了。