AOP实现原理
😉 本文共3257字,阅读时间约10min
代理
代理模式
用代理对象来代替对真实对象的访问,可以在不修改原目标对象的前提下,增强目标对象的功能。
静态代理
静态代理,就是自己写代理类,几乎用不到。比如接口增加方法了,目标对象和代理对象都要改。而且每个目标类都得单独写一个代理类。
从JVM层面讲,静态代理在编译器就生成了代理类的字节码文件。
public interface SmsService {
String send(String message);
}
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
// 代理类
public class SmsProxy implements SmsService {
private final SmsService smsService;
public SmsProxy(SmsService smsService) {
this.smsService = smsService;
}
// 访问原方法,并在方法前后进行增强
@Override
public String send(String message) {
System.out.println("before method send()");
smsService.send(message);
System.out.println("after method send()");
return null;
}
}
public class Main {
public static void main(String[] args) {
SmsService smsService = new SmsServiceImpl();
SmsProxy smsProxy = new SmsProxy(smsService);
smsProxy.send("java");
}
}
动态代理
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类。
从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理等等。
JDK动态代理机制
代理类和被代理类一样,是同一个接口的实现类,类似兄弟关系。
InvocationHandler
接口和 Proxy
类
在 Java 动态代理机制中 InvocationHandler
接口和 Proxy
类是核心。
-
Proxy
类中使用频率最高的方法是:newProxyInstance()
,这个方法主要用来生成一个代理对象。public static Object newProxyInstance( ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){} // 类加载器,被加载类实现的一些接口,实现了InvocationHandler接口的对象
-
还必须需要实现
InvocationHandler
来自定义处理逻辑(增强逻辑)。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler
接口类的invoke
方法来调用。public interface InvocationHandler { /** * 当你使用代理对象调用方法的时候实际会调用到这个方法 */ public Object invoke(Object proxy, Method method, Object[] args); // 动态生成的代理类,调用的method,method的参数 }
-
(通过
Proxy
类的newProxyInstance()
创建的代理类对象)在调用方法的时候,实际会调用到(实现InvocationHandler
接口的类)的invoke()
方法。 你可以在invoke()
方法中自定义处理逻辑,比如在方法执行前后做什么事情。// 接口和实现类 public interface ClacInterface { int add(int a ,int b ); } public class MyClac implements ClacInterface{ @Override public int add(int a, int b) { System.out.println("a+b"+"="+(a+b)); return a+b; } } // 代理类对象调用InvocationHandler的invoke方法 public static void main(String[] args) { ClacInterface myClac = new MyClac(); //被代理的对象 //生成了一个代理类对象proxyBean ClacInterface proxyBean = (ClacInterface)Proxy.newProxyInstance( myClac.getClass().getClassLoader(), myClac.getClass().getInterfaces(), //第三参数是InvocationHandler 这里是lambda简化的匿名内部类写法 (a, b, c) -> { System.out.println("我代理了myClac对象"); return b.invoke(myClac, c); } ); }
jdk动态代理原理
原理
这里不做过多说明,具体可以看B站讲Spring原理的讲解。
代理类和被代理类一样,是同一个接口的实现类,类似兄弟关系。
先生成一个实现代理接口的匿名类(就是代理类),然后重写方法进行方法增强。在调用具体方法前通过调用InvocationHandler的invoke方法来处理。
在这个代理类里也有那个handler。最后相当于
\(\stackrel{调用}{\rightarrow} 代理类对象的方法 \stackrel{调用}{\rightarrow} 调用代理类里的handler的invoke方法 \stackrel{调用}{\rightarrow} 反射调用被代理类的原方法\)
- 为啥要一个InvocationHandler接口,并在生成代理类对象作为参数传入?
- 解耦,因为得让用户/框架能去自定义代理的增强行为
特点
他的特点是生成代理类的速度很快,但是运行时调用方法操作会比较慢,因为是基于反射机制的
只能针对接口编程,即目标对象要实现接口
反射优化
前 16 次是调用了java native的MethodAccessor 的实现类,反射性能较低
第 17 次调用会生成又一个代理类,相当于为这个方法创建了一个访问的代理类,把原来的反射调用优化为正常调用
Cglib动态代理机制
代理类是被代理类的子类,父子关系
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
MethodInterceptor
接口和 Enhancer
类
-
需要自定义
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法。跟那个InvocationHandler的invoke()很像。Enhancer类的create()方法可以生成代理类。public interface MethodInterceptor extends Callback{ // 拦截被代理类中的方法 public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable; }
-
例子:
// 被代理类 public class AliSmsService { public String send(String message) { System.out.println("send message:" + message); return message; } } // 自定义 MethodInterceptor(方法拦截器) public class DebugMethodInterceptor implements MethodInterceptor { /** * @param o 被代理的对象(需要增强的对象) * @param method 被拦截的方法(需要增强的方法) * @param args 方法入参 * @param methodProxy 用于调用原始方法 */ @Override public Object intercept (Object o, Method method, Object[] args, MethodProxy methodProxy) { //调用方法之前,我们可以添加自己的操作 System.out.println("before method " + method.getName()); Object object = methodProxy.invokeSuper(o, args); //调用方法之后,我们同样可以添加自己的操作 System.out.println("after method " + method.getName()); return object; } } // Enhancer的create()获取代理类 public class CglibProxyFactory { public static Object getProxy(Class<?> clazz) { // 创建动态代理增强类 Enhancer enhancer = new Enhancer(); enhancer.setClassLoader(clazz.getClassLoader()); // 设置被代理类 enhancer.setSuperclass(clazz); // 设置方法拦截器 enhancer.setCallback(new DebugMethodInterceptor()); // 创建代理类 return enhancer.create(); } } // 使用 AliSmsService aliSmsService = (AliSmsService)CglibProxyFactory.getProxy (AliSmsService.class); aliSmsService.send("java"); > before method send > send message:java > after method send
Cglib动态代理原理
原理
基于asm指令,直接生成代理类的字节码。且代理类是目标类的子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。
特点
代理类继承被代理类,底层是基于asm第三方框架对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
生成代理类的速度很慢,但是运行时调用方法操作会比较快,因为是没有基于反射机制的
无反射
MethodProxy在调用invoke和invokeSuper时不会走反射途经,而是通过FastClass来避免反射,cglib在调用方法时,生成两个FastClass(为FastClass的子类),FastClass中的方法能避免反射。
$\stackrel{调用}{\rightarrow} 代理类对象的方法 \stackrel{调用}{\rightarrow} 调用代理类里的interceptor的intercepet方法 $$\stackrel{调用}{\rightarrow} 调用methodproxy的invoke方法(调用fastclass里的方法) \stackrel{调用}{\rightarrow} 正常调用目标类方法$
两种动态代理比较
比较
- jdk动态代理机制
- 只能代理接口实现类,代理类和被代理类都实现了同一个接口,兄弟关系
- 基于反射调用目标类方法,有反射优化
- 生成代理类的速度很快,但是运行时调用方法操作会比较慢
- cglib动态代理机制
- 能代理大部分类,除非final不能继承或重写;被代理类和代理类是父子关系
- 用fastclass,无反射问题
- 生成代理类的速度慢,但是运行时调用方法操作会比较快(cglib是MethodProxy调用的时候就产生代理,一个proxy代理类对应两个fastClass的子类;而且基于asm对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理会慢一点)
什么时候用cglib什么时候用jdk动态代理?
1、目标对象生成了接口 默认用JDK动态代理
2、如果目标对象使用了接口,可以强制使用cglib
3、如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换
Cglib比JDK快?
一开始是调用次数比较少的时候,jdk好;多就cglib好。
随着jdk版本升级,jdk动态代理效率越来越好了,大量调用效率也高于cglib
Spring AOP增强技术
AJC编译器
AspectJ
支持在编译期
和类加载期
通过修改class文件来实现增强。不支持运行期织入。- 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等增强。
- 如果在类加载期织入,则需要使用agent。
- 如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
Spring AOP 内容与原理
作用与内容
-
作用:面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度。
-
内容:切面(切点 + 通知)
aspect = 通知1(advice)+ 切点1(pointcut) 通知2(advice)+ 切点2(pointcut) 通知3(advice)+ 切点3(pointcut) asvisor = 更细粒度的切面,只包含一个通知和切点 在aspect生效之前,会被拆解成多个advisor
原理
Spring AOP 就是基于动态代理的
- 如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象
- 而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
即使有接口,也可以设置参数强制使用Cglib代理
代理注入时机
- 自动代理后处理器
AnnotationAwareAspectJAutoProxyCreator
会帮我们创建代理- 通常代理创建在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行
- 自动代理后处理器工作步骤
- 解析某个类的切面
findEligibleAdvisors
- 解析时会将高级的 @Aspect 切面会转换为低级的 Advisor 切面,如果此处有多个Advisor,则将其添加到ProxyFactory的List<Advisor>成员变量中。
- 基于所有的切面按照创建一个代理
- 解析某个类的切面
创建代理的多个切面顺序
- Spring AOP会将当前方法的所有切面都封装进包含原方法的增强方法,使所有切面生效。
- 不同的增强器,如@Before、@After,他们的执行顺序由他们自身控制。
- 同一个方法的的多个增强类内相同的增强器,增强顺序按照Spring加载类后注册BeanDefinition的顺序,即Spring注册BeanDefinition的顺序。
- 可以在Aspect类上加@Order(1)注解,数字越小越早加载
定义的通知类型有哪些?
类型 | 内容 |
---|---|
Before(前置通知) | 目标对象的方法调用之前触发 |
After (后置通知) | 目标对象的方法调用之后触发 |
AfterReturning(返回通知) | 目标对象的方法调用完成,在返回结果值之后触发 |
AfterThrowing(异常通知) | 目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。 |
Around (环绕通知) | 编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法 |
- 对外是为了方便使用要区分 before、afterReturning
- 对内统一都是环绕通知, 统一用 MethodInterceptor 表示