Java 代理(二) 动态代理概述篇

动态代理推导

问题1:静态代理接口/类膨胀太快

通过第一篇文章《Java代理(一)静态代理》,我们可以知道:要实现一个静态代理模式,需要三个类:接口类,实际实现类,代理接口类。那么问题来了,假如我们的计算器有加减乘除四个方法,按照每个方法一个接口,然后就是3*4=12类/接口。类和接口膨胀得太快,我们是否有办法解决这个问题呢?

方案1:相关的方法集中到同一个接口

把相关的方法放到同一个接口,这样确实可以减少接口和实现类的数量。让我们看看实现效果。 

public interface Calculator {

    Long add(Integer a, Integer b);
    
    Long subtract(Integer a, Integer b);
    
    Long multiply(Integer a, Integer b);
    
    Long divide(Integer a, Integer b);

}

具体业务实现类如下:
 

public class BusinessCalculator implements Calculator {

    @Override
    public Long add(Integer a, Integer b) {
        long result = 0;
        for(int i=0; i<100000000; i++) {
            result += i + a + b;
        }
        return result;
    }

    @Override
    public Long subtract(Integer a, Integer b) {
        long result = 0;
        for(int i=0; i<100000000; i++) {
            result += i + a - b;
        }
        return result;
    }

    @Override
    public Long multiply(Integer a, Integer b) {
        long result = 0;
        for(int i=0; i<100000000; i++) {
            result += i + a * b;
        }
        return result;
    }

    @Override
    public Long divide(Integer a, Integer b) {
        long result = 0;
        for(int i=0; i<100000000; i++) {
            result += i + a / b;
        }
        return result;
    }
}

这里我们仅仅是为了讲解代理,业务具体实现是mock的循环,仅仅是为了演示耗时。

代理实现类如下:

package org.derek;

public class BusinessCalculatorProxy implements Calculator {
    private Calculator calculator;

    public BusinessCalculatorProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public Long add(Integer a, Integer b) {
        long start = System.currentTimeMillis();

        Long result = calculator.add(a, b);

        long end = System.currentTimeMillis();
        System.out.println("cost time: " + (end - start) + "ms");
        return result;
    }

    @Override
    public Long subtract(Integer a, Integer b) {
        long start = System.currentTimeMillis();

        Long result = calculator.subtract(a, b);

        long end = System.currentTimeMillis();
        System.out.println("cost time: " + (end - start) + "ms");
        return result;
    }

    @Override
    public Long multiply(Integer a, Integer b) {
        long start = System.currentTimeMillis();

        Long result = calculator.multiply(a, b);

        long end = System.currentTimeMillis();
        System.out.println("cost time: " + (end - start) + "ms");
        return result;
    }

    @Override
    public Long divide(Integer a, Integer b) {
        long start = System.currentTimeMillis();

        Long result = calculator.divide(a, b);

        long end = System.currentTimeMillis();
        System.out.println("cost time: " + (end - start) + "ms");
        return result;
    }

    public static void main(String[] args) {
        Calculator calculator = new BusinessCalculator();
        Calculator proxy = new BusinessCalculatorProxy(calculator);
        proxy.add(9, 2);
        proxy.subtract(9, 2);
        proxy.multiply(9, 2);
        proxy.divide(9, 2);
    }
}

 经过把相关方法放到同一个接口内后,可以看到接口/类缩减到了3个,明显减少。

问题2:耗时统计分布在多个方法内?

通过查看BusinessCalculatorProxy中代码,我们看到统计方法耗时的代码,重复出现在每一个需要统计耗时的方法中。那么我们假如需要稍微修改下统计耗时代码,那么就需要改动4个方法里的代码。

有没有办法把统计耗时的代码逻辑封装在一个地方,方便后期维护呢?

方案2:Method反射调用

自己琢磨半天,想到一个方法,抛个砖。

public class TimeService {

    private final Map<String, Method> methodMap = new HashMap<>();

    public void initMethods(Class<?>[] interfaceClasses) {
        Arrays.stream(interfaceClasses).forEach(intrfaceClass -> {
            for (Method method: intrfaceClass.getDeclaredMethods()) {
                methodMap.put(method.getName(), method);
            }
        });
    }

    public Object invoke(Object realSubject, String methodName, Object[] args) {
        Object result = null;
        try {
            long start = System.currentTimeMillis();

            Method method = methodMap.get(methodName);
            result = method.invoke(realSubject, args);

            long end = System.currentTimeMillis();
            System.out.println("method:" + method.getName() + " cost:" + (end - start) + "ms");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static void main(String[] args) {
        Calculator calculator = new BusinessCalculator();
        TimeService timeService = new TimeService();

        timeService.initMethods(calculator.getClass().getInterfaces());
        timeService.invoke(calculator, "add", new Object[]{9, 2});
        timeService.invoke(calculator, "subtract", new Object[]{9, 2});
        timeService.invoke(calculator, "multiply", new Object[]{9, 2});
        timeService.invoke(calculator, "divide", new Object[]{9, 2});
    }
}

通过上面使用Method反射传入,把方法做成参数,这样就可以把重复代码封装在一个方法内了。解决了代码重复的问题。可是有点瑕疵,改变了方法调用名,不能做到对接口方法的直接调用。

那么有没有可以直接调用接口方法的代理呢?答案是肯定的,那就是动态代理。

1. 什么是动态代理?

动态代理是指在程序运行期间动态创建代理类和代理对象的方式。与静态代理不同,动态代理不需要在编译时定义代理类,而是通过反射机制在运行时生成代理类。
 

2. 动态代理的特点

灵活性高:代理类在运行时生成,可以根据需求动态调整。
通用性强:可以为任意接口生成代理对象,无需手动编写代理类。
依赖于反射:通过Java的反射机制实现。
 

3. 动态代理的实现方式

在Java中,动态代理主要有下面两种实现方式:
(1)基于JDK的动态代理
适用场景:只能代理实现了接口的类。
核心类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler。
工作原理:
使用Proxy.newProxyInstance方法创建代理对象。
实现InvocationHandler接口,重写invoke方法,在其中定义增强逻辑。

(2)基于CGLIB/ByteBuddy的动态代理
适用场景:可以代理没有实现接口的类。
核心库:CGLIB(Code Generation Library)/ ByteBuddy。
工作原理:
通过继承目标类,生成子类代理对象。
在子类中拦截目标方法的调用,添加增强逻辑。

4. 动态代理的应用场景

AOP(面向切面编程):如日志记录、性能监控、事务管理等。
远程调用:如RMI(Remote Method Invocation)。
权限校验:在方法执行前后进行权限检查。
缓存:在方法调用前检查缓存,减少重复计算。
 

5. 总结

动态代理的核心思想是“解耦”,它允许我们在不修改目标对象的情况下,为其添加额外的功能。JDK动态代理适合代理接口,而CGLIB动态代理适合代理没有接口的类。根据实际需求选择合适的代理方式即可。

后续的章节,我们将详细讲解上述几种动态代理的实现和使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值