动态代理

动态代理可以在不改动原代码的前提下,为其添加验权、事务控制、日志打印等功能。

例如,统计某方法运行所消耗的时间。通常做法如下:

// 记录此方法的开始时间(动态代理模式下,这部分不在此方法中实现)
// 处理业务
// 记录此方法的结束时间并打印(动态代理模式下,这部分不在此方法中实现)

在动态代理里,时间统计代码写在了代理模块里。将业务代码和时间统计代码完全解耦了。

动态代理有两种实现方式,一种是调用 JDK。另一种是使用 Cglib,两者的区别是Cglib是可以不需要接口。

就上面伪代码的例子。如果用代理模式,应该如何做呢?下面就是我用代码完成的一个示例及相关说明。

用调用JDK的方式实现动态代理,有这么几个类:
1、JDK 实现的 Proxy 类。此类的 newProxyInstance 方法用来生成代理对象。需要 InvocationHandler 的实例和目标代码类的字节码作为参数。
2、实现 InvocationHandler 接口的类,这个类通过实现InvocationHandler 接口的 invoke 方法,将「代理的代码」注入到「目标代码」中。而 createProxyIntance 方法用目标对象字节码和 handler(此类)以反射的方式生成代理对象。

public class RemindHandler implements InvocationHandler {
    
    /** 要注入的目标对象 */
    private Object target;
    
    public Object createProxyIntance(Object target) {
        this.target = target;
        Class clazz = this.target.getClass();// 目标对象的字节码
        
        // 通过目标对象字节码和 handler(此类)以反射的方式生成代理对象。
        Object object = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
        return object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("学习之前,提醒自己做好学习计划。");
        method.invoke(target);
        System.out.println("学习之后,提醒自己及时总结,做笔记,自测。");
        return null;
    }
}

3、目标代码类,对目标代码的具体事务进行实现。

public class StudyEnglish implements Study{

    @Override
    public void startReading() {
        System.out.println("阅读英语教材中...");
    }

}

4、目标代码类的接口类。

public interface Study {
    public void startReading();
}

下面是对以上代码进行测试的代码

public static void main(String args[]){
        StudyEnglish study = new StudyEnglish();
        RemindHandler handler = new RemindHandler();
        Study s = (Study) handler.createProxyIntance(study);
        s.startReading();
    }
}

一句话总结就是,动态代理通过被代理类的字节码和注入的代码(日志,权限,事务),以反射的方式生成一个代理的对象。

备注1:代理模式中,日志、验权、事务等代码是可复用的,不变的代码,在项目中大部分属于技术代码。而被代理的代码,大部分属于变化的业务代码。

备注2:动态代理需要注意的一个点
Spring事务处理时自我调用的解决方案及一些实现方式的风险

你可能感兴趣的:(动态代理)