spring-aop 两种代理方式 JDK动态代理和CGLIB动态代理
Spring的两大特性是IOC和AOP:
Spring的核心特性就是IOC和AOP,IOC(Inversion of Control),即“控制反转”;AOP(Aspect-OrientedProgramming),即“面向切面编程”。下面分享我个人对AOP特性的理解。
AOP的实际应用:
面向切面编程,通常被定义为系统关注点的一种分离技术。系统是许多不同组件组成,组件的作用除了要实现自身的业务逻辑,通常都会被要求添加日志,事务管理,和缓存或安全等问题。这些系统服务进程被称为横切关注点,在系统中多个组件都会相继使用。
AOP实现的关键在于AOP框架自动创建AOP代理,AOP代理主要分为静态代理(AspectJ)和动态代理(StringAop)
AspectJ:会被称为编译时增强,AspectJ是静态代理的增强,采用编译时生成AOP代理类,会具有更好的性能,就是需要使用特定的编译器去处理,使用不太方便。
Spring-Aop:是以动态代理的方式,运行时生成AOP代理类,不会改变字节码,在内存中生成一个方法的AOP临时对象。在运行过程中需要每次生成AOP代理,性能会相对受到影响。
Spring-Aop动态代理实现有两种方式,jdk动态代理和CGIB动态代理。
JDK动态代理:是通过反射接收被代理的类。
一般主要涉及到以下两个类:
(1)Interface InvocationHandler:该接口中仅定义了一个方法
(2)Proxy:该类即为动态代理类,(详情看实例代码注解)
下面就来演示下jdk代理的实现原理。
1、jdk动态实现aop拦截
自定义要实现的接口MyInv,需要实现的方法add();
/**
* Created by zzm on 2020/04/19
* desc: jdk动态aop代理需要实现的接口
*/
public interface MyInv {
public void add();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
用要代理的目标类MyInvImpl实现上面我们定义的接口,在目标类的add方法的前后实现拦截,加入自定义切面逻辑。这就是aop的魅力所在:代码与代码之间没有耦合。
/**
* Created by zzm on 2020/04/19
* desc: 被代理的类,即目标类target
*/
public class MyInvImpl implements MyInv {
@Override
public void add() {
System.out.println("目标类的add方法");
System.out.println("时间和机会才是最后的本钱!");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
用MyInvoQuote类去实现InvocationHandler接口,并且实现接口中的invoke方法。在该方法中加入切面逻辑,实现自己的目标接口是在method.invoke里完成。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
-
Created by zzm on 2020/04/19
-
desc:这里加入切面实现InvocationHandler 接口的类
*/
public class MyInvoQuote implements InvocationHandler {private Object target;
public MyInvoQuote(Object target) { // 构造函数
this.target = target;
}
/**- 这个抽象方法在代理类中动态实现。
- @param proxy 第一个参数obj一般是指代理类
- @param method method是被代理的方法
- @param args args为该方法的参数数组
- @return
- @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“before—》切面执行逻辑”);
Object invoke = method.invoke(target, args);//通过反射执行,目标类的方法
System.out.println(“after—》切面执行逻辑”);
return invoke;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
最后写个测试方法。实现aop方式之一的:jdk动态代理
public class test {
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> MyInvImpl myInvImpl <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MyInvImpl</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 实现需要代理的类</span> MyInvoQuote handler <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MyInvoQuote</span><span class="token punctuation">(</span>myInvImpl<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//构造实现InvocationHandler接口的实现类</span> <span class="token comment">// Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例</span> <span class="token comment">//这里的proxyInstance就是我们目标类的增强代理类</span> MyInv proxyInstance <span class="token operator">=</span> <span class="token punctuation">(</span>MyInv<span class="token punctuation">)</span> Proxy<span class="token punctuation">.</span><span class="token function">newProxyInstance</span><span class="token punctuation">(</span>myInvImpl<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// getClassLoader() 取得该Class对象的类装载器</span> myInvImpl<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">getInterfaces</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> handler<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//getInterfaces()获取该class对象下的方法可通过索引找到对应方法</span> <span class="token comment">// 列: myInvImpl.getClass().getInterfaces()[0] 获得myInvImpl对象所实现的第一个接口</span> <span class="token comment">//Proxy.newProxyInstance()返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)</span> proxyInstance<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//打印增强过的类类型</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"打印增强过的类类型:"</span> <span class="token operator">+</span> proxyInstance<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
结果
在实现过程中证实了jdk动态代理的核心是InvocationHandler接口和Proxy类
注意:jdk动态代理的应用前提,反射机制在生成类的过程中比较高效,不过必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性
cglib动态代理:底层则是借助asm来实现的
下面演示下cglibProxy动态代理
目标类,cglib不需要定义目标类的统一接口
/**
* Created by zzm on 2020/04/19
* desc: 被代理的类,即目标类target
*/
public class MyInvImpl {
public void myBase() {
System.out.println("目标类的myBase方法");
System.out.println("时间和机会才是最后的本钱!");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
实现动态代理类CglibProxy,需要实现MethodInterceptor接口,实现intercept方法。该代理中在myBase方法前后加入了自定义的切面逻辑,目标类myBase方法执行语句为proxy.invokeSuper(object, args);
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
-
Created by zzm on 2020/04/19
-
desc:这里加入切面实现MethodInterceptor 接口的类
*/
public class MyInvoQuote implements MethodInterceptor {@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(“before添加切面,方法执行前”);
methodProxy.invokeSuper(o, objects);
System.out.println(“after添加切面,方法执行后”);
return null;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
测试类:
public class test {
<span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> MyInvoQuote proxy <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MyInvoQuote</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//构造实现 MethodInterceptor接口的类</span> Enhancer enhancer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Enhancer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//设置代理目标</span> enhancer<span class="token punctuation">.</span><span class="token function">setSuperclass</span><span class="token punctuation">(</span>MyInvImpl<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//回调方法的参数为代理类对象CglibProxy,</span> <span class="token comment">// 最后增强目标类调用的是代理类对象CglibProxy中的intercept方法</span> enhancer<span class="token punctuation">.</span><span class="token function">setCallback</span><span class="token punctuation">(</span>proxy<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//此刻,base不是单车的目标类,而是增强过的目标类</span> MyInvImpl base <span class="token operator">=</span> <span class="token punctuation">(</span>MyInvImpl<span class="token punctuation">)</span> enhancer<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> base<span class="token punctuation">.</span><span class="token function">myBase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Class<span class="token operator"><</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">MyInvImpl</span><span class="token operator">></span> baseClass <span class="token operator">=</span> base<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//java 泛型 上界通配符(Upper Bounds Wildcards)</span> <span class="token comment">//查看增强过的类的父类是不是未增强的Base类</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"增强过的类的父类:"</span><span class="token operator">+</span>baseClass<span class="token punctuation">.</span><span class="token function">getSuperclass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"============打印增强过的类的所有方法=============="</span><span class="token punctuation">)</span><span class="token punctuation">;</span> FanSheUtils<span class="token punctuation">.</span><span class="token function">printMethods</span><span class="token punctuation">(</span>baseClass<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//自定义获取类下面方法的工具类</span>
// 没有被增强过的base类
MyInvImpl base2 = new MyInvImpl();
System.out.println(“未增强过的类的父类:”+base2.getClass().getSuperclass().getName());
System.out.println("=打印增未强过的目标类的方法===");
FanSheUtils.printMethods(base2.getClass());//打印没有增强过的类的所有方法
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
查看结果
before添加切面,方法执行前
目标类的myBase方法
时间和机会才是最后的本钱!
after添加切面,方法执行后
增强过的类的父类:com.billiards.service.impl.MyInvImpl
============打印增强过的类的所有方法==============
public final equals(java.lang.Object);
public final toString();
public final hashCode();
CGLIB$SET_THREAD_CALLBACKS([Lorg.springframework.cglib.proxy.Callback;);
public static CGLIB$SET_STATIC_CALLBACKS([Lorg.springframework.cglib.proxy.Callback;);
public getCallback(int);
public getCallbacks();
public static CGLIB$findMethodProxy(org.springframework.cglib.core.Signature);
final CGLIB$equals$1(java.lang.Object);
final CGLIB$myBase$0();
final CGLIB$hashCode$3();
final CGLIB$clone$4();
final CGLIB$toString$2();
static CGLIB$STATICHOOK1();
private static final CGLIB$BIND_CALLBACKS(java.lang.Object);
未增强过的类的父类:java.lang.Object
=============打印增未强过的目标类的方法===============
public myBase();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
自此,cglib动态代理实现的AOP拦截机制已经基本实现
asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
spring aop的两个核心方式已经简单实现。后续实现Spring-boot中使用aop切面,使用注解的形式更加简化了代码量,通过注解是使用springaop两种形式中的(AspectJ)方式实现的。掌握原理在使用注解的形式去做,才不会一头雾水。
</div>