');}

Spring中的代理

Author Avatar
Rico 9月 01, 2018

代理模式(Proxy)

特点:

1、 有2个角色,执行者、被代理人

2、 对于被代理人来说,这件事情是一定要做的,但是我自己不想做或者没有时间做,找代理做

3、 需要获取到被代理的人个人资料,只是参与整个过程的某个或几个环节

生活中的案例:

租房中介、

售票黄牛、

婚介、

经纪人

快递、事务代理、非侵入式日志监听

应用场景:为其他对象提供一种代理以控制对这个对象的访问。从结构上来看和Decorator模式类似,但Proxy是控制,更像是一种对功能的限制,而Decorator是增加职责。

Spring的Proxy模式在AOP中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。

代码举例:租房子

定义一个人,要找房子

  1. package findHouse;
  2. public interface Person {
  3. String getName();
  4. String getAddress();
  5. void findHouse();
  6. void anotherMethod();
  7. }

定义个张三,要找房子

  1. package findHouse;
  2. public class Zhangsan implements Person{
  3. private String name="张三";
  4. private String address="西湖";
  5. public void findHouse() {
  6. System.out.println("我是"+this.getName()+",我住"+this.getAddress()+"附近,要租房子");
  7. }
  8. public void anotherMethod() { System.out.println("另一个方法"); }
  9. public String getName() { return name; }
  10. public void setName(String name) { this.name = name; }
  11. public String getAddress() { return address; }
  12. public void setAddress(String address) { this.address = address; }
  13. }

如果不使用任何模式,最简单的调用是这样

  1. package findHouse;
  2. public class TestFindHouse {
  3. public static void main(String[] args) {
  4. Person person=new Zhangsan();
  5. person.findHouse();
  6. }
  7. }

但是张三是个上班族,没时间租房子,就去找中介。

中介类

在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口

  1. package findHouse;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. //中介
  6. public class Zhongjie implements InvocationHandler{//实现这个接口才具有代理功能
  7. private Person target;
  8. //获取被代理人的个人资料
  9. public Object getInstance(Person target) throws Exception {
  10. this.target=target;
  11. Class clazz=target.getClass();
  12. ClassLoader loader=clazz.getClassLoader();
  13. return Proxy.newProxyInstance(loader, clazz.getInterfaces(), this);//第一个参数是ClassLoader,第二个参数是interfaces,第三个参数是InvocationHandler
  14. }
  15. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  16. System.out.println("代理开始");
  17. System.out.println("我是中介,你叫"+this.target.getName()+",你住"+this.target.getAddress()+"附近,我帮你找房子,找了几个合适的你挑选一下");
  18. //this.target.findHouse();
  19. Object result = method.invoke(this.target, args);//这里其实并没有返回值
  20. System.out.println("代理结束");
  21. return result;
  22. }
  23. }

从InvocationHandler这个名称我们就可以知道,实现了这个接口的中介类用做“调用处理器”。当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)

中介类实现InvocationHandler接口,作为调用处理器”拦截“对代理类方法的调用

