六、动态代理模式
1、模式结构和结构图
-
1 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
1.2 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
1.3 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能
2、代码实战
需求背景:我们通过代理模式,来实现对某个方法的执行时间统计。
公共方法:
// 时间类
package proxy;
/**
* 时间工具类
*
* @author 刀刀和阳
*/
public class DateTimeUtils {
/**
* 开始计时
*
* @return 当前时间
*/
public static long start() {
System.out.println("开始计时!");
return System.currentTimeMillis();
}
/**
* 结束计时
*
* @return 当前时间
*/
public static long end() {
System.out.println("结束计时!");
return System.currentTimeMillis();
}
}
公共方法接口
package proxy;
/**
* 公共方法接口
*
* @author 刀刀和阳
*/
public interface Method {
void doSomething();
}
具体的方法实现类:
package proxy;
/**
* @author 刀刀和阳
*/
public class RealMethod implements Method {
@Override
public void doSomething() {
System.out.println("实际的方法调用!");
}
}
2.1 静态代理模式:
静态代理方法实现类:
package proxy.staticproxy;
import proxy.DateTimeUtils;
import proxy.Method;
/**
* 静态代理方法实现类
*
* @author 刀刀和阳
*/
public class ProxyMethod implements Method {
private Method method;
public ProxyMethod(Method method) {
this.method = method;
}
@Override
public void doSomething() {
long start = DateTimeUtils.start();
method.doSomething();
long end = DateTimeUtils.end();
System.out.println("总计耗时:" + (end - start) + "ms;");
}
}
测试:
package proxy.staticproxy;
import proxy.Method;
import proxy.RealMethod;
/**
* 实现某方法的调用时间统计
*
* @author 刀刀和阳
*/
public class Client {
public static void main(String[] args) {
Method realMethod = new RealMethod();
ProxyMethod proxyMethod = new ProxyMethod(realMethod);
proxyMethod.doSomething();
}
}
执行结果:
开始计时!
实际的方法调用!
结束计时!
总计耗时:0ms;
2.2 动态代理模式
代理方法实现类
package proxy.dynamicproxy;
import proxy.DateTimeUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 动态代理方法实现类
*
* @author 刀刀和阳
*/
public class ProxyMethod implements InvocationHandler {
private Object object;
public ProxyMethod(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = DateTimeUtils.start();
method.invoke(object, args);
long end = DateTimeUtils.end();
System.out.println("总计耗时:" + (end - start) + "ms;");
return object;
}
}
测试:
package proxy.dynamicproxy;
import proxy.Method;
import proxy.RealMethod;
import java.lang.reflect.Proxy;
/**
* 动态代理测试方法
*
* @author 刀刀和阳
*/
public class Client {
public static void main(String[] args) {
Method realMethod = new RealMethod();
Method proxyMethod = (Method) Proxy.newProxyInstance(
Method.class.getClassLoader(),
new Class[]{Method.class},
new ProxyMethod(realMethod));
proxyMethod.doSomething();
}
}
测试结果:
开始计时!
实际的方法调用!
结束计时!
总计耗时:0m=s;
3、总结
上述代码可以总结看出,静态代理和动态代理的区别在于:静态代理在代码运行前就已经确定需要代理的具体类是什么。而动态代理则是在运行时确定具体的代理对象。
其次,如果静态代理的公共接口发生了变化,则具体的实现接口和代理接口都要做相应的改变。违反了开闭原则。可以在上述案列中Method接口新增一个方法doOtherThing()方法即可感受到差别。
最后,上述案例中的动态代理通过JDK代理的方式,此外,还有通过cglib的方式来实现动态代理。我们在进阶中会具体讲述。
4、进阶
1、 jdk代理和cglib代理的区别
4.1 原理区别:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的通知。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的通知。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
4.2 性能区别:
1、CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
2、在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理。
4.3 各自局限:
1、JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。
唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
2、在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理。
4.3 各自局限:
1、JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。
2、cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。