• 代理模式


    一、代理模式

    (1)、代理模式简介

    代理模式(Proxy Pattern)是指建立某一个对象的代理对象,并且由代理对象控制对原对象的引用。可以在目标对象的基础上,增强额外的功能操作,即扩展目标对象的功能.
    这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。
    例如,我们不能直接访问对象A,则可以建立对象的A代理A Proxy。这样,就可以通过访问A Proxy来间接地使用对象A的功能。A Proxy就像 A的对外联络人一般。如下图所示给出了代理模式类图。

    (2)、代理模式使用场景

    1、隔离功能:

    通过建立一个目标对象的代理对象,可以防止外部对目标对象的直接访问,这样就使得目标对象与外部隔离。我们可以在代理对象中增加身份验证、权限验证等功能,从而实现对目标对象的安全防护。

    2、扩展功能:

    对一个目标对象建立代理对象后,可以在代理对象中增加更多的扩展功能。例如,可以在代理对象中增加日志记录功能,这样对目标对象的访问都会被代理对象计入日志。

    3、直接替换:

    对一个目标对象建立代理对象后,可以直接使用代理对象完全替换目标对象,由代理对象来实现全部的功能。例如,MyBatis 中数据库操作只是一个抽象方法,但实际运行中会建立代理对象来完成数据库的读写操作。

    二、Java实现代理模式

    (1)、静态代理

    静态代理就是代理模式最简单的实现。所谓“静态”,是指被代理对象和代理对象在程序中是确定的,不会在程序运行过程中发生变化。
    静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
    如下所示,先定义一个接口,在接口中增加一个抽象方法。

    public interface UserInterface {
        void sayHello();
    }
    

    实现以上接口的被代理对象,如下代码所示

    public interface User implements UserInterface {
        @Override
        public void sayHello(){
            System.out.println("hello,world");
        }
    }
    

    下面为被代理类增加一个代理类。代理类中调用了被代理类的 sayHello方法,并在此方法的基础上增加了新的功能:在打招呼前后增加开场语句和结束语句。代码如下所示

    public class UserProxy implements UserInterface{
    	private UserInterface target;
        
        public UserProxy(UserInterface target){
            this.target=target
        }
        
        @Override
        public void sayHello(){
            System.out.println("sayHello前处理");
            target.sayHello();
            System.out.println("sayHello后处理");
        }
    }
    

    通过以下代码调用代理对象中的方法

        public static void main(String[] args) {
    
            //被代理对象
            User user =new User();
    
            //生成代理对象,顺便明确被代理对象是那个
            UserProxy userProxy = new UserProxy(user);
            
            //触发代理方法
            UserProxy.sayHello();
        }
    

    以上例子的类图如下所示:

    静态代理也有一些局限性,最明显的就是代理对象和被代理对象是在程序中写死的,一个代理对象就需要编写一个被代理对象的类,当代理对象很多的时候,被代理对象也会越来越多,显然不够灵活。

    (2)、动态代理

    动态代理的代理对象,不需要实现接口。代理对象的生成有用两种方式。一种是使用原生的JDK的API,一种是使用Cglib方式生成。

    1.JDK动态代理

    用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型),JDK中生成代理对象的API在java.lang.reflect.Proxy包中。Proxy有一个静态方法

    static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
    

    该方法需要接口三个参数依次为:
    ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
    Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
    InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
    代码示例如下:
    接口类(UserInterface.java)、目标对象类(User)依然沿用上述静态代理的代码,不做任何修改。在这个基础上增加代理工厂类(ProxyFactory.java),在工厂类中生成代理类。代码如下

    /**
     * 创建动态代理对象
     * 动态代理不需要实现接口,但是需要指定接口类型
     */
    public class ProxyFactory{
        //维护一个目标对象
        private Object target;
    
        public ProxyFactory(Object target){
            this.target=target;
        }
    
       //生成代理对象
        public Object getProxyInstance(){
            return Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
            //匿名内部类
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("sayHello前处理");
                            //执行目标对象方法
                            Object returnValue = method.invoke(target, args);
                            System.out.println("sayHello后处理");
                            return returnValue;
                        }
                    }
            );
        }
    
    }
    

    对JDK生成的动态代理进行测试

    public static void main(String[] args) {
            // 目标对象
            User target = new User();
    
            // 给目标对象,创建代理对象
            UserInterface proxy = (User) new ProxyFactory(target).getProxyInstance();
            // class $Proxy0   内存中动态生成的代理对象
            System.out.println(proxy.getClass());
            // 执行方法 【代理对象的方法】
            proxy.sayHello();
        }
    
    2.Cglib动态代理

    Cglib子类代理实现步骤:
    1.需要引入cglib的jar包

    <dependency>
    			<groupId>cglib</groupId>
    			<artifactId>cglib</artifactId>
    			<version>3.3.0</version>
    		</dependency>
    

    2.引入功能包后,就可以在内存中动态构建子类
    3.代理的类不能为final,否则报错
    4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
    具体代码如下所示

    /**
     * 目标对象,没有实现任何接口
     */
    public class User {
        public void sayHello() {
            System.out.println("sayHello");
        }
    }
    

    Cglib代理工厂

    /**
     * Cglib子类代理工厂
     * 在内存中创建目标类的子类
     */
    public class ProxyFactory implements MethodInterceptor{
        //维护目标对象
        private Object target;
        public ProxyFactory(Object target) {
            this.target = target;
        }
    
        //给目标对象创建一个代理对象
        public Object getProxyInstance(){
            //1.工具类
            Enhancer en = new Enhancer();
            //2.设置父类
            en.setSuperclass(target.getClass());
            //3.设置回调函数
            en.setCallback(this);
            //4.创建子类(代理对象)
            return en.create();
    
        }
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("sayHello之前处理");
            //执行目标对象的方法
            Object returnValue = method.invoke(target, args);
            System.out.println("sayHello之后处理");
            return returnValue;
        }
    }
    

    测试类

    public static void main(String[] args) {
            //目标对象
            User target = new User();
            //代理对象
            User proxy = (User)new ProxyFactory(target).getProxyInstance();
            //执行代理对象的方法
            proxy.sayHello();
        }
    
    4.总结

    使用JDK原生的API生成代理对象,要求被代理对象必须实现接口,否则不能用动态代理。
    通过DEBUG发现生成的代理类父类是Proxy类,因为Java是单继承的,而代理类又必须继承自Proxy类,所以通过jdk代理的类必须实现接口.
    CGLIB 可以代理未实现任何接口的类。CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
    就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。

  • 相关阅读:
    泛型简介
    单元测试(junit使用)
    枚举简介
    面试题:二叉树的镜像
    面试题:和为S的连续正数列
    面试题:丑数
    面试题:合并两个排序的链表
    面试题:数值的整数次方
    面试题:矩形覆盖
    面试题:数组中的逆序对
  • 原文地址:https://www.cnblogs.com/quartz/p/15556496.html
Copyright © 2020-2023  润新知