动态代理推导
问题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动态代理适合代理没有接口的类。根据实际需求选择合适的代理方式即可。
后续的章节,我们将详细讲解上述几种动态代理的实现和使用。