测试类

  1. package findHouse;
  2. public class TestFindHouse {
  3. public static void main(String[] args) {
  4. Zhongjie zhongjie=new Zhongjie();//中介
  5. try {
  6. Person obj=(Person)zhongjie.getInstance(new Zhangsan());
  7. System.out.println("代理后生成的class:"+obj.getClass().getName());
  8. obj.findHouse();
  9. //obj.anotherMethod();
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }

从以上代码中我们可以看到,中介类持有一个委托类对象引用,在invoke方法中调用了委托类对象的相应方法

感觉上就是 ,中介类可以在调用张三的找房方法前,先执行一些逻辑,然后调用张三的找房,然后在执行以下后续方法。

有点像我们现实生活中的情况,比如你要买房,中介在你签字前做了大量工作,找房东,拿钥匙,联系贷款银行问利率,帮你算总价,分期等,你决定要买了之后你只需要签字即可(调用委托对象的相应方法),你签完字,中介还要继续后续银行放款,权证房产证契税缴纳,房管局取送资料,最后交付。 你只需要签字付钱即可。

贷款利率变了,手续流程变了,你不需要关心,你只要签字,脏活累活业务强相关的代理类帮你干了。

在编程中也一样,某些东西流程变了,委托类不需要管,代理类帮你管好,你的核心流程不变。这样就省心多了

动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数

原理:

1 拿到被代理对象的引用(张三类),然后获取它的接口

2 JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口 (Person接口)

3 把被代理对象的引用也拿到了

4 重新动态生成一个class字节码

5 然后编译

你委托类(被代理类)的原始方法是不改的,只是在你前面,在你后面加了一些逻辑

把JVM编译后的代理类的class字节获取到然后反编译看下

这个this.h是什么? 在父类Proxy中可以看到这个h

this.h就是InvocatinHandler

我们把生成的proxy0.class的字节码反编译输出看下

h就是InvocationHandler

我们代码中

Proxy.newProxyInstance(loader, clazz.getInterfaces(), this) 把Zhongjie的this当做第三个参数传入,所以h就是中介类

所以上图代码中的

this.h.invoke(this.m4.null)就是调用了 zhangsan类中的invoke方法,而中介类的invoke的代码是

  1. System.out.println("代理开始");
  2. System.out.println("我是中介,你叫"+this.target.getName()+",你住"+this.target.getAddress()+"附近,我帮你找房子,找了几个合适的你挑选一下");
  3. Object result = method.invoke(this.target, args);//这里其实并没有返回值
  4. System.out.println("代理结束");
  5. return result;

其中有一行是

  1. Object result = method.invoke(this.target, args);

又调用了method.lnvoke,这才真正调用了张三的实际方法,但是在调用张三方法之前和之后,就是我们在代理类中需要在调用张三方法之前需要执行的操作,达到了我们的目的

了解清楚代理原理和代理模式对后续看Spring源码有帮助。 修炼内功心法

自己编写Proxy和ClassLoader,InvocationHandler

根据接口生成java代码的方法

  1. //重新根据接口生成代码(拼代码)
  2. private static String generateSourceCode(Class<?> interfaces) {
  3. //包名
  4. StringBuilder sourceCode=new StringBuilder("package org.rico.learnSpring.customProxy;").append(ln);//包名
  5. //导包
  6. sourceCode.append("import java.lang.reflect.Method;").append(ln).append(ln);
  7. //声明类
  8. sourceCode.append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append(ln);//这里只实现了一个,如果通用的话要写循环//这里的类名是随便的,只要.java文件和类名一样就可以了这里是$Proxy0
  9. sourceCode.append(" MyInvocationHandler h;").append(ln);//域 局部变量
  10. //构造函数
  11. sourceCode.append(" public $Proxy0(MyInvocationHandler h){").append(ln);//这个构造函数名应该是动态生成的,但是这里写死了,为了方便
  12. sourceCode.append(" this.h=h;").append(ln);
  13. sourceCode.append("}").append(ln);//匹配public $Proxy0的{
  14. for(Method m:interfaces.getMethods()){
  15. sourceCode.append(" public ").append(m.getReturnType().getName()).append(" ").append(m.getName()).append("(){").append(ln);//我们的Person方法都是无参的,这里也是图简便,应该是要把参数也遍历出来的
  16. sourceCode.append(" try{").append(ln);
  17. //获取这个Method
  18. sourceCode.append(" Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(m.getName()).append("\",new Class[0]);").append(ln);//new Class[]{}
  19. sourceCode.append(" this.h.invoke(this,m,null);").append(ln);
  20. sourceCode.append(" }catch(Throwable e){e.printStackTrace();}").append(ln);
  21. if(!m.getReturnType().getName().equals("void")){//如果返回值不为空,返回
  22. sourceCode.append(" return null;").append(ln);//这里为了方便直接返回null
  23. }
  24. sourceCode.append(" }").append(ln);
  25. }
  26. sourceCode.append("}");//匹配public class 的 {
  27. return sourceCode.toString();
  28. }

生成的java代码

编写的这个generageSourceCode方法也是不断尝试,直到生成的.java文件放在工程中不报错了就可以了 ,经常会出现括号不成对出现,返回值,大小写等的问题

编译java文件成class文件

  1. //3 编译源代码 ,并且生成.class文件
  2. try {
  3. JavaCompiler compiler= ToolProvider.getSystemJavaCompiler();//javax提供的编译器
  4. StandardJavaFileManager manager=compiler.getStandardFileManager(null,null,null);
  5. Iterable iterable=manager.getJavaFileObjects(f);//获取迭代器 就是为了找到Java文件
  6. JavaCompiler.CompilationTask task=compiler.getTask(null,manager,null,null,null,iterable);//编译任务
  7. task.call();
  8. manager.close();
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. }

完整源码

测试类

  1. package org.rico.learnSpring.customProxy;
  2. import org.rico.learnSpring.findHouse.Person;
  3. import org.rico.learnSpring.findHouse.Zhangsan;
  4. public class TestFindHouse {
  5. public static void main(String[] args) {
  6. MyZhongjie zhongjie=new MyZhongjie();//中介
  7. try {
  8. Person obj=(Person)zhongjie.getInstance(new Zhangsan());//代理出来的对象可以强转成接口的 ,当然zhangsan也要实现Person
  9. System.out.println("代理后生成的class:"+obj.getClass().getName());
  10. obj.findHouse();
  11. //obj.anotherMethod();
  12. } catch (Exception e){
  13. e.printStackTrace();
  14. }
  15. }
  16. }

中介类 MyZhongjie.java

  1. package org.rico.learnSpring.customProxy;
  2. import org.rico.learnSpring.findHouse.Person;
  3. import java.lang.reflect.Method;
  4. //房屋中介类
  5. public class MyZhongjie implements MyInvocationHandler{
  6. private Person target;
  7. //获取被代理人的个人资料
  8. public Object getInstance(Person target) throws Exception {
  9. this.target=target;
  10. Class clazz=target.getClass();
  11. System.out.println("被代理对象的class是:"+clazz.getName());
  12. return MyProxy.newProxyInstance(new MyClassLoader(), clazz.getInterfaces(), this);//第一个参数是ClassLoader,第二个参数是interfaces,第三个参数是InvocationHandler也即本身,因为zhongjie本身实现了InvocationHandler
  13. }
  14. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  15. System.out.println("代理开始");
  16. System.out.println("我是中介,你叫"+this.target.getName()+",你住"+this.target.getAddress()+"附近,我帮你找房子,找了几个合适的你挑选一下");
  17. Object result = method.invoke(this.target, args);//这里其实并没有返回值
  18. System.out.println("代理结束");
  19. return result;
  20. }
  21. }

其中用到了MyProxy和MyInvocationhandler接口还有MyClassLoader

代理类MyProxy.java的newProxyInstance会生成新的java源码并编译成class然后classLoader会加载进JVM,gtInstance方法return的就是这个新生成并加载的class 然后用Person接口来接收(在测试类中),然后调用findHouse时就调用了这个新生成的类的findHouse(见上图新“生成的类”) 而这个findHouse方法又调用了中介的invoke方法,当然会把实际调用的方法名也传入,例如findhouse,然后调到了中介的invoke方法,中介的invoke方法是

  1. System.out.println("代理开始");
  2. System.out.println("我是中介,你叫"+this.target.getName()+",你住"+this.target.getAddress()+"附近,我帮你找房子,找了几个合适的你挑选一下");
  3. Object result = method.invoke(this.target, args);//这里其实并没有返回值
  4. System.out.println("代理结束");
  5. return result;

其中有一行是

  1. Object result = method.invoke(this.target, args);

又调用了method.lnvoke,这才真正调用了张三的实际方法,但是在调用张三方法之前和之后,就是我们在代理类中需要在调用张三方法之前需要执行的操作,达到了我们的目的

下面是MyProxy的代码

  1. package org.rico.learnSpring.customProxy;
  2. import javax.tools.JavaCompiler;
  3. import javax.tools.StandardJavaFileManager;
  4. import javax.tools.ToolProvider;
  5. import java.io.File;
  6. import java.io.FileWriter;
  7. import java.io.IOException;
  8. import java.lang.reflect.Constructor;
  9. import java.lang.reflect.Method;
  10. //这个类会生成代理对象的代码
  11. public class MyProxy {
  12. private static final String ln="\n";//换行
  13. //这个方法很重要 用于生成代理对象的代码
  14. public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) {
  15. //1 生成源代码
  16. String proxySourceCode=generateSourceCode(interfaces[0]);
  17. //2 将生成的源代码输出到磁盘,保存为.java文件
  18. final String filePath = MyProxy.class.getResource("").getPath();
  19. //final String filePath = "C:\\Users\\chenhongjie\\Desktop\\";
  20. File f= new File(filePath+"$Proxy0.java");//.java文件名和类名一致
  21. try {
  22. FileWriter fw=new FileWriter(f);
  23. fw.write(proxySourceCode);
  24. fw.flush();
  25. fw.close();
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. }
  29. //3 编译源代码 ,并且生成.class文件
  30. try {
  31. JavaCompiler compiler= ToolProvider.getSystemJavaCompiler();//javax提供的编译器
  32. StandardJavaFileManager manager=compiler.getStandardFileManager(null,null,null);
  33. Iterable iterable=manager.getJavaFileObjects(f);//获取迭代器 就是为了找到Java文件
  34. JavaCompiler.CompilationTask task=compiler.getTask(null,manager,null,null,null,iterable);//编译任务
  35. task.call();
  36. manager.close();
  37. //4 通过MyClassLoader将class文件中的内容动态加载到JVM中来
  38. Class proxyClass=loader.findClass("$Proxy0");
  39. Constructor c=proxyClass.getConstructor(MyInvocationHandler.class);//定义构造函数
  40. f.delete();//获取到构造函数后可以把源文件删掉 JDK是这么做的,当然你也可以不删
  41. //5 返回被代理后的代理对象
  42. return c.newInstance(h);
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. }
  46. return null;
  47. }
  48. //重新根据接口生成代码(拼代码)
  49. private static String generateSourceCode(Class<?> interfaces) {
  50. //包名
  51. StringBuilder sourceCode=new StringBuilder("package org.rico.learnSpring.customProxy;").append(ln);//包名
  52. //导包
  53. sourceCode.append("import java.lang.reflect.Method;").append(ln).append(ln);
  54. //声明类
  55. sourceCode.append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append(ln);//这里只实现了一个,如果通用的话要写循环//这里的类名是随便的,只要.java文件和类名一样就可以了这里是$Proxy0
  56. sourceCode.append(" MyInvocationHandler h;").append(ln);//域 局部变量
  57. //构造函数
  58. sourceCode.append(" public $Proxy0(MyInvocationHandler h){").append(ln);//这个构造函数名应该是动态生成的,但是这里写死了,为了方便
  59. sourceCode.append(" this.h=h;").append(ln);
  60. sourceCode.append("}").append(ln);//匹配public $Proxy0的{
  61. for(Method m:interfaces.getMethods()){
  62. sourceCode.append(" public ").append(m.getReturnType().getName()).append(" ").append(m.getName()).append("(){").append(ln);//我们的Person方法都是无参的,这里也是图简便,应该是要把参数也遍历出来的
  63. sourceCode.append(" try{").append(ln);
  64. //获取这个Method
  65. sourceCode.append(" Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(m.getName()).append("\",new Class[0]);").append(ln);//new Class[]{}
  66. sourceCode.append(" this.h.invoke(this,m,null);").append(ln);
  67. sourceCode.append(" }catch(Throwable e){e.printStackTrace();}").append(ln);
  68. if(!m.getReturnType().getName().equals("void")){//如果返回值不为空,返回
  69. sourceCode.append(" return null;").append(ln);//这里为了方便直接返回null
  70. }
  71. sourceCode.append(" }").append(ln);
  72. }
  73. sourceCode.append("}");//匹配public class 的 {
  74. return sourceCode.toString();
  75. }
  76. }

