zoukankan      html  css  js  c++  java
  • Java代理简述

      1.什么是代理?

      对类或对象(目标对象)进行增强功能,最终形成一个新的代理对象,(Spring Framework中)当应用调用该对象(目标对象)的方法时,实际调用的是代理对象增强后的方法,比如对功能方法login实现日志记录,可以通过代理实现;

      PS:目标对象--被增强的对象;代理对象--增强后的对象;

      2.为什么需要代理?

      一些类里面的方法有相同的代码或类中有相同的功能,可以将这些相同抽取出来形成一个公共的方法或功能,但Java有两个重要的原则:单一职责(对类来说的,即一个类应该只负责一项职责)和开闭原则(开放扩展,修改关闭),如果每个类的每个功能都调用了公共功能,就破坏了单一职责,如下图;如果这个类是别人已经写好的,你动了这个代码,同时也破坏了开闭原则(同时改动代码很麻烦,里面可能涉及其他很多的调用,可能带出无数的bug,改代码比新开发功能还难/(ㄒoㄒ)/~~);

      由于有上面的问题存在,使用代理来实现是最好的解决办法;

      3.Java实现代理有哪些?

      (1)静态代理:通过对目标方法进行继承或聚合(接口)实现;(会产生类爆炸,因此在不确定的情况下,尽量不要使用静态代理,避免产生类爆炸)

        1)继承:代理对象继承目标对象,重写需要增强的方法;

    //业务类(目标对象)
    public class UserServiceImpl {
        public void query(){
            System.out.println("业务操作查询数据库....");
        }
    }
    //日志功能类
    public class Log {
        public static void info(){
            System.out.println("日志功能");
        }
    }
    //继承实现代理(代理对象)
    public class UserServiceLogImpl extends UserServiceImpl {
        public void query(){
            Log.info();
            super.query();
        }
    }

        从上面代码可以看出,每种增强方法会产生一个代理类,如果现在增强方法有日志和权限,单个方法增强那需要两个代理类(日志代理类和权限代理类),如果代理类要同时拥有日志和权限功能,那又会产生一个代理类,同时由于顺序的不同,可能会产生多个类,比如先日志后权限是一个代理类,先权限后日志又是另外一个代理类。

        由此可以看出代理使用继承的缺陷:产生的代理类过多(产生类爆炸),非常复杂,维护难;

        2)聚合:代理对象和目标对象都实现同一接口,使用装饰者模式,提供一个代理类构造方法(代理对象当中要包含目标对象),参数是接口,重写目标方法;

    //接口
    public interface UserDao {
        void query();
    }
    //接口实现类:目标对象
    public class UserDaoImpl implements  UserDao {
        @Override
        public void query() {
            System.out.println("query......");
        }
    }
    //日志功能类
    public class Log {
        public static void info(){
            System.out.println("日志功能");
        }
    }
    //聚合实现代理:同样实现接口,使用装饰者模式;(代理对象)
    public class UserDaoLogProxy implements UserDao {
        UserDao userDao;
        public UserDaoLogProxy(UserDao userDao){//代理对象包含目标对象
            this.userDao = userDao;
        }
        @Override
        public void query() {
            Log.info();
            userDao.query();
        }
    }
    //测试
    public static void main(String[] args) {
        UserDaoImpl target = new UserDaoImpl();
        UserDaoLogProxy proxy = new UserDaoLogProxy(target);
        proxy.query();
    }

        聚合由于利用了面向接口编程的特性,产生的代理类相对继承要少一点(虽然也是会产生类爆炸,假设有多个Dao,每个Dao产生一个代理类,所以还是会产生类爆炸),如下案例:

    //时间记录功能
    public class Timer {
        public static void timer(){
            System.out.println("时间记录功能");
        }
    }
    //代理类:时间功能+业务
    public class UserDaoTimerProxy implements UserDao {
    
        UserDao userDao;
        public UserDaoTimerProxy(UserDao userDao){
            this.userDao = userDao;
        }
        @Override
        public void query() {
            Timer.timer();
            userDao.query();
        }
    }
    //测试
    public static void main(String[] args) {
        //timer+query
        UserDao target = new UserDaoTimerProxy(new UserDaoImpl());
        //log+timer+query
        UserDao proxy = new UserDaoLogProxy(target);
        proxy.query();
    }
    public static void main(String[] args) {
        //log+query
        UserDao target = new UserDaoLogProxy(new UserDaoImpl());
        //timer+log+query
        UserDao proxy = new UserDaoTimerProxy(target);
        proxy.query();
    }

      PS:

        装饰和代理的区别:代理不需要指定目标对象,可以对任何对象进行代理;装饰需要指定目标对象,所以需要构造方法参数或set方法指定目标对象;

        几个io的Buffer流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)就是使用了装饰模式的静态代理;

    public class BufferedReader extends Reader {
        private Reader in;
        ........
        public BufferedReader(Reader in, int sz) {
            super(in);
            if (sz <= 0)
                throw new IllegalArgumentException("Buffer size <= 0");
            this.in = in;
            cb = new char[sz];
            nextChar = nChars = 0;
        }
    
        public BufferedReader(Reader in) {
            this(in, defaultCharBufferSize);
        }
    }

      (2)动态代理:Java有JDK动态代理和CGLIB代理;(Spring Framework通过Spring AOP实现代理,底层还是使用JDK代理和CGLIB代理来实现)

        模拟动态代理:不需要手动创建类文件(因为一旦手动创建类文件,会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,然后利用URLclassLoader把这个动态编译的类加载到jvm中,然后通过反射把这个类实例化。(通过字符串产生一个对象实现代理);

        PS:Java文件 -> class文件 -> byte字节(JVM)-> class对象(类对象)-> new(对象);

        所以步骤如下:

          1)代码实现一个内容(完整的Java文件内容,包含包名、变量、构造方法、增强后的方法等),使其通过IO产生一个Java文件;
          2)通过第三方编译技术产生一个class文件;
          3)加载class文件利用反射实例化一个代理对象出来;
    public class ProxyUtil {
        /**
         *  content --->string
         *     |
         *     |生成
         *     v
         *  .java   <-----通过io产生
         *  .class  <-----Java文件编程产生
         *
         *  .new    <-----class文件反射产生实例对象
         * @return
         */
        public static Object newInstance(Object target){
            Object proxy=null;
            //根据对象获取接口
            Class targetInf =target.getClass().getInterfaces()[0];
            //获取接口的所有方法
            //getMethods(),该方法是获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的)
            //getDeclaredMethods(),该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法
            Method[] declaredMethods = targetInf.getDeclaredMethods();
            String line="
    ";
            String tab ="	";
            //接口名称
            String targetInfName = targetInf.getSimpleName();
            String content ="";
            //包位置
            String packageContent = "package com;"+line;
            //接口
            String importContent = "import "+targetInf.getName()+";"+line;
            String clazzFirstLineContent = "public class $Proxy implements "+targetInfName+"{"+line;
            //属性
            String filedContent  =tab+"private "+targetInfName+" target;"+line;
            //构造方法
            String constructorContent =tab+"public $Proxy ("+targetInfName+" target){" +line
                    +tab+tab+"this.target =target;"
                    +line+tab+"}"+line;
            //方法
            String methodContent = "";
            for(Method method:declaredMethods){
                //返回值
                String returnTypeName = method.getReturnType().getSimpleName();
                //方法名
                String methodName = method.getName();
                // Sting.class String.class 参数类型
                Class<?>[] parameterTypes = method.getParameterTypes();
                String argsContent = "";
                String paramsContent="";
                int flag = 0;
                for(Class args :parameterTypes){
                    //String,参数类型
                    String simpleName = args.getSimpleName();
                    //String p0,Sting p1,
                    argsContent+=simpleName+" 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;
                if(returnTypeName.equals("void")){
                    methodContent+=tab+tab+"target."+methodName+"("+paramsContent+");"+line
                            +tab+"}"+line;
                }else{
                    methodContent+=tab+tab+"return target."+methodName+"("+paramsContent+");"+line
                            +tab+"}"+line;
                }
            }
            content+=packageContent+importContent+clazzFirstLineContent+filedContent
                    +constructorContent+methodContent+"}";
            //生成Java文件
            File file = new File("D:\com\$Proxy.java");
            try{
                if(!file.exists()){
                    file.createNewFile();
                }
                //创建
                FileWriter wr = new FileWriter(file);
                wr.write(content);
                wr.flush();
                wr.close();
                //编译Java文件
                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();
                //new --> 反射
                URL[] urls = new URL[]{new URL("file:D:\\")};
                //加载外部文件
                URLClassLoader classLoader = new URLClassLoader(urls);
                Class<?> proxyClass = classLoader.loadClass("com.$Proxy");
                Constructor constructor = proxyClass.getConstructor(targetInf);
                //构造方法创建实例
                proxy = constructor.newInstance(target);
                //clazz.newInstance();//根据默认构造方法创建对象
                //Class.forName()
            }catch (Exception e){
                e.printStackTrace();
            }
            return proxy;
        }
    }

      自定义代理的缺点:生成Java文件;动态编译文件;需要一个URLClassLoader;涉及到了IO操作,软件的最终性能体现到了IO操作,即IO操作影响到软件的性能;

    案例:

      1)接口:

    public interface UserDao {
         void query();
         void query(String name);
         String getName(String id);
    }

      2)实现类:

    public class UserService implements UserDao {
        @Override
        public void query() {
            System.out.println("query");
        }
    
        @Override
        public void query(String name) {
            System.out.println(name);
        }
    
        @Override
        public String getName(String id) {
            System.out.println("id:"+id);
            return "李四";
        }
    }

      3)测试:

    public static void main(String[] args) {
        UserDao dao = (UserDao) ProxyUtil.newInstance(new UserService());
        dao.query();
        dao.query("张三");
    }
    --------结果--------
    log
    query
    log
    张三

              

    package com;
    import com.hrh.dynamicProxy.dao.UserDao;
    public class $Proxy implements UserDao{//自定义动态代理生成的文件
        private UserDao target;
        public $Proxy (UserDao target){
            this.target =target;
        }
        public String getName(String p) {
            System.out.println("log");
            return target.getName(p);
        }
        public void query(String p) {
            System.out.println("log");
            target.query(p);
        }
        public void query() {
            System.out.println("log");
            target.query();
        }
    }

      JDK动态代理:基于反射实现,通过接口反射得到字节码,然后将字节码转成class,通过一个native(JVM实现)方法来执行;

      案例实现:实现 InvocationHandler 接口重写 invoke 方法,其中包含一个对象变量和提供一个包含对象的构造方法;

    public class MyInvocationHandler implements InvocationHandler {
        Object target;//目标对象
        public MyInvocationHandler(Object target){
            this.target=target;
        }
        /**
         * @param proxy 代理对象
         * @param method 目标对象的目标方法
         * @param args    目标方法的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("log");
            return method.invoke(target,args);
        }
    }
    public class MyInvocationHandlerTest {
        public static void main(String[] args) {
            //参数: 当前类的classLoader(保证MyInvocationHandlerTest当前类可用)
            //         接口数组:通过接口反射得到接口里面的方法,对接口里面的所有方法都进行代理
            //         实现的InvocationHandler:参数是目标对象
            UserDao jdkproxy = (UserDao) Proxy.newProxyInstance(MyInvocationHandlerTest.class.getClassLoader(),
                    new Class[]{UserDao.class},new MyInvocationHandler(new UserService()));
            jdkproxy.query("query");
            //-----结果------
            //log
            //query
        }
    }

      下面查看底层JDK生成的代理类class:

            byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy18", new Class[]{UserDao.class});
    
            try {
                FileOutputStream fileOutputStream = new FileOutputStream("xxx本地路径\$Proxy18.class");
                fileOutputStream.write(bytes);
                fileOutputStream.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

      或在代码的最前面添加下面代码:

    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "xxx本地路径");

      代理类class反编译后的内容:下面的内容验证了JDK动态代理为什么基于聚合(接口)来的,而不能基于继承来的?因为JDK动态代理底层已经继承了Proxy,而Java是单继承,不支持多继承,所以以接口来实现;

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    import com.hrh.dao.UserDao;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy18 extends Proxy implements UserDao {
        private static Method m1;
        private static Method m4;
        private static Method m5;
        private static Method m2;
        private static Method m0;
        private static Method m3;
    
        public $Proxy18(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void query(String var1) throws  {
            try {
                super.h.invoke(this, m4, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void query() throws  {
            try {
                super.h.invoke(this, m5, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final String getName(String var1) throws  {
            try {
                return (String)super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m4 = Class.forName("com.hrh.dao.UserDao").getMethod("query", Class.forName("java.lang.String"));
                m5 = Class.forName("com.hrh.dao.UserDao").getMethod("query");
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
                m3 = Class.forName("com.hrh.dao.UserDao").getMethod("getName", Class.forName("java.lang.String"));
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }

      CGLIB代理:借助asm(一个操作字节码的框架)实现代理操作;CGLIB基于继承来的(前文有CGLIB代理类class的反编译可以看出);

    public class UserService {
        public void query(){
            System.out.println("query");
        }
    
        public static void main(String[] args) {
            // 通过CGLIB动态代理获取代理对象的过程
            Enhancer enhancer = new Enhancer();
            // 设置enhancer对象的父类
            enhancer.setSuperclass(UserService.class);
            // 设置enhancer的回调对象
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    System.out.println("before method run...");
                    Object result = proxy.invokeSuper(obj, args);
                    System.out.println("after method run...");
                    return result;
                }
            });
            //创建代理对象
            UserService bean = (UserService) enhancer.create();
            bean.query();
        }
    }
    //-----------结果------
    before method run...
    query
    after method run...

      PS:如果只是对项目中一个类进行代理,可以使用静态代理,如果是多个则使用动态代理;

      关于代理的其他相关知识介绍可参考前文:Spring(11) - Introductions进行类扩展方法Spring笔记(3) - SpringAOP基础详解和源码探究

    作者:huangrenhui
    欢迎任何形式的转载,但请务必注明出处。
    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
    如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
    如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【码猿手】。
    限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
  • 相关阅读:
    Kettle进行数据迁移(ETL)
    Visual Studio常用快捷键
    RESTful API 设计指南
    理解RESTful架构
    LINQ for XML简单示例
    .NET导入导出Excel方法总结
    AJAX + WebService 实现文件上传
    GitHub使用教程
    .NET读取Excel数据,提示错误:未在本地计算机上注册“Microsoft.ACE.OLEDB.12.0”提供程序
    ASP.NET 操作Excel中的DCOM配置方式
  • 原文地址:https://www.cnblogs.com/huangrenhui/p/14691856.html
Copyright © 2011-2022 走看看