• 【重铸Java根基】理解Java代理模式机制


    最近带应届新员工,教然后知不足,发现自己把很多基础知识已经还给了大学老师,因此开贴,温故而知新!

    从最基础的Java知识开始由浅入深,在某个知识点中遇到有疑惑的点会额外多写几句或者单独开帖子展开。

    什么是代理模式

    简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。

    代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。

    先来看一下最基本的静态代理

    1.先创建一个接口,定义方法,并创建一个被代理类实现这个接口和方法

    2.再创建一个代理类同样实现这个接口和方法,并且将被代理类注入到代理类中

    3.在代理类中调用被代理类的同名方法,这样就可以在调用前后做更多的业务操作

    静态代理中,我们对目标对象的每个方法的增强都是手动完成的(后面会具体演示代码),非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)。 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。

    下面说一下动态代理,动态代理在日常开发的业务代码中其实也很少见,但是在框架型项目中几乎是必不可少的,因此理解动态代理的原理有利于理解框架。

    JDK动态代理

    JDK动态代理基于接口、JDK提供的Proxy类和InvocationHandler类

    调用Proxy类的newProxyInstance方法来创建一个代理对象

    这个方法一共有 3 个参数:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    loader : 目标类的类加载器。
    interfaces : 目标类实现的一些接口;
    h : 实现了 InvocationHandler 接口的对象;

    其中h需要自己定义一个对象来实现InvocationHandler 接口,并重写里面的invoke方法,invoke方法内部就是具体的业务逻辑

    public interface InvocationHandler {
    
        /**
         * 当你使用代理对象调用方法的时候实际会调用到这个方法
         */
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    invoke() 方法有下面三个参数:

    proxy :动态生成的代理类,由jdk在动态代理的编译过程中产生
    method : 与代理类对象调用的方法相对应
    args : 当前 method 方法的参数
    也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

    JDK 动态代理类使用步骤

    定义一个接口及其实现类;
    自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
    通过 Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) 方法创建代理对象;

    那么为什么JDK动态代理为什么必须针对接口

    查看jdk的动态代理源码发现:
    动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class文件,并加载class文件运行的过程,通过反编译被生成的$Proxy0.class文件发现:
    class类定义为:
    public final class $Proxy0 extends Proxy implements Interface {
    public $Proxy0(InvocationHandler paramInvocationHandler) {
    super(paramInvocationHandler);
    }

    该方法为被代理接口的业务方法,代理类都会自动生成相应的方法,里面去执行invocationHandler 的invoke方法。

    由于java的单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口。

    JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类
    为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。

    CGLIB 动态代理机制

    很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
    不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。

    
      cglib
      cglib
      3.3.0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。

    你需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。

    public interface MethodInterceptor
    extends Callback{
        // 拦截被代理类中的方法
        public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    obj : 动态生成的代理对象
    method : 被拦截的方法(需要增强的方法)
    args : 方法入参
    proxy : 用于调用原始方法

    你可以通过 Enhancer类来动态获取代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

    CGLIB 动态代理类使用步骤
    1.定义一个类;
    2.自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;

    public class DebugMethodInterceptor 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("before method " + method.getName());
            Object object = methodProxy.invokeSuper(o, args);
            //调用方法之后,我们同样可以添加自己的操作
            System.out.println("after method " + method.getName());
            return object;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.通过 Enhancer 类的 create()创建代理类;

    import net.sf.cglib.proxy.Enhancer;
    
    public class CglibProxyFactory {
    
        public static Object getProxy(Class clazz) {
            // 创建动态代理增强类
            Enhancer enhancer = new Enhancer();
            // 设置类加载器
            enhancer.setClassLoader(clazz.getClassLoader());
            // 设置被代理类
            enhancer.setSuperclass(clazz);
            // 设置方法拦截器
            enhancer.setCallback(new DebugMethodInterceptor());
            // 创建代理类
            return enhancer.create();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.调用

    myService aliSmsService = (myService ) CglibProxyFactory.getProxy(myService .class);
    aliSmsService.doSomeThing("java");
    
    • 1
    • 2
  • 相关阅读:
    C# 抽象类和接口
    Hive默认分割符、存储格式与数据压缩
    HTTP协议中的Cookie 和 Session
    LeetCode分支-搜索插入位置
    vscode下载安装和配置使用
    Java入门篇 之 数据类型(简单介绍)
    数据库Redis(一):基础知识
    【读书笔记->数据分析】03 BDA数据可视化
    Redis实现短信登入功能(一)传统的Session登入
    我的创作纪念日
  • 原文地址:https://blog.csdn.net/playadota/article/details/126177080