• java中的静态代理、jdk动态代理以及CGLIB 动态代理


    代理模式是一种比较好理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能
    那以下文章主要谈三种代理模式,分别是静态代理,jdk的动态代理,cglib的动态代理

    本文相关代码已提交至gitee仓库 链接放在了文末 有需要自取。
    
    • 1

    1.静态代理

    简要描述

    静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)

    实现

    1.创建一个接口

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 20:37
     **/
    public interface SmsService {
    
        String send(String message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.创建一个实现类

    该类实现上面的接口,并重写里面的send方法。即我们的目标方法

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 20:38
     **/
    public class SmsServiceImpl implements SmsService{
    
    
        @Override
        public String send(String message) {
            System.out.println("目标方法:" + message);
            return message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.创建一个代理类

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 20:39
     **/
    public class SmsProxy implements SmsService{
    
        //定义一个smsService的字段
        private SmsService smsService;
    
        //在初始化该代理类的时候,带一个参数 smsService 给上面的字段赋值
        public SmsProxy(SmsService smsService){
            this.smsService = smsService;
        }
    
        //重写send方法,并对send方法做加强(前后新增输出)
        //这也是静态代理的麻烦之处 当我们代理的接口新增方法时,我们的代理类须同时修改
        @Override
        public String send(String message) {
            System.out.println("前置增强,可新增自己的业务代码");
    
            String targetMethod = smsService.send(message);
    
            System.out.println("后置增强,可新增自己的业务代码");
    
            return null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    4.创建主函数

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 20:42
     **/
    public class Main {
    
        public static void main(String[] args) {
            SmsServiceImpl smsService = new SmsServiceImpl();
            SmsProxy smsProxy = new SmsProxy(smsService);
            smsProxy.send("java");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.输出

    前置增强,可新增自己的业务代码
    目标方法:java
    后置增强,可新增自己的业务代码
    
    • 1
    • 2
    • 3

    总结

    静态代理很好的实现了在不修改原有代码的基础上进行业务拓展的功能,即我们上面的代码 实现的在目标方法执行前后 执行我们的业务代码

    但是静态代理是代理类在创建的时候,接口以及代理类就已经确定了,因此一个静态代理类只能代理一个类,并且当目标类的方法新增后,静态代理类也得随之修改,造成代码的冗余。

    2.jdk动态代理

    简要描述

    为了解决静态代理的不足(代码冗余以及代码写死的缺点),于是便有了动态代理

    动态代理是指代理类在程序运行时进行创建的代理方式。这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据Java代码动态生成的。(动态代理中的代理类并不要求在编译期就确定,而是可以在运行期动态生成,从而实现对目标对象的代理功能)
    但他有一个致命的缺点,只能代理实现了接口的类

    实现

    1.创建一个接口

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 20:37
     **/
    public interface SmsService {
    
        String send(String message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.创建一个实现类

    public class SmsServiceImpl implements SmsService {
        @Override
        public String send(String message) {
            System.out.println("目标方法:" + message);
            return message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.创建一个代理类

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 21:21
     **/
    //代理类
    public class DebugInvocationHandler{
            private SmsService smsService;
    
            //1.构造器 传入动态绑定的对象  接口的某一子类
            public DebugInvocationHandler(SmsService smsService){
                this.smsService = smsService;
            }
    
            /**
             * 2.得到一个代理的对象  通过这个方法 可以去调用方法
             * @return
             */
            public SmsService getProxy(){
                //a.凑第一个参数 类加载器
                ClassLoader classLoader = smsService.getClass().getClassLoader();
    
                //b.凑第二个参数 要代理的对象/被执行对象 的接口信息,底层是通过接口来完成调用
                Class<?>[] interfaces = smsService.getClass().getInterfaces();
    
                //c.凑第三个参数 InvocationHandler
                //使用了匿名内部类方法
                InvocationHandler invocationHandler = new InvocationHandler() {
    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("前置增强,可新增自己的业务代码");
                        // smsService 是 代理中的 真实对象
                        // args 是方法参数 比如我们调用的是send方法 传过来的参数是java 那这里就是一个数组 里面有一个值 java
                        Object result = method.invoke(smsService, args);
                        System.out.println("后置增强,可新增自己的业务代码 ");
                        return result;
                    }
                };
            //最主要的是这个方法  给这个方法凑参数
            //Proxy.newProxyInstance 返回的是Object类型 我们可以给他强转为SmsService 此时他已经是一个单例的对象
            //用该对象去调用某一个方法时,会走到上面的invoke方法
            SmsService proxy = (SmsService) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
            return proxy;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    5.输出

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 21:23
     **/
    public class Main {
        public static void main(String[] args) {
            //传入的时需要代理的实现类的对象
            DebugInvocationHandler debugInvocationHandler = new DebugInvocationHandler(new SmsServiceImpl());
            SmsService proxy = debugInvocationHandler.getProxy();
            proxy.send("java");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.CGLIB动态代理

    简要描述

    一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。 通过继承目标类,在子类中重写父类同名方法,实现功能修改(重写的方法不能是final)

    和jdk动态代理形成互补。在java源码中,实现了接口的类的代理用jdk动态代理,没有实现的用cglib代理

    实现

    1. 创建需要代理的类。此时没有接口

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 21:55
     **/
    public class AliSmsService {
        public String send(String message) {
            System.out.println("目标方法:" + message);
            return message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.引入cglib依赖

           <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>3.3.0</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.创建代理类

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 21:56
     **/
    public class CglibMethodInterceptor implements MethodInterceptor {
    
    
        /**
         * @param o           代理对象(增强的对象)
         * @param method      被拦截的方法(需要增强的方法)
         * @param args        方法入参
         * @param methodProxy 用于调用原始方法
         */
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            //调用方法之前,我们可以添加自己的操作
            System.out.println("前置增强,可新增自己的业务代码");
            Object object = methodProxy.invokeSuper(o, args);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("后置增强,可新增自己的业务代码");
            return object;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    4.创建获取代理对象的工厂类

    /**
     * @program: Blog-agency
     * @description:
     * @author: Daigl
     * @create: 2022-12-02 21:58
     **/
    public class CglibProxyFactory {
    
        public static Object getProxy(Class<?> clazz) {
            // 创建动态代理增强类
            Enhancer enhancer = new Enhancer();
            // 设置类加载器
            enhancer.setClassLoader(clazz.getClassLoader());
            // 设置被代理类
            enhancer.setSuperclass(clazz);
            // 设置我们自定定义的代理类(方法拦截器)
            enhancer.setCallback(new CglibMethodInterceptor());
            // 创建代理类
            return enhancer.create();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    5.创建主函数

    /**
     * @program: Blog-agency
     * @description: ]
     * @author: Daigl
     * @create: 2022-12-02 21:58
     **/
    public class Main {
        public static void main(String[] args) {
            
            Object object = CglibProxyFactory.getProxy(AliSmsService.class);
            AliSmsService aliSmsService = (AliSmsService) object;
            aliSmsService.send("java");
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    6.输出

    前置增强,可新增自己的业务代码
    目标方法:java
    后置增强,可新增自己的业务代码
    
    • 1
    • 2
    • 3

    谢谢大家看完。相关代码已经放在gitee:https://gitee.com/gaoludai/blog

  • 相关阅读:
    LVS+Keepalived群集
    Vue Router的使用
    Java错题归纳day8
    爱因斯坦:我的世界观(中英文对照)
    【Mybatis+springBoot】实现模糊查询
    <Linux系统复习>进程概念
    【SSM整合】SpringMVC + Spring + Mybatis整合详细教程
    Hive的相关操作
    Webrtc支持HEVC之FFMPEG支持HEVC编解码(一)
    HTTP与HTTPS
  • 原文地址:https://blog.csdn.net/qq_42978230/article/details/128154211