【设计模式】设计模式之代理模式| 8月更文挑战

130 阅读5分钟

一、定义

Provide a surrogate or placeholder for another object to control access to it. (为其他对象提

一种代理以控制对这个对象的访问。)

代理模式也叫做委托模式 ,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。

二、UML示意图

代理模式.png

  • Subject抽象主题角色:可以是抽象类,也可以是接口,是一个普通业务类型的抽象定义。
  • RealSubject具体主题角色:也叫做被委托角色、被代理角色。是业务逻辑的具体执行中。
  • Proxy代理主题角色:也叫委托类、代理类。负责把所有抽象主题类定义的方法通过访问控制后,委托给真实主题角色实现

三、代码实现

  • 抽象主题类,对于普通业务类型的抽象,可以根据业务类型来声明具体的方法

    /**
     * 代理类抽象接口
     */
    public interface Subject {
    
        void request();
    
    }
    
  • 具体主题类,实现抽象接口中定义的方法,具体实现某一类业务

    /**
     * 真实的被代理类
     * 实现抽象的代理接口,实现具体的操作
     */
    public class RealSubject implements Subject {
    
        @Override
        public void request() {
            System.out.println("RealSubject is doing request");
        }
    }
    
    
  • 代理主题类,负责将请求进行访问控制后,委托给具体主题类进行执行

    /**
     * 代理类
     */
    public class Proxy implements Subject {
    
        /**
         * 持有被代理对象的引用
         */
        private Subject subject;
    
        /**
         * 通过构造注入进行初始化
         */
        public Proxy(Subject subject) {
            this.subject = subject;
        }
    
        /**
         * 在调用被代理对象的方法前后实现访问控制
         */
        @Override
        public void request() {
            //前置增强
            before();
            //调用代理对象的方法
            subject.request();
            //后置增强
            after();
        }
    
        private void before() {
            System.out.println("proxy class can doing something here before access real subject");
        }
    
        private void after() {
            System.out.println("proxy class can doing something here after access real subject");
        }
    }
    
  • Client类,模拟访问请求,以及请求结果输出

    public class Client {
        public static void main(String[] args) {
            Subject subject = new RealSubject();
            Proxy proxy = new Proxy(subject);
            proxy.request();
        }
    }
    
    
    proxy class can doing something here before access real subject
    RealSubject is doing request
    proxy class can doing something here after access real subject
    

四、动态代理

以上便是代理模式的静态实现,从结果上来说,他达到了我们想要对某个类中的方法进行访问控制的需求。但是,当我们的需求有所变更的时候,依然想使用这个代理类来完成需求,例如有另一种类型的业务Subject2需要做类似的访问控制,那么我们就需要去调整代码,让代理类实现Subject2接口,并实现接口中的所有方法,那这样的话就违反了设计原则中的开闭原则,并且设计十分不优雅。

此时就需要用到动态代理。在动态代理中,代理主题类不需要去实现抽象主题类,便可以实现对目标类的代理。

动态代理是基于JDK中的API,java.lang.reflect.Proxy类,动态的在内存中构建代理对象,从而实现代理。

JDK动态代理

  • UML示意图

jdk代理.png

  • 代理主题类,实现InvocationHandler接口,在创建代理对象时,将当前对象传入代理对象中

    /**
     * 代理类
     * 实现InvocationHandler接口,在创建代理对象时,将当前对象传入代理对象中
     */
    public class ProxyFactory implements InvocationHandler {
    
        /**
         * 持有被代理对象的引用
         */
        private Object object;
    
        /**
         * 通过构造注入进行初始化
         */
        public ProxyFactory(Object object) {
            this.object = object;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            before();
            Object invoke = method.invoke(object, args);
            after();
            return invoke;
        }
    
        public Object getProxyInstance() {
            return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
        }
    
        private void before() {
            System.out.println("proxy class can doing something here before access real subject");
        }
    
        private void after() {
            System.out.println("proxy class can doing something here after access real subject");
        }
    }
    
  • Client类,模拟访问请求,正常输出

    public class Client {
        public static void main(String[] args) {
            Subject subject = new RealSubject();
            Subject proxy = (Subject) new ProxyFactory(subject).getProxyInstance();
            proxy.request();
        }
    }
    

CGLIB代理

CGLIB底层是通过使用字节码处理框架ASM来转换字节码并生成新的代理类。

  • UML示意图

cglib.png

  • 导包

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
    
  • 被代理主题类,不需要实现接口

    /**
     * 真实的被代理类
     * 实现抽象的代理接口,实现具体的操作
     */
    public class RealSubject {
    
        public void request() {
            System.out.println("RealSubject is doing request");
        }
    }
    
  • 代理主题类,实现MethodInterceptor接口,在创建代理对象时,将当前对象传入代理对象中

    /**
     * 代理类
     * 实现MethodInterceptor接口,在创建代理对象时,将当前对象传入代理对象中
     */
    public class ProxyFactory implements MethodInterceptor {
    
        /**
         * 持有被代理对象的引用
         */
        private Object object;
    
        /**
         * 通过构造注入进行初始化
         */
        public ProxyFactory(Object object) {
            this.object = object;
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            before();
            Object invoke = method.invoke(object, objects);
            after();
            return invoke;
        }
    
        /**
         * 返回一个代理对象,是object对象的代理对象
         */
        public Object getProxyInstance() {
            //1.创建工具类
            Enhancer enhancer = new Enhancer();
            //2.设置父类
            enhancer.setSuperclass(object.getClass());
            //3.设置回调函数
            enhancer.setCallback(this);
            //4.创建子类对象,即代理对象
            return enhancer.create();
        }
    
        private void before() {
            System.out.println("proxy class can doing something here before access real subject");
        }
    
        private void after() {
            System.out.println("proxy class can doing something here after access real subject");
        }
    }
    
  • Client类,模拟访问请求,正常输出

    public class Client {
        public static void main(String[] args) {
            RealSubject subject = new RealSubject();
            RealSubject proxy = (RealSubject) new ProxyFactory(subject).getProxyInstance();
            proxy.request();
        }
    }
    
  • 注意:

    1. 代理的类不能为final,否则会报java.lang.IllegalArgumentException异常;
    2. 目标对象的方法如果为final/static,那么就不会被拦截进行访问控制。

五、小结

  1. 静态代理是代理模式的灵魂,能够充分演绎代理模式的含义,但在实际运用中可能存在违反开闭原则的问题;
  2. 在动态代理选择的时候,如果需要代理的目标对象需要实现接口,则用JDK代理;如果不需要实现接口,用CGLIB代理。

六、参考

  • 《设计模式之禅》第2版