• 动态代理在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="
    ";
            String tab ="	";
            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();
          }
      }
    }
  • 相关阅读:
    从属性文件中读取配置
    Page Object Manager
    在Selenium中使用JavaScriptExecutor处理Ajax调用?
    wait
    常用操作
    Selenium收藏官方网址
    PageObject样例
    解决办法-错误:Access denied for user 'root'@'localhost'
    Struts2中的OGNL详解
    用C++,调用浏览器打开一个网页
  • 原文地址:https://www.cnblogs.com/tianboblog/p/12625334.html
Copyright © 2020-2023  润新知