动态代理
动态代理的特点
字节码随用随创建,随用随加载。
它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
装饰者模式就是静态代理的一种体现。
动态代理的两种方式
一、基于接口的动态代理
提供者:JDK 官方的 Proxy 类。
要求:被代理类最少实现一个接口。
在此模拟一个经纪人和演员之间的关系
所谓代理,就是代替别人去做事情,比如经纪人就要代演员去谈合同,去处理事务
上图为一个接口,接口中的两个方法,代表了两种表演的方式,即常规的和危险的
假设上图为一个演员,经纪人和他即为代理和被代理的关系
基于jdk实现的动态代理,被代理类必须至少实现一个接口,也叫基于接口的动态代理
讲的通俗一点,代理模式就是将增强方法的方法体和原有的方法体相结合,在内存中生成一个结合后的字节码文件,但是这个文件看不见摸不着,并不是物理存在的,当字节码文件被读取后就会释放掉。
动态代理就是只有在用到生成的字节码文件时会加载,静态代理会一开始就加载好字节码文件
下面,我们写一个测试类来实现一下动态代理(这里就直接用main方法,不使用junit测试了)
public static void main(String[] args) {
Actor actor = new Actor();
IActor iActor =(IActor)Proxy.newProxyInstance(actor.getClass().getClassLoader(), actor.getClass().getInterfaces() , new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
Float money = (Float) args[0];
Object rtValue = null;
if ("basicAct".equals(name)){
if (money > 2000){
rtValue = method.invoke(actor,money/2);
}
}
if ("dangerAct".equals(name)){
if (money > 8000){
rtValue = method.invoke(actor,money/2);
}
}
return rtValue;
}
});
iActor.basicAct(5000f);
iActor.dangerAct(50000f);
}
在上面的代码中,很明显可以看出actor就是被代理的对象
在创建代理对象的实例化时,用到了三个参数,分别是被代理对象的类加载器(ClassLoader),接口(Interfaces),和一个invocationHandle的匿名内部类(这也就是为什么基于JDK的动态代理为什么必须实现接口的原因)
在匿名内部类中,通过method.getName()方法获取方法名,通过对方法名的判断,可以对指定的方法进行增强,而通过args[]可以获得想要获得的参数(索引从0开始)
可以看出invoke方法返回值为一个object,所以先创建一个空对象,用来接收invoke方法的返回值
在上述的例子中,假定经纪人会扣取演员一半的费用,所以对方法的增强就是将传入的参数money除以2
而如果没有进行方法增强,演员会得到所有的费用
同理,在invoke方法中,也可以对方法进行别的增强
下图就是增强后的运行结果
二、基于子类的动态代理
提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
要求:被代理类不能用 final 修饰的类(最终类)。
基于cglib实现的动态代理,并不要求被代理类必须实现接口,只要求其不能用final修饰
下图就是基于cglib实现的动态代理的被代理类
下面写一个main方法来测试一下
可以看出,基于cglib的动态代理,用到了第三方jar包中的Enhancer类中的create方法,与基于jdk的实现方式不同的是并没有用到接口
两种动态代理实现方式的选择
已经很明显了,如果被代理的类实现了接口的话,就要用基于JDK的方式,如果没有就要用到基于cglib的方式
动态,说到底指的是字节码文字的加载是动态的,spring的aop底层就用到了动态代理
欢迎各位大佬指导修正,接下来的一段时间我会每天分享对spring的一些感悟和踩过的很多坑,望大佬们能帮忙解答很多我解决不了的问题。