一、 静态代理、JDK和CGLIB动态代理
1、静态代理
需要代理对象和目标对象实现一样的接口
优点:可以在不修改目标对象的前提下扩展目标对象的功能。
缺点:
冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
这点类似于装饰者模式
//把InputStreamReader装饰成BufferedReader来成为具备缓冲能力的Reader。
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
Java IO流装饰者模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例(各种字符流间装饰,各种字节流间装饰)。
1、JDK动态代理
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
2、CGLIB动态代理
Code Generation Library,利用ASM开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
3、何时使用JDK还是CGLIB?
1)如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
2)如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
3)如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
因为JVM动态生成的代理类有如下特性:
继承了Proxy类,实现了代理的接口,最终形式如下(HelloInterface为被代理类实现的接口):
public final class $Proxy0 extends Proxy implements HelloInterface{
…
}
然而由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。
4、如何强制使用CGLIB实现AOP?
1)添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
2)在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
5、静态代理、JDK动态代理和CGLIB字节码生成的区别?
1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。
3)静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
总结:
1)静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。
2)JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。
3)动态代理生成的类为 lass com.sun.proxy.$Proxy4,cglib代理生成的类为class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6。
4)静态代理在编译时产生class字节码文件,可以直接使用,效率高。
动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。
6、CGlib比JDK快?
1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,
在jdk6之前CGLib比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
2)在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,
总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。
7、Spring如何选择用JDK还是CGLIB?
1)当Bean实现接口时,Spring就会用JDK的动态代理。
2)当Bean没有实现接口时,Spring使用CGlib是实现。
3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。
8、什么时候使用代理模式
1、当我们想要隐藏某个类时,可以为其提供代理。
2、当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中进行权限判断来进行不同权限的功能调用)。
3、当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行)。
二、代码验证
1、核心代码目录结构红色框已标注
代码下载地址:
整体代码下载
2、IUserManager接口
package com.proxy;
//被代理的接口 ---用户管理接口
public interface IUserManager {
void addUser(String id, String password);
}
3、UserManagerImpl接口实现类
package com.proxy;
//被代理的实现类 ----用户管理接口实现
public class UserManagerImpl implements IUserManager {
@Override
public void addUser(String id, String password) {
// TODO Auto-generated method stub
System.out.println("======调用了UserManagerImpl.addUser()方法======");
System.out.println(id+"------"+password);
}
}
4、JDKProxy动态代理类
package com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//JDK动态代理类
//利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
public class JDKProxy implements InvocationHandler{
//需要代理的目标对象
private Object targetObject;
//将目标对象传入进行代理
public Object newProxy(Object targetObject) {
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);
}
@Override
//InvocationHandler接口的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 一般我们进行逻辑处理的函数,比如这个地方是打印方法名
printMethod();
// 设置方法的返回值
Object ret = null;
// 调用invoke方法,ret存储该方法的返回值
ret = method.invoke(targetObject, args);
return ret;
}
//逻辑处理,比如打印方法名
private void printMethod() {
System.out.println("======打印方法名()======");
}
}
5、CGLibProxy动态代理
package com.proxy;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CGLibProxy implements MethodInterceptor {
/** CGLib需要代理的目标对象 */
private Object targetObject;
public Object createProxyObject(Object obj) {
this.targetObject = obj;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
Object proxyObj = enhancer.create();
// 返回代理对象
return proxyObj;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object obj = null;
// 过滤方法
if ("addUser".equals(method.getName())) {
// 一般我们进行逻辑处理的函数,比如这个地方是打印方法名
printMethod();
}
obj = method.invoke(targetObject, args);
return obj;
}
// 逻辑处理,比如打印方法名
private void printMethod() {
System.out.println("======打印方法名()======");
}
}
6、ClientTest测试类
package com.proxy;
//代理模式: 客户端--》代理对象--》目标对象
public class ClientTest {
public static void main(String[] args) {
System.out.println("**********************CGLibProxy**********************");
CGLibProxy cgLibProxy = new CGLibProxy();
IUserManager userManager = (IUserManager) cgLibProxy.createProxyObject(new UserManagerImpl());
userManager.addUser("张三丰", "123456");
System.out.println("*********分割线*************分割线**************分割线********");
System.out.println("**********************JDKProxy**********************");
JDKProxy jdkPrpxy = new JDKProxy();
IUserManager userManagerJDK = (IUserManager) jdkPrpxy.newProxy(new UserManagerImpl());
userManagerJDK.addUser("张三丰", "123456");
}
}
测试结果如下
参考文章
https://blog.csdn.net/weixin_39666581/article/details/103899190
https://blog.csdn.net/yhl_jxy/article/details/80635012
https://blog.csdn.net/doujinlong1/article/details/80680149
https://blog.csdn.net/yhl_jxy/article/details/80586785