AOP简介
AOP的底层原理是动态代理。
AOP也就是面向切面,意思是做出一些通用的功能,如打印日志等,应用于所有需要的地方。传统的面向对象,在每个对象里进行打印日志,重复且无意义。
AOP实现的思路,拦截住一些需要使用打印日志功能的方法,使用动态代理,对原本的方法加入了打印日志的操作。
AOP重要概念
- 通知(增强,Advice):需要添加的功能叫做通知,比如说打印日志的操作
- 连接点(Join point):就是允许使用通知的地方,基本上每个方法前、后、抛异常时都可以是连接点(也就是before、after)
- 切点(Poincut):需要添加新功能的地方,比如需要在类A的方法 f() 前后打印日志, f() 就是切点
- 切面(Aspect):其实就是通知和切点的结合,通知和切点共同定义了切面的全部内容,它是干什么的,什么时候在哪执行
- 引入(Introduction):在无需修改现有类的前提下,让它们具有新的行为和状态。针对方法,而织入重点是实现方式为代理,针对对象。
- 目标(target):被通知的对象。也就是需要加入额外代码的对象,也就是真正的业务逻辑被组织织入切面。
- 织入(Weaving):把切面加入程序代码的过程。切面在指定的连接点被织入到目标对象中,在目标对象的生命周期里有多个点可以进行织入:编译期(切面在目标类编译时被织入,这种方式需要特殊的编译器)、类加载期(切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码)、运行期(切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,Spring AOP就是以这种方式织入切面的)
AOP具体使用方法
先定义一个切点,也就是需要拦截的地方。
围绕着切点,进行日志打印,共有五种执行日志打印的时机:
- before(前置通知): 在方法开始执行前执行
- after(后置通知): 在方法执行后执行
- around(环绕通知): 在方法执行前和执行后都会执行(优先于before和after)
- afterReturning(返回后通知): 在方法返回后执行
- afterThrowing(异常通知): 在抛出异常时执行
AOP的具体使用方法见:
传送门
拦截的东西
package com.aili.poem.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class LoveController {
@RequestMapping(value = "sayLove")
@ResponseBody
/**
* 这里必须加@ResponseBody,否则会出现白页
*/
public String sayLove(String name){
System.out.println("方法运行,参数为:" + name);
return "求爱";
}
}
拦截后进行的操作
@Aspect
@Component
public class ControllerAspect {
//切点
@Pointcut("execution(* com.aili.poem.controller..*(..))")
public void pointcut(){
}
//开始之前做点事
@Before("pointcut()")
public void doSomethingBefore(JoinPoint joinPoint){
//获取参数
Object[] args = joinPoint.getArgs();
System.out.println("开始对" + args[0].toString() + "进行求爱");
System.out.println("Buy follower");
System.out.println("Buy gift");
System.out.println("Buy order");
}
//结束之后做点事
@After("pointcut()")
public void doSomethingAfter(){
System.out.println("Show friend");
}
//前后都做点事(在最前面)
//这里注意必须返回Object
@Around("pointcut()")
public Object doSomethingAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("love you,baby");
//在这条语句之前,相当于@Before,之后则相当于@After
Object obj = joinPoint.proceed();
System.out.println("love you,baby");
return obj;
}
//返回结果后做点事
@AfterReturning(value = "pointcut()", returning = "returnValue")
public void doSomethingAfterReturn(JoinPoint joinPoint, Object returnValue){
//获取参数
Object[] args = joinPoint.getArgs();
System.out.println("完成对" + args[0].toString() + "的求爱");
System.out.println("对她进行了" + returnValue.toString());
}
//出现意外做点事
@AfterThrowing("pointcut()")
public void doSomethingAfterThrow(){
System.out.println("求爱失败");
}
}
AOP原理
AOP是Spring的一个特性,是Spring通过动态代理实现的功能。它主要使用两种动态代理方式:
- 当目标类实现了至少一个接口时,Spring会优先使用JDK动态代理
- 否则使用CGLIB动态代理
1. JDK动态代理
- 应用场景:当目标类实现了至少一个接口时。
- 实现原理:基于Java反射机制,运行时生成代理类($Proxy开头的类),实现目标对象的接口。
- 特点:只能代理接口方法,生成的代理类继承Proxy类。
- 性能开销:较低,只需生成接口代理类
2. CGLIB动态代理
- 应用场景:当目标类没有实现接口时
- 实现原理:基于ASM字节码操作框架,通过继承目标类生成子类,方法重写覆盖父类方法实现增强
- 特点:可以代理普通类,通过方法重写实现代理,不能代理final类和方法。
- 性能开销:较高,需要生成子类,字节码操作复杂
什么场景下AOP会失效
- 方法内部调用:在同一个类中,一个方法调用另一个方法时,AOP代理不会生效。AOP通常通过代理对象实现(如JDK动态代理或CGLIB代理),而内部调用是直接调用目标方法,绕过了代理。
- 静态方法:AOP无法拦截静态方法的调用。
- CGLIB代理的情况下,Final方法或类:AOP无法拦截final方法或final类的调用。原因:CGLIB代理通过生成子类来实现代理,而final方法或类无法被继承。
- Private方法:AOP代理无法访问private方法。
- 非Spring管理的Bean:AOP依赖于Spring的代理机制,只有Spring管理的Bean才能被代理。如果是我们自己通过 new 实现的对象,就不会被切面切到。
- 异步方法:异步方法通常在新线程中执行,而AOP的上下文可能无法传递到新线程。
- AOP拦截顺序问题:多个切面同时拦截同一个方法时,可能因顺序问题导致某些切面未生效。
- AOP与反射调用:反射调用直接操作目标方法,绕过了AOP代理。
AOP的应用场景
- 日志记录
- 事务管理
- 权限管理
- 埋点监控
通过注解实现AOP
切点除了目录之外,还可以通过注解的形式:
定义一个注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
/**
* 测试
*
* @return s
*/
String testName() default "";
}
定义注解的切面:
@Aspect
@Component
@Order(1)
public class TestAnnotationAspect {
/**
* 定义一个环绕通知
*/
@Around("@annotation(com.xxx.TestAnnotation)")
public Object aroundTestAnnotationMethod(ProceedingJoinPoint joinPoint) throws Throwable {
try {
// 执行被注解的方法
Object res = joinPoint.proceed();
// 执行要切面的内容,例如打日志
return res;
} catch (Throwable e) {
throw e;
} finally {
// do something
}
}
}