• 结构型设计模式之代理模式


    代理模式

    代理模式(Proxy Pattern)属于结构型模式。

    它是指为其他对象提供一种代理以控制对这个对象的访问。

    在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

    在代理模式中,创建具有现有对象的对象,以便向外界提供功能接口。

    想在访问一个类时做一些控制的时候就可以是用代理模式。

    分类

    代理模式属于结构型模式,分为静态代理和动态代理。

    1.静态代理:静态定义代理类

    静态代理需要自己生成代理类

    2.动态代理:动态生成代理类

    动态代理不用亲自去实现,通常使用现成的API即可。目前普遍使用的是JDK自带的代理与CGLIB提供的类库。

    主要角色

    代理模式一般包含三种角色:

    1.抽象主题角色(Subject)

    抽象主题类的主要职责是声明真实主题与代理的共同接口方法,该类可以是接口也可以是抽象类

    2.真实主题角色(RealSubject)

    该类也被称为被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的罗辑业务对象

    3.代理主题角色(Proxy)

    代理主题也被称为代理类,其内部特有RealSubject的引用,因此具备完全的对RealSubject的代理权。

    客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增加一些处理代码。可以理解为代码增强,实际上就是在原代码罗辑前后增加一些代码逻辑,而使调用者无感知。

    作用

    1.保护目标对象,将代理对象与真实被调用目标对象分离
    
    2.增强目标对象
    
    3.降低系统耦合性,提升扩展性
    
    • 1
    • 2
    • 3
    • 4
    • 5

    静态代理与动态代理的区别

    1.静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类需要同步增加,违背开闭原则。

    2.动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原测。

    3.若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无须修改代理类的代码。

    静态代理的基本使用

    静态代理需要自己生成代理类

    创建抽象主题

    public interface ISubject {
        /**
         * 买票
         */
        void buyTickets();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建真实主题

    public class RealSubject implements ISubject {
        public void buyTickets() {
            System.out.println("进行买票操作");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建代理主题

    public class Proxy implements ISubject {
        private ISubject subject;
        public Proxy(ISubject subject) {
            this.subject = subject;
        }
        public void buyTickets() {
            before();
            subject.buyTickets();
            after();
        }
        public void before() {
            System.out.println("买票前的操作");
        }
        public void after() {
            System.out.println("买票后的操作");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    客户端调用

        public static void main(String[] args) {
            Proxy proxy = new Proxy(new RealSubject());
            proxy.buyTickets();
        }
    
    • 1
    • 2
    • 3
    • 4
    买票前的操作
    进行买票操作
    买票后的操作
    
    • 1
    • 2
    • 3

    JDK动态代理的基本使用

    创建抽象主题

    public interface IUser {
        /**
         * 购物
         */
        void shopping();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建真实主题

    public class User implements IUser{
        @Override
        public void shopping() {
            System.out.println("user shopping....");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建代理主题

    public class JDKProxy implements InvocationHandler {
    
        private Object tarjet;
    
        public JDKProxy(Object tarjet) {
            this.tarjet = tarjet;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("代理user,执行shopping()开始...");
            Object oj = method.invoke(tarjet, args);
            System.out.println("代理user,执行shopping()结束...");
            return oj;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    客户端调用

        public static void main(String[] args) {
            User user = new User();
            JDKProxy jdkProxy = new JDKProxy(user);
            IUser proxyInstance = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass().getInterfaces(), jdkProxy);
            proxyInstance.shopping();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    代理user,执行shopping()开始...
    user shopping....
    代理user,执行shopping()结束...
    
    • 1
    • 2
    • 3

    小优化

    在调用时候,传入了一推参数,可进一步优化

    public class JDKProxy implements InvocationHandler {
    
        private Object tarjet;
        
        public Object JDKProxy(Object target){
            this.tarjet = target;
            Class<?> clazz =  target.getClass();
            return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("代理user,执行shopping()开始...");
            Object oj = method.invoke(tarjet, args);
            System.out.println("代理user,执行shopping()结束...");
            return oj;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
        public static void main(String[] args) {
            JDKProxy jdkProxy = new JDKProxy();
            IUser user= (IUser)jdkProxy.JDKProxy(new User());
            user.shopping();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    CGLIB动态代理的基本使用

    CGLIB动态代理也不需要生成代理类,实现MethodInterceptor 就可以了。

    注意:CGLib不能代理final的方法

    创建抽象主题

    注意:CGLb代理的目标对象不需要实现任何接口,就可以通过动态继承目标对象实现动态代理。所以此处可以不用创建接口。直接使用真实主题。

    public interface IUser {
        public void shopping();
    }
    
    • 1
    • 2
    • 3

    创建真实主题

    public class User implements IUser{
        @Override
        public void shopping() {
            System.out.println("user shopping....");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    直接使用真实主题。

    public class User {
        public void shopping() {
            System.out.println("user shopping....");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建代理主题

    public class CglibProxy implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("代理user,执行shopping()开始...");
            Object invokeSuper = methodProxy.invokeSuper(o, objects);
            System.out.println("代理user,执行shopping()结束...");
            return invokeSuper;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    客户端调用

        public static void main(String[] args) {
            CglibProxy cglibProxy = new CglibProxy();
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(User.class);
            enhancer.setCallback(cglibProxy);
            IUser iUser = (IUser) enhancer.create();
            iUser.shopping();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    小优化

    在客户端调用时,稍显复杂,可进一步优化

    public class CglibProxy implements MethodInterceptor {
    
        public Object getInstance(Class<?> clazz) throws Exception{
            //相当于Proxy,代理工具类
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("代理user,执行shopping()开始...");
            Object invokeSuper = methodProxy.invokeSuper(o, objects);
            System.out.println("代理user,执行shopping()结束...");
            return invokeSuper;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
        public static void main(String[] args) throws Exception {
            IUser user = (IUser) new CglibProxy().getInstance(User.class);
            user.shopping();
        }
    
    • 1
    • 2
    • 3
    • 4

    CGLIB与JDK动态代理区别

    1.执行条件

    JDK动态代理实现了被代理对象的接口。CGLb代理的目标对象不需要实现任何接口,它是通过动态继承目标对象实现动态代理。

    2.实现机制

    JDK动态代理调用代理方法是由Java内部的反射机制来实现的,需要读取接口信息。CGLib动态代理是通过FastClass机制来实现的,需要覆盖父类方法。

    3.性能

    首先都在运行期生成字节码。

    JDK动态代理的代理逻辑简单,直接写Class字节码,使用反射机制在生成类的过程中比较高效。

    CGLib代理实现更复杂,使用ASM框架写Class字节码,但是asm在生成类之后的相关执行过程中比较高效。但是可以通过将asm生成的类进行缓存,解决asm生成类过程低效问题。

    一句话:CGLib生成代理类比JDK动态代理效率低,但是执行效率比JDK动态代理高。

  • 相关阅读:
    Kafka中主题和分区的概念
    深入详解高性能消息队列中间件 RabbitMQ
    如何利用AWS CloudFront 自定义设置SSL
    机器学习:基于梯度下降算法的线性拟合实现和原理解析
    linux安装Promethus普罗米修斯监控
    根据前序与中序遍历结果构造二叉树
    2022年陕西省工程师职称评审程序在这里,还怕上报不了职称吗?
    Linux UWB Stack实现——FiRa会话状态机
    【Linux】基本的指令(三)
    小学生用哪种护眼灯好?2022年无蓝光的护眼灯推荐
  • 原文地址:https://blog.csdn.net/qq_38628046/article/details/126090087