Aop的基本了解
参考文献 https://mp.weixin.qq.com/s/gHejUiWOKyFWgRVKcFd8-w
Aop是什么,什么时候用
AOP 即“面向切面编程”,它是一种 编程思想,用来把程序中那些和业务无关、但又必须做的事情(比如日志、安全、事务)从业务逻辑中分离出来,方便统一管理。
我们写的程序里一般有两种逻辑,一类是和业务强相关的核心逻辑,比如用户注册、商品下单。还有一类是在很多地方都出现的重复逻辑,比如日志、安全、事务、性能监控。
传统写法里,这些横切逻辑会散落在每个类、每个方法里,导致代码重复、难以维护,改一个日志逻辑要改十几个类。
所以,我们就需要 AOP 来把这些横切逻辑“抽出来、集中写,让它们自动在指定的地方“织入”。
Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。
动态代理和静态代理
什么是代理?
代理就是给目标类提供一个代理对象,由代理对象控制目标对象的引用。代理模式是JAVA的一种设计模式,它通过提供代理对象来控制对原对象的访问。代理对象充当中介,通常在请求实际操作之前或之后做一些额外的工作(例如检查权限、缓存结果、延迟加载等)。
代理有啥好处呢?
通过代理对象的方式间接的访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性,通过代理对象对原有的业务增强。
动态代理和静态代理的区别
以下面这个UserServiceImpl为例
public interface UserService {
void login(String username);
}
public class UserServiceImpl implements UserService {
public void login(String username) {
System.out.println(username + " 登录了系统");
}
}
静态代理是这样实现的
public class UserServiceProxy implements UserService {
private UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
public void login(String username) {
System.out.println("日志记录");
target.login(username);
System.out.println("结束日志");
}
}
这里的静态代理类是写死的,耦合在类结构里,只能用于UserService类的对象,不能复用别的代理对象。每多一个业务类,就得多写一个静态代理类,扩展性差。
而动态代理是这样实现的:
public class LogHandler implements InvocationHandler {
private Object target;
public LogHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("记录日志...");
Object result = method.invoke(target, args);
System.out.println("登录结束");
return result;
}
}
动态代理通过将通用的代理逻辑封装在实现了InvocationHandler接口中的类中,借助反射机制在运行时动态生成代理对象,并拦截方法调用,从而实现对所有实现了接口的对象进行统一增强处理。
静态代理和动态代理的区别在于,对于一种代理逻辑,比如记录日志。静态代理对每个要调用代理逻辑的类都要手写一个代理对象类,本质上和传统写法没区别。而动态代理通过反射机制,只需一份代理逻辑就可以为所有要调用代理逻辑的类创建代理对象。
通俗地说,静态代理是每个类都写一份代理逻辑,而动态代理是写一份代理逻辑,服务所有类。
动态代理的两种方式
动态代理有两种方式:JDK动态代理和CGLIB动态代理。
这两种代理方式最主要的区别就是JDK代理的对象必须要实现接口,而CGLIB代理的对象不用实现接口。
JDK动态代理
JDK 动态代理通过 java.lang.reflect.Proxy 类,在运行时创建一个实现了指定接口的新类,然后通过 InvocationHandler 去拦截方法调用。
因为JDK 只能生成一个实现接口的类,它不能继承目标类(Java 不能多继承,如果采用“继承类”的方式,它最多只能代理一个类)。所以必须传一个接口数组给它,JDK才能知道“我要生成一个实现这些接口的代理类”
JDK会在运行时为UserService对象创建一个代理对象类,就像下面这样:
UserService proxy = (UserService) Proxy.newProxyInstance(...);
它生成的是一个实现了接口的 匿名类,内部通过 InvocationHandler 的 invoke() 方法来转发所有接口方法调用:
public final class $Proxy0 implements SomeInterface {
private InvocationHandler handler;
public void someMethod() {
handler.invoke(this, method, args);
}
}
CGLIB动态代理
CGLIB(Code Generation Library)不是JDK的官方实现,它使用 ASM(一个字节码编辑器)直接生成目标类的子类,并在这个子类中重写所有非 final 方法,在这些方法里加入“增强逻辑”,实现“方法覆盖”。
就像下面这样:
UserService proxy = (UserService) enhancer.create();
内部生成一个字节码相当于:
public class UserService$$EnhancerByCGLIB extends UserService {
public void login(String username) {
System.out.println("前置增强逻辑");
super.login(username);
System.out.println("后置增强逻辑");
}
}
AOP的实现
切面(Aspect):把某个横切逻辑封装成一个“类”,比如日志、安全、事务控制等。在类前面加一个@Aspect,表示这是一个切面类
连接点(JoinPoint):程序运行过程中,能被 AOP 拦截的点。
切入点(Pointcut):用表达式定义在哪些连接点插手的规则,execution(...)
是AOP切点表达式的固定格式,比如下面就是一个切入点表达式
execution(* com.example.service.*.*(..))
通知(Advice):在连接点上执行的具体动作,比如打印日志、做校验、记录时间等。
常用通知类型:
@Before("pointcut()") :方法执行前。
@After("pointcut()"):方法执行后(无论是否异常)。
@AfterReturning("pointcut()"):方法执行成功后。
@AfterThrowing("pointcut()"):方法抛异常后。
@Around("pointcut()"):环绕执行,可以控制方法是否继续执行。.
目标对象(Target Object):被增强的那个原始对象(也就是业务类)
织入(Weaving):把通知逻辑插入目标对象的切入点中的过程。
使用Spring API接口
实现 MethodInterceptor 接口,创建日志增强类,MethodInterceptor 是 Spring AOP机制中的一种拦截器接口,来自 org.aopalliance.intercept.MethodInterceptor 包, 可以在方法执行 前后都插入自定义逻辑。
@Component
public class LogInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String method = invocation.getMethod().getName();
Object[] args = invocation.getArguments();
System.out.println("[LOG] 执行方法:" + method + " 参数:" + Arrays.toString(args));
return invocation.proceed();
}
}
用 Spring 配置类将其应用到目标类上, ProxyFactoryBean,是 Spring 提供的一个 工厂 Bean,用来创建 代理对象,并把设置的增强逻辑(比如日志、事务、权限校验)织入进去。
@Configuration
public class ProxyConfig {
@Resource
private LogInterceptor logInterceptor;
@Bean
public ProxyFactoryBean userService(UserServiceImpl target) {
ProxyFactoryBean proxy = new ProxyFactoryBean();
proxy.setTarget(target);
proxy.addAdvice(logInterceptor);
return proxy;
}
}
controller如下
@RestController
@RequestMapping("/user")
public class UserConroller {
@Resource
private UserService userService;
@GetMapping("/login")
public void login(@RequestParam("userName") String userName){
userService.login(userName);
}
}
service 如下
@Service
public class UserServiceImpl implements UserService {
@Override
public void login(String user) {
System.out.println(user + "登录成功!");
}
}
Spring API 接口的方式适合自己控制代理方式的情况,不过一般不使用。
运行结果如图


