动态代理在Spring中的应用

Posted 领域见识决定专业高度

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态代理在Spring中的应用相关的知识,希望对你有一定的参考价值。

Spring中主要使用cglib和jdk动态代理,主要在SpringAop中有大量应用。

JDK动态代理

jdk动态代理主要使用场景是被代理的对象有实现的接口。最终生成的代理类:

class $Proxy0 extends Proxy implements IDao

 

jdk动态代理主要是基于反射,其实我们完全可以自己模拟;其中两个比较关键的思路:

  1. 使用反射解析目标对象的属性、方法等
  2. 根据解析的内容生成proxy.class,说白了就是把要生成的class按照字符串的形式拼接,最终通过ClassLoader加载。
package com.tian.proxy;
import com.sun.jndi.toolkit.url.UrlUtil;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;


public class ProxyUtil {
    public static Object newInstance(Object target){
        Object proxy=null;
        Class targetInf = target.getClass().getInterfaces()[0];
        Method methods[] =targetInf.getDeclaredMethods();
        String line="\\n";
        String tab ="\\t";
        String infName = targetInf.getSimpleName();
        String content ="";
        String packageContent = "package com.tian;"+line;
        String importContent = "import "+targetInf.getName()+";"+line;
        String clazzFirstLineContent = "public class $Proxy implements "+infName+"{"+line;
        String filedContent  =tab+"private "+infName+" target;"+line;
        String constructorContent =tab+"public $Proxy ("+infName+" target){" +line
                                  +tab+tab+"this.target =target;"
                                  +line+tab+"}"+line;
        String methodContent = "";
        for (Method method : methods) {
            String returnTypeName = method.getReturnType().getSimpleName();
            String methodName =method.getName();
            // Sting.class String.class
            Class args[] = method.getParameterTypes();
            String argsContent = "";
            String paramsContent="";
            int flag =0;
            for (Class arg : args) {
                String temp = arg.getSimpleName();
                //String
                //String p0,Sting p1,
                argsContent+=temp+" p"+flag+",";
                paramsContent+="p"+flag+",";
                flag++;
            }
            if (argsContent.length()>0){
                argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1);
                paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1);
            }
            methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+") {"+line
                          +tab+tab+"System.out.println(\\"log\\");"+line
                          +tab+tab+"target."+methodName+"("+paramsContent+");"+line
                          +tab+"}"+line;




        }
        content=packageContent+importContent+clazzFirstLineContent+filedContent+constructorContent+methodContent+"}";


        File file =new File("d:\\\\com\\\\tian\\\\$Proxy.java");
        try {
            if (!file.exists()) {
                file.createNewFile();
            }




            FileWriter fw = new FileWriter(file);
            fw.write(content);
            fw.flush();
            fw.close();


            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
            Iterable units = fileMgr.getJavaFileObjects(file);


            JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
            t.call();
            fileMgr.close();


            URL[] urls = new URL[]{new URL("file:D:\\\\\\\\")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class clazz = urlClassLoader.loadClass("com.tian.$Proxy");


            Constructor constructor = clazz.getConstructor(targetInf);


            proxy = constructor.newInstance(target);
        }catch (Exception e){
            e.printStackTrace();
        }
        return proxy;
    }
}


 IDao proxy = (IDao) ProxyUtil.newInstance(new 
CGLIB动态代理

cglib代理主要使用场景是:被代理对象的是类而没有任何接口实现。通过字节码增强实现动态代理(底层使用了asm)。在Spring中一个比较重要的应用就是解析@Configuration注解。Spring应用中只要在相应的class上添加了@Configuration注解,就被认为是一个全注解的类(看spring源码看实例化BeanDefinition对象时,会设置一个属性标识为full;当然与之对应一个是lite,就是标注了@Component,@ComponentScan,@Import,@ImportResource,@Bean这些注解的类)。

 

添加了@Configuration一个比较重要的作用就是会把该配置类使用cglib进行字节码增强,其实主要目的就是Spring可以更好的管理Bean的依赖关系了,如下示例:

定义要给配置类:

@Configuration
@ComponentScan("com.tian.*")
public class AppConfig {
   @Bean
   public UserDaoImpl1 userDaoImpl1(){
      return new UserDaoImpl1();
   }


   @Bean
   public UserDaoImpl2 userDaoImp2(){
      userDaoImpl1();
      return new UserDaoImpl2();
   }
}

相应的类:

public class UserDaoImpl1 {
   public UserDaoImpl1() {
      System.out.println("UserDaoImpl1 init......");
   }


   public void query() {
      System.out.println("UserDaoImpl1 query.......");
   }
}


public class UserDaoImpl2 {
   public UserDaoImpl2() {
      System.out.println("UserDaoImpl2 init......");
   }


   public void query() {
      System.out.println("UserDaoImpl2 query......");
   }

 

执行如下:会发现UserDaoImpl1 init......只会打印一次,当然如果把@Configuration注解去掉就会打印两次。增强带来的好处是:Spring可以更好的管理Bean的依赖关系了。比如@Bean之间方法之间的调用,其实是去Spring容器里去找Bean了,而并不是再生成了一个实例。

 

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

通过查看源码发现使用cglib做了动态代理:

Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);

同样可以简单模拟下,依然使用如上相关类。新增一个关键的类,主要就是通过它进行回调增强:

public class MyEnhancerCallBack implements MethodInterceptor {
   @Override
   public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
      System.out.println("cglib proxy.......");


      return methodProxy.invokeSuper(o, objects);
   }
}

测试:

//可以查看cglib生成的class
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "G:\\\\demo");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserDaoImpl1.class);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setCallback(new MyEnhancerCallBack());
UserDaoImpl1 userDaoImpl1 =(UserDaoImpl1) enhancer.create();
userDaoImpl1.query();

 

可以把生成class拷贝到idea中

 

 

取部分代码:

//其实就是把代理的对象作为父类
public class UserDaoImpl1$$EnhancerBySpringCGLIB$$a779f942 extends UserDaoImpl1 implements Factory {
  final void CGLIB$query$0() {
    super.query();
  }


  public final void query() {
      //通过这个进行回调增强
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if (var10000 == null) {
          CGLIB$BIND_CALLBACKS(this);
          var10000 = this.CGLIB$CALLBACK_0;
      }
  
      if (var10000 != null) {
          var10000.intercept(this, CGLIB$query$0$Method, CGLIB$emptyArgs, CGLIB$query$0$Proxy);
      } else {
          super.query();
      }
  }
}

 

以上是关于动态代理在Spring中的应用的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之代理模式详解和应用

Spring AOP详解 JDK动态代理CGLib动态代理

转载Spring AOP详解 JDK动态代理CGLib动态代理

Spring中的cglib动态代理

Spring学习记录

Spring——AOP的相关通知及应用