Java 动态代理-Jdk动态代理

版权声明:转载请注明出处! https://blog.csdn.net/zongf0504/article/details/86629034

java 在java.lang.reflect 包下提供了一个Proxy 和 InvocationHandler 接口来支持生产Jdk 动态代理类(不常用)或动态代理对象. 在普通的企业开发中, 通常是不会涉及到动态代理开发的, 但是若是开发框架或做底层封装时就需要涉及到动态代理了, 如spring 的AOP 就是使用动态代理来实现的. 若想使用Jdk 动态代理, 则被代理类必须实现了接口, 未实现任何接口的类不能使用jdk 动态代理方式生产动态代理.

1. 创建动态代理

jdk 动态代理的核心是InvocationHandler, 因为调用被动态代理对象的方法实则调用的是InvocationHandler 的invoke 方法. Proxy类 提供了两个静态方法分别用于生成动态代理类和动态代理对象:

方法签名 方法描述
tatic Class<?> getProxyClass(ClassLoader loader, Class<?>…interfaces) 创建一个动态代理类所对应的Class 对象, 该代理类将实现interfaces 指定的所有接口. 在创建实例时需要传入InvocationHandler 对象, 所以这种方式并不常用
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 创建一个动态代理对象, 该代理对象实现了interfaces 指定的所有接口, 当执行代理对象的每一个方法时, 都会被替换成 invocationHandler 的invoke 方法

2. jdk 动态代理开发步骤

  1. 自定义动态代理处理器InvokeHandler: 当调用被代理对象的任何方法时, 都会替换成调用此类的invoke 方法. 在invoke 方法中可以控制被调用方法前后的操作
  2. 创建动态代理工厂: 通常会为动态代理定义一个代理工厂, 方法生成动态代理
  3. 转换代理对象为动态代理对象接口类型: 将生成的动态代理对象类型强转为被代理对象接口的类型, 不能转换为实现类的类型, 这样才能调用被代理对象的方法.

3. 动态代理测试

笔者创建一计算器类, 并提供两个方法, 目标不侵入计算器类代码, 实现执行每个计算方法前后都输出日志.

3.1 定义被代理对象, 需要实现接口

普通业务组件类, 只需要实现接口即可.

public interface ICalculator <T> {

    public T plus(T t1, T t2);

    public T minus(T t1, T t2);

}
public class IntCalculator implements ICalculator<Integer>{

    private void sleep() {

        int seconds = new Random().nextInt(10) * 1000;

        try {
            Thread.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Integer plus(Integer t1, Integer t2) {
        // 模拟方法执行耗时
        sleep();

        int result = t1 + t2;
        System.out.printf("%d + %d = %d\n", t1, t2, result);
        return result;
    }

    @Override
    public Integer minus(Integer t1, Integer t2) {
        // 模拟方法执行耗时
        sleep();

        int result = t1 - t2;
        System.out.printf("%d - %d = %d\n", t1, t2, result);
        return result;
    }

}

3.2 定义日志类

目标方法执行前后输出日志.

public class CalculatorLogUtil {

    // 调用方法前打印日志
    public static void before(String method, Object... args){
        System.out.printf("\n[%s] method:%s, params:%s --start\n", LocalDateTime.now(), method, Arrays.asList(args));
    }

    // 调用方法后打印日志
    public static void after(String method, Object... args){
        System.out.printf("[%s] method:%s, params:%s --ended\n", LocalDateTime.now(), method, Arrays.asList(args));
    }
}

3.3 自定义动态代理处理器

动态代理的核心, 可以在invoke 中对调用目标方法前后做处理工作

public class CalculatorHandler implements InvocationHandler {

    // 被代理对象
    private Object target;

    public CalculatorHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 调用方法前处理
        CalculatorLogUtil.before(method.getName(), args);

        // 调用方法
        Object result = method.invoke(target, args);

        // 调用方法后处理
        CalculatorLogUtil.after(method.getName(), args);

        return result;
    }

}

3.4 自定义动态代理工厂

动态代理工厂用于生产动态代理对象

public class CalculatorProxyFactory {

    /**
     * 获取目标对象代理
     * @param target 目标对象
     * @return 代理对象
     */
    public static Object getProxy(Object target) {

        // 创建代理处理器
        CalculatorHandler calculatorProxy = new CalculatorHandler(target);

        // 创建jdk 代理对象
        Object proxyObject = Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), calculatorProxy);

        return proxyObject;
    }
}

3.5 测试

需要注意的是, 必须将动态代理对象转换为被代理对象的接口类型, 不能转换为实现类类型IntCalculator, 否则会抛出类型转换异常.

@Test
public void test_JdkProxy(){

    // 创建被代理目标对象
    ICalculator<Integer> calculator = new IntCalculator();

    // 通过动态代理工厂获取动态代理
    Object proxy = CalculatorProxyFactory.getProxy(calculator);

    // 将动态代理强制转换为被代理目标对象接口类型, 否则Object 类型对象没有目标对象的相关方法
    ICalculator<Integer> calculatorProxy = (ICalculator) proxy;

    // 通过动态代理对象执行方法, 其实执行的是CalculatorHandler的invoke方法
    calculatorProxy.plus(5, 3);
    calculatorProxy.minus(5, 3);

}

3.6 测试输出

[2019-01-24T15:59:11.342] method:plus, params:[5, 3] --start
5 + 3 = 8
[2019-01-24T15:59:18.344] method:plus, params:[5, 3] --ended

[2019-01-24T15:59:18.345] method:minus, params:[5, 3] --start
5 - 3 = 2
[2019-01-24T15:59:24.348] method:minus, params:[5, 3] --ended

总结:

  • 动态代理的核心就是InvocationHandler中的invoke方法, 所以当看到动态代理时, 直接找其对应的InvocationHandler即可
  • Jdk 动态代理的方法必须实现了接口, 且是重写接口的方法才行
  • AOP(切面编程)得以实现就是依赖于动态代理
  • Spring 的AOP 会根据目标类是否实现了接口自动选择使用Jdk 动态代理还是Cglib动态代理

猜你喜欢

转载自blog.csdn.net/zongf0504/article/details/86629034