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 接口的方式适合自己控制代理方式的情况,不过一般不使用。
运行结果如图
image

image

自定义切面类+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()));
	}
}

运行结果如下
image

自定义切面类+自定义注解+@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);
	}
}

运行结果如下
image

方法二使用 @Aspect + @Before("execution(...)") 等形式,通过包名、类名、方法名等来匹配目标方法,适合统一增强逻辑。

方法三使用 @Aspect + 自定义注解 + @Before("@annotation(...)"),通过注解精确控制哪些方法被增强,适合细粒度控制。而且注解上可以加参数,功能性更强。

如果想要统一增强一大批方法,就用第二种方式(自定义切面类+execution切入点表达式) 如果只想在某些特定方法上做增强,就用第三种方式(自定义切面类+自定义注解+@annotation表达式)

AOP的限制

  1. 不能代理 final 类和 final 方法
  2. 不能增强私有方法(private)
  3. 不能增强static 静态方法
  4. 不能增强非 Spring 管理的对象
  5. 不能增强构造函数
  6. 不能调用类中自调用方法

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 编译。

posted @ 2025-04-30 00:15  V02445  阅读(93)  评论(0)    收藏  举报