zoukankan      html  css  js  c++  java
  • Java 静态代理和动态代理

    代理Proxy

    Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象是带来的问题

      代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

      为了保持 代理类 和 委托类 的行为一致性, 代理类 和 委托类 通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。而且通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好的隐藏和保护委托类对象,同时也为了实施不同控制策略预留了空间,从而在设计上获得更大的灵活性。

      更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

    使用场合举例:

    如果需要 委托类 处理某一业务,那么我们就可以先在代理类中统一处理后在调用具体实现类

    按照代理的创建时间,代理类可以分为两种:

    静态:由程序员创建的代理类 或 特定的工具自动生成源代码在对其编译。在程序运行前代理类的.class文件就已经存在了。

    动态类:在程序运行时运用 反射机制 动态创建而成。

    静态代理特点:

       1、需要知道核心类(被代理类)是哪一个类,并且有什么方法。 

       2、非核心的代码需要重复写多次,显得代码的结构臃肿,形成代码冗余。

       3、非核心类(代理类)需要实现核心类(被代理类)实现的接口,也就是他们需要实现共同的接口,但是以核心类实现的接口(被代理类)为准。

    动态代理特点:

       1、不需要知道核心类(被代理类)具体是什么类。

       2、非核心类(代理类)需要实现InvocationHandler接口。

    1、静态代理:

      1、委托类和代理类要实现的接口

    public interface UserManager {
        public void addUser(String userId,String userName);
    }

      2、委托类

    public class UserManagerImpl implements UserManager {
        @Override
        public void addUser(String userId, String userName) {
            System.out.println("UserManagerImpl.addUser");
        }
    }

      3、代理类

    public class UserManagerImplProxy implements UserManager{
    
        private UserManager userManager;
    
        public UserManagerImplProxy(UserManager _userManager){
            this.userManager=_userManager;
        }
    
        @Override
        public void addUser(String userId, String userName) {
            try {
                System.out.println("start--->addUser()");
                userManager.addUser(userId,userName);
                System.out.println("success--->addUser()");
            }catch (Exception e){
                System.out.println("error--->addUser()");
            }
        }
    }

      4、客户端

    public class TestClient {
        public static void main(String[] args) {
           UserManager userManager=new UserManagerImplProxy(new UserManagerImpl()); 
        userManager.addUser(
    "A001","Not_Copy");
      }
    }

    结果:

      start--->addUser()
      UserManagerImpl.addUser
      success--->addUser()

    静态代理类优缺点

    优点:

      代理类 使 客户端 不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,new UserManagerImpl() 可以应用工厂将它隐藏。

    缺点:

      1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的重复代码。如果接口添加了一个方法,除了所有的实现类需要实现这个方法外,所有的相关代理也需要实现此方法,增加了代码维护的复杂程度。

      2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时,就无法胜任了,如上的代码只是为UserManager类的访问提供代理,但是如果还要为其他类如 Department类 提供代理的话,就需要我们再次添加 Department类 的 代理类。

    2、引入动态代理:

      通过一个代理类完成全部的代理功能,那么我们就需要用动态代理

      在静态代理的示例中,一个代理类 只能代理一种类型,而且是在编译器就已经确认被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。

    在Java中想要实现动态代理机制,需要 java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy 类的支持

      委托类  和静态代理中的委托类一样

      动态创建代理对象的类

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    /**
     * 动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。
     * 该invoke方法就是调用被代理接口的所有方法时需要调用的,
     * 该invoke方法返回的值是被代理接口的一个实现类
     */
    public class LogHandler implements InvocationHandler {
        //目标对象
        private Object targetObject;
        /**
         * 绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
         */
        public Object newProxyInstance(Object targetObject) {
            this.targetObject = targetObject;
            /**
             * 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
             * 第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
             * 第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
             * 第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
             * 根据传入的目标返回一个代理对象
             */
            return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
        }
        /**
         *关联的这个实现类的方法被调用时将被执行
         *InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("start--->>");
            for (int i = 0; i< args.length; i++) {
                System.out.println(args[i]);
            }
            Object ret=null;
            try {
                /**
                 * 原对象方法调用前处理日志信息
                 */
                System.out.println("start--->>");
                //调用目标方法
                ret=method.invoke(targetObject,args);
                /**
                 * 原对象方法调用后处理日志信息
                 */
                System.out.println("success--->>");
            }catch (Exception e){
                e.printStackTrace();
                System.out.println("error--->>");
                throw e;
            }
            return ret;
        }
    }

      被代理对象targetObject通过参数传递进来,我们通targetObject.getClass().getClassLoader()获取ClassLoader对象,然后通过targetObject.getClass().getInterfaces()获取它实现的所有接口,然后将targetObject包装到实现了InvocationHandler接口的LogHandler对象中。通过newProxyInstance函数我们就获得了一个动态代理对象。

      客户端代码

    public class TestClientProxy {
        public static void main(String[] args) {
            LogHandler logHandler=new LogHandler();
            UserManager userManager=(UserManager)logHandler.newProxyInstance(new UserManagerImpl());
            userManager.addUser("A002","Not_Copy");
        }
    }

      可以看到,我们可以通过LogHandler代理不同类型的对象,如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。

    总结:

    其实所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用。

    纵观静态代理与动态代理,它们都能实现相同的功能,而我们看从静态代理到动态代理的这个过程,我们会发现其实动态代理只是对类做了进一步抽象和封装,使其复用性和易用性得到进一步提升而这不仅仅符合了面向对象的设计理念,其中还有AOP的身影,这也提供给我们对类抽象的一种参考。关于动态代理与AOP的关系,个人觉得AOP是一种思想,而动态代理是一种AOP思想的实现!

    参考原文:https://www.cnblogs.com/maohuidong/p/7992894.html

  • 相关阅读:
    加载效果(遮罩)
    [MVC] 自定义ActionSelector,根据参数选择Action
    【mysql】存储过程无参,传入参数,传出参数,动态sql,游标的简单例子
    【idea】启动报错Failed to create JVM:error code -6
    【idea】【maven】报错Unable to import maven project: See logs for details
    【springcloud】启动web模块报错spring-boot-maven-plugin:2.2.1.RELEASE:run (default-cli) on project da-web
    【mysql】删除服务
    【mysql】安装多个版本
    【mysql】安装报错Install/Remove of the Service Denied!
    【idea】设置console控制台显示内容大小
  • 原文地址:https://www.cnblogs.com/mww-NOTCOPY/p/11223946.html
Copyright © 2011-2022 走看看