MyClassLoader.java的代码

  1. package org.rico.learnSpring.customProxy;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.IOException;
  6. //实现了代码生成,编译,动态重新load到JVM中
  7. public class MyClassLoader extends ClassLoader {
  8. private final File baseDir;
  9. public MyClassLoader(){
  10. final String basePath=MyClassLoader.class.getResource("").getPath();
  11. this.baseDir=new File(basePath);
  12. }
  13. @Override
  14. protected Class<?> findClass(String name) throws ClassNotFoundException {
  15. //加载到JVM中来
  16. String className=MyClassLoader.class.getPackage().getName()+"."+name;
  17. System.out.println(className);
  18. if(baseDir!=null){
  19. File classFile=new File(baseDir,name.replace("\\.","/")+".class");//点.替换成斜杠/
  20. if(classFile.exists()){
  21. FileInputStream in=null;
  22. try{
  23. in =new FileInputStream(classFile);
  24. ByteArrayOutputStream out=new ByteArrayOutputStream();
  25. byte [] buff=new byte[1024];
  26. int len;
  27. while ((len=in.read(buff))!=-1){
  28. out.write(buff,0,len);;
  29. }
  30. out.close();
  31. return defineClass(className,out.toByteArray(),0,out.size());
  32. }catch (Exception e){
  33. e.printStackTrace();
  34. }finally {
  35. if(null!=in){
  36. try {
  37. in.close();
  38. } catch (IOException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. classFile.delete();//加载到jvm后delete掉(要在in,out都删掉之后再关) JDK是删掉的,当然你也可以不删掉
  43. }
  44. }
  45. }
  46. return null;
  47. }
  48. }

MyInvocationHandler.java的代码

JDK原生的InvocationHandler也是一个接口,用于代理类来实现的,本例中就是中介

  1. package org.rico.learnSpring.customProxy;
  2. import java.lang.reflect.Method;
  3. public interface MyInvocationHandler{
  4. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  5. }

代理成功!

通过这个案例就理解了JDK动态代理的原理,真正是如何实现的

复习一下原理

  1. 拿到被代理对象的引用(张三类),然后获取它实现的接口接口也就是Person
  2. JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口 Person接口)给每个方法内部实现都写成this.h.invoke,这样就变成了调用中介类的invoke,当然也会把实际需要调用的方法名传入给中介的invoke以便执行完代理逻辑前/后调用真正的方法

总结一下有点像狸猫换太子:新生成一个类,类里的所有方法都去调用代理类的那个invoke方法,同时把真正需要调用的方法名也传入。 然后代理类的invoke就被执行了,然后代理类的invoke在执行代理逻辑时再调用下真实那个被代理类的那个方法

总结:字节码重组

上面的JDK的动态代理一定要写一个接口,例如案例中的Person(张三实现了Person接口,所以在张三类中可以拿到它实现的接口也就是Person),因为我们生成的class文件需要实现这个接口,所以必须要这个接口,否则实现不了。CGlib不需要接口就可以实现动态代理

代理的实现分动态代理静态代理

静态代理的实现是对已经生成了的JAVA类进行封装。

动态代理则是在运行时生成了相关代理类,在JAVA中生成动态代理一般有两种方式

  • JDK实现代理生成,是用类 java.lang.reflect.Proxy, 实现方式 就是上面这个案例
  • 用CGLib实现 CGLIB是一个开源项目

Spring中,bean都是由动态代理生成出来的 Spring AOP中,当拦截对象实现了接口时,生成方式是用JDK的Proxy类。当没有实现任何接口时用的是GCLIB开源项目生成的拦截类的子类. Spring是集成了CGLib的

CGLib的实现原理: 自动生成一个类,继承自己的那个被代理类,然后把子类的引用指向父 类 ,同样是做了字节码重组,

好处就是可以少写一个接口 ,对于使用API的用户来说是无感知的

CGLib案例

Zhangsan.java 张三

  1. public class Zhangsan {
  2. public void findHouse(){
  3. System.out.println("我要找房子");
  4. }
  5. }

中介 Zhongjie.java

  1. public class Zhongjie implements MethodInterceptor {//interceptor的英文意思是拦截机
  2. public Object getInstance (Class clazz) throws Exception{
  3. //通过反射机制,实例化
  4. Enhancer enhancer=new Enhancer();
  5. //把父类设置为谁?
  6. enhancer.setSuperclass(clazz);//传入被代理对象的Class,这一步就是告诉cglib,生成的子类需要继承哪个类
  7. enhancer.setCallback(this);//设置回调 后续this.h.invoke调用的是本身的intercept方法
  8. return enhancer.create();//第一步生成源代码,第二步编译Class,第三步加载到JVM中,并返回被代理后的对象
  9. }
  10. @Override
  11. public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {//这里的obj并不是我们new出来的被代理对象的引用,而是被代理对象的class
  12. System.out.println("代理开始");
  13. System.out.println("被调用的方法名:"+method.getName());
  14. //这个obj的引用是由CGLib给我们new出来的,CGLib new出来以后的对象是被代理对象的子类(继承了我们自己写的那个被代理对象也就是Zhangsan.java)
  15. //new子类的同时,必须先new出父类来,这就相当于是间接的持有了我们父类的引用,子类重写了父类的所有的方法,变成类似于this.h.invoke(...),我们改变子类对象的某些属性是可以间接的操作父类的属性的
  16. Object result=methodProxy.invokeSuper(obj,args);//一定是调Super,这里调用Super其实就是原始的那个被代理对象的那个原始方法.不能写Invoke不然会死循环 相当于在findHose中调用findHouse
  17. System.out.println("代理结束");
  18. return result;
  19. }
  20. }

测试类,main方法

  1. public static void main(String[] args) {
  2. //JDK的动态代理是通过接口来进行强制转换的,生成以后的代理对象可以强制转换为接口
  3. //CGLib的动态代理是通过生成一个被代理对象的子类,然后重写父类的方法,生成以后的对象时可以强制转换为被代理对象,子类引用赋值给父类
  4. Zhongjie zhongjie =new Zhongjie();
  5. try{
  6. //Zhangsan zhangsan=(Zhangsan)zhongjie.getInstance(new Zhangsan());//其实没用到这个对象的引用,只需要它的class,这样写只是方便,在Zhongjie.java中也是obj.getClass,为了不避免误会,采用下面的方式
  7. Zhangsan zhangsan=(Zhangsan)zhongjie.getInstance(Zhangsan.class);
  8. zhangsan.findHouse();
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. }
  12. }

代理模式用的最多的就是SpringAOP

可以做什么事情?可以在每一个方法调用之前加一些代码,在方法调用之后再加一些代码

AOP一般用在哪里 :事务代理(声明式事务,哪个方法需要加事务,哪个方法不需要加事务s)、日志

在调用service方法之前开启(open)一个事务,事务的执行是由我们自己的代码完成的,事务执行完成后监听是否有异常,可能要根据异常的类型来决定这个事务是否要回滚(rollaback)还是继续提交(commit),最后事务还要关闭(close)

上面的流程中,除了事务的执行(加粗部分),其他操作都是AOP帮我们做的

AOP是通过代理这种技术来实现的,AOP不是代理,代理也不是AOP

何时Spring使用JDK/CGLib代理

当有接口时使用JDK动态代理,没有时使用CGLib动态代理。

当proxyTargetClass为true是不管什么情况都使用CGLib

扩展阅读:

十分钟理解Java之动态代理

https://www.jianshu.com/p/942a4a349ac4

https://www.jianshu.com/p/774c65290218

https://www.cnblogs.com/lianghaoc/p/5699537.html

https://segmentfault.com/a/1190000007089902

This blog is under a CC BY-NC-SA 3.0 Unported License
本文链接:http://hogwartsrico.github.io/2018/09/01/proxy-in-Spring/