自定义切面类+execution切入点表达式
定义一个 @Aspect 类,使用切入点表达式来设置在哪里插入切面
用 @EnableAspectJAutoProxy注解加在配置类上开启 Spring AOP 的自动代理支持,Spring会去扫描 @Aspect 注解的类,并根据切点表达式找到目标方法,然后创建代理对象,在方法执行前后织入增强逻辑。
@Aspect
@Component
@EnableAspectJAutoProxy
public class LogAspect {
@Before("execution(* com.example.demoaop.demos.aop.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("自定义切面类+execution切入点表达式:[LOG] 执行方法:" + joinPoint.getSignature().getName()
+ " 参数:" + Arrays.toString(joinPoint.getArgs()));
}
}
运行结果如下

自定义切面类+自定义注解+@annotation表达式
定义一个注解 @Log
@Target(ElementType.METHOD) // 注解作用于方法上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过反射读取
public @interface Log {
String value() default "默认值";
}
定义一个切面类处理这个注解
@Aspect
@Component
@EnableAspectJAutoProxy
public class LogByAnnotationAspect {
@Before("@annotation(com.example.demoaop.demos.aop.common.annotation.Log)")
public void log(JoinPoint joinPoint) {
System.out.println("自定义切面类+自定义注解+@annotation表达式:[LOG] 执行方法:" + joinPoint.getSignature().getName()
+ " 参数:" + Arrays.toString(joinPoint.getArgs()));
}
}
在需要切面处理的方法上标注注解
@RestController
@RequestMapping("/user")
public class UserConroller {
@Resource
private UserService userService;
@GetMapping("/login")
@Log
public void login(@RequestParam("userName") String userName){
userService.login(userName);
}
}
运行结果如下

方法二使用 @Aspect + @Before("execution(...)") 等形式,通过包名、类名、方法名等来匹配目标方法,适合统一增强逻辑。
方法三使用 @Aspect + 自定义注解 + @Before("@annotation(...)"),通过注解精确控制哪些方法被增强,适合细粒度控制。而且注解上可以加参数,功能性更强。
如果想要统一增强一大批方法,就用第二种方式(自定义切面类+execution切入点表达式) 如果只想在某些特定方法上做增强,就用第三种方式(自定义切面类+自定义注解+@annotation表达式)
AOP的限制
- 不能代理 final 类和 final 方法
- 不能增强私有方法(private)
- 不能增强static 静态方法
- 不能增强非 Spring 管理的对象
- 不能增强构造函数
- 不能调用类中自调用方法
AspectJ 的编译期织入
AspectJ 编译期织入(Compile-Time Weaving, CTW)完全绕过了 JDK 动态代理和 CGLIB 的限制,无论是 final 类、final 方法、private 方法,甚至构造器,它都能织入增强逻辑。
AspectJ CTW 是在 Java 编译阶段,使用 AspectJ 编译器(ajc)将普通的 .java 和 .aj 源码编译成 已经织入增强逻辑的 .class 字节码文件,本质上是将增强逻辑“焊死”在目标类的字节码中,不再依赖运行时代理。
要想实现CTW,不能用 JDK 自带的 javac 编译,要用 ajc,即通过 Maven 插件或手动命令 ajc 编译。

浙公网安备 33010602011771号