文章目录
JDK动态代理和CGLIB代理
1、引言
动态代理在 Java 中有着广泛的应用,比如 AOP 的实现原理、RPC远程调用、Java 注解对象获取、日志框架、全局性异常处理、事务处理等。
Spring框架的声明式事务管理,本质就是代理设计模式的体现
在了解动态代理前,我们需要先了解一下什么是代理模式。
2、代理模式
代理模式(Proxy Pattern)
是 23 种设计模式的一种,属于结构型模式。他指的是一个对象本身不做实际的操作,而是通过其他对象来得到自己想要的结果。这样做的好处是可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
这里能体现出一个非常重要的编程思想:不要随意去改源码,如果需要修改,可以通过代理的方式来扩展该方法。
如上图所示,用户不能直接使用目标对象,而是构造出一个代理对象,由代理对象作为中转,代理对象负责调用目标对象真正的行为,从而把结果返回给用户。
也就是说,代理的关键点就是代理对象和目标对象的关系。
代理模式主要由三个元素共同构成:
1)一个接口,接口中的方法是要真正去实现的。
2)被代理类,实现上述接口,这是真正去执行接口中方法的类。
3)代理类,同样实现上述接口,同时封装被代理类对象,帮助被代理类去实现方法
使用代理模式必须要让代理类和目标类实现相同的接口,客户端通过代理类来调用目标方法,代理类会将所有的方法调用分派到目标对象上反射执行,还可以在分派过程中添加”前置通知”和后置处理!
(如在调用目标方法前校验权限,在调用完目标方法后打印日志等)等功能。
3、静态代理
第一步:创建 UserService 接口
public interface UserService {
// 添加 user
public void addUser(User user);
// 删除 user
public void deleteUser(int uid);
}
第二步:创建 UserService的实现类
public class UserServiceImpl implements UserService {
public void addUser(User user) {
System.out.println("增加 User");
}
public void deleteUser(int uid) {
System.out.println("删除 User");
}
}
第三步:创建事务类
public class MyTransaction {
// 开启事务
public void before() {
System.out.println("开启事务");
}
// 提交事务
public void after() {
System.out.println("提交事务");
}
}
第四步:创建代理类 ProxyUser.java
public class ProxyUser implements UserService {
// 真实类
private UserService userService;
// 事务类
private MyTransaction transaction;
// 使用构造函数实例化
public ProxyUser(UserService userService, MyTransaction transaction) {
this.userService = userService;
this.transaction = transaction;
}
public void addUser(User user) {
transaction.before();
userService.addUser(user);
transaction.after();
}
public void deleteUser(int uid) {
transaction.before();
userService.deleteUser(uid);
transaction.after();
}
}
测试:
public class TestUser {
@Test
public void testOne() {
MyTransaction transaction = new MyTransaction();
UserService userService = new UserServiceImpl();
// 产生静态代理对象
ProxyUser proxy = new ProxyUser(userService, transaction);
proxy.addUser(null);
proxy.deleteUser(0);
}
}
运行结果:
这是一个很基础的静态代理,业务类UserServiceImpl 只需要关注业务逻辑本身,保证了业务的重用性,这也是代理类的优点,没什么好说的。我们主要说说这样写的缺点:
①、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
②、如果接口增加一个方法,比如 UserService 增加修改 updateUser()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
4、使用JDK动态代理
测试:
接口:
public interface UserService {
int addUser(User u);
int deleteById(Integer id);
int updateById(User u);
User findById(Integer id);
}
目标类:
/**
* 目标类
*/
public class UserServiceImpl implements UserService {
/**
* 目标方法
* @param u
* @return
*/
public int addUser(User u) {
System.out.println("调用了dao层新增方法,新增对象:"+u);
return 1;
}
/**
* 目标方法
* @param id
* @return
*/
public int deleteById(Integer id) {
System.out.println("调用了dao层删除方法,删除对象:"+id);
return 1;
}
public int updateById(User u) {
System.out.println("调用了dao层更新方法,新增对象:"+u);
return 1;
}
public User findById(Integer id) {
System.out.println("正在查询ID为的姐姐...");
return new User(111,"小姐姐");
}
}
增强类(要增强目标类的类)
/**
* 事务类
*/
public class Transaction {
/**
* 开启事务
*/
public void openTx(){
System.out.println("事务逻辑代码-开启事务");
}
/**
* 提交事务
*/
public void commitTx(){
System.out.println("事务逻辑代码-提交事务");
}
}
重写方法拦截器:
/**
* 方法拦截处理器
*/
public class MyInvocationHandler implements InvocationHandler {
//事务增强
Transaction tx=new Transaction();
//目标对象
UserService us=new UserServiceImpl();
/**
*
* @param proxy 代理类对象
* @param method 目标类中的目标方法
* @param args 目标类中的目标方法参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=null;
/**
* 这里我们为什么要进行判断,就是为了方便不对某些方法进行增强,虽然方法被代理了,但是没有被增强((目标类中不是所有的类都需要增强)。
*/
if (!"findById".equals(method.getName())){
tx.openTx();
//调用目标类的方法,反射调用
result = method.invoke(us, args);
tx.commitTx();
}else {
//调用目标类目标方法,反射调用
result=method.invoke(us,args);
}
return result;
}
}
测试:
@Test
public void test1(){
//方法拦截处理器
MyInvocationHandler h=new MyInvocationHandler();
//动态生成一个代理类对象
/**
* loader 定义类的类加载器
* interfaces 要代理类实现的接口列表
* h 方法拦截器
*/
// UserService o = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, h);
UserService us = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), h);
System.out.println("------------------------------------------------");
int count1 = us.deleteById(1);
System.out.println(count1>0?"删除成功":"删除失败");
System.out.println("------------------------------------------------");
int count2 = us.updateById(new User(111, "林志玲"));
System.out.println(count1>0?"更新成功":"更新失败");
System.out.println("------------------------------------------------");
User user = us.findById(1);
System.out.println("查询:"+user);
//把动态生成的代理类输出到硬盘上
saveProxyFile("D:\\");
}
/**
* 保存JDK动态代理生产的类class 保存在硬盘上学习之用!
* @param filePath 保存路径,默认在项目路径下生成 $Proxy4.class 文件
*/
private static void saveProxyFile(String filePath) {
FileOutputStream out = null;
try {
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy4", UserServiceImpl.class.getInterfaces());
String path=filePath + "$Proxy4.class";
System.out.println(path);
out = new FileOutputStream(path);
out.write(classFile);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.flush();
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
通过对$Proxy4.class进反编译,得到代理类
public final class $Proxy4 extends Proxy
implements UserService
{
private static Method m1;
private static Method m5;
private static Method m2;
private static Method m3;
private static Method m6;
private static Method m4;
private static Method m0;
public $Proxy4(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj})).booleanValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int updateById(User user)
{
try
{
return ((Integer)super.h.invoke(this, m5, new Object[] {
user
})).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final User findById(Integer integer)
{
try
{
return (User)super.h.invoke(this, m3, new Object[] {
integer
});
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int addUser(User user)
{
try
{
return ((Integer)super.h.invoke(this, m6, new Object[] {user})).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int deleteById(Integer integer)
{
try
{
return ((Integer)super.h.invoke(this, m4, new Object[] {
integer
})).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m5 = Class.forName("com.haidi8.service.UserService").getMethod("updateById", new Class[] {
Class.forName("com.haidi8.pojo.User")
});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.haidi8.service.UserService").getMethod("findById", new Class[] {
Class.forName("java.lang.Integer")
});
m6 = Class.forName("com.haidi8.service.UserService").getMethod("addUser", new Class[] {
Class.forName("com.haidi8.pojo.User")
});
m4 = Class.forName("com.haidi8.service.UserService").getMethod("deleteById", new Class[] {
Class.forName("java.lang.Integer")
});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch (NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch (ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
从图中可以看出来:
JDK的代理类$Proxy4和目标类UserServiceImpl都要实现接口UserService中的方法,我们以addUser(User user)为例,在测试中我们用
int count =us.addUser(new User(111,"张三"))
实际上是去调用代理类的addUser(User User),$Proxy4代理中该方法的具体实现如下:
public final int addUser(User user)
{
try
{
return ((Integer)super.h.invoke(this, m6, new Object[] {user})).intValue();
}
catch (Error ) { }
catch (Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
我们应该重点关注:
return ((Integer)super.h.invoke(this, m6, new Object[] {user})).intValue();
在上面的返回中我们应该注意 h.invoke(this,m6,new Object[]{user})).intValue();这个部分表明去调用了重写的方法拦截器MyInvocationHandler这个类里面的方法:
/**
*
* @param proxy 代理类对象
* @param method 目标类中的目标方法
* @param args 目标类中的目标方法参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result=null;
/**
* 这里我们为什么要进行判断,就是为了方便不对某些方法进行增强,虽然方法被代理了,但是没有被增强。
*/
if (!"findById".equals(method.getName())){
tx.openTx();
//调用目标类的方法,反射调用
result = method.invoke(us, args);
tx.commitTx();
}else {
//调用目标类目标方法,反射调用
result=method.invoke(us,args);
}
return result;
}
通过上面的形参:this,m6,new Object[]{user}我们知道了MyInvocation里面的Object proxy, Method method, Object[] args
这些实参对应的含义。其中this在代理类$Proxy4中代表当前类对象,也就是代理类的对象。m6在代理类中它的赋值:
m6 = Class.forName("com.haidi8.service.UserService").getMethod("addUser", new Class[] {
Class.forName("com.haidi8.pojo.User")
可以看出来,其是赋值接口中的方法。我们可以通过**result = method.invoke(us, args);**来获取目标方法(这里可以理解为多态的使用,我们调用接口中的方法,但是我们真正调用的是接口实现类中的方法)。这里又引出一个点,method.invoke(us,args)传入的参数,us是目标类的对象,可以调用目标类中的方法,如果传入的参数为method.invoke(proxy,args),proxy为代理类的对象,则是调用代理类中的方法(因为这个method.invoke(prox,args)会暴力反射去调用$proxy4中实现接口的方法,比如addUser(User user)),最后会造成栈溢出。
5、Mybatis底层使用JDK动态代理的原理
接口:
/**
* @Author haidi8
* @Date 2022/8/26 16:21
**/
public interface MybatisMapper {
int insert(User user);
int update(User user);
}
测试:
/**
* @Author haidi8
* @Date 2022/8/26 16:23
**/
public class TesstMybatisMapper {
@Test
public void test1(){
//获取接口代理对象
MybatisMapper mybatisMapper = (MybatisMapper) Proxy.newProxyInstance(MybatisMapper.class.getClassLoader(), MybatisMapper.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("insert")){
System.out.println("调用MyBatis执行器执行insert方法");
}else {
System.out.println("调用MyBatis执行器执行update方法");
}
return 1;
}
});
int insert = mybatisMapper.insert(new User());
System.out.println(insert);
int update = mybatisMapper.update(new User());
System.out.println(update);
}
}
6、使用CGLIB动态代理
使用JDK创建代理有一个限制,它只能为接口创建代理实例.这一点可以从Proxy的接口方法 newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)
中看的很清楚
第二个入参 interfaces就是需要代理实例实现的接口列表.
对于没有通过接口定义业务方法的类,如何动态创建代理实例呢? JDK动态代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这一空缺.
CGLib采用底层的字节码技术,可以为一个类创建子类, 在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势志入横切逻辑.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
目标类:
/**
* 目标类
*/
public class UserServiceImpl implements UserService {
/**
* 目标方法
* @param u
* @return
*/
public int addUser(User u) {
System.out.println("调用了dao层新增方法,新增对象:"+u);
return 1;
}
/**
* 目标方法
* @param id
* @return
*/
public int deleteById(Integer id) {
System.out.println("调用了dao层删除方法,删除对象:"+id);
return 1;
}
public int updateById(User u) {
System.out.println("调用了dao层更新方法,新增对象:"+u);
return 1;
}
public User findById(Integer id) {
System.out.println("正在查询ID为的姐姐...");
return new User(111,"小姐姐");
}
}
方法拦截器:
**
* @Author haidi8
* @Date 2022/8/26 16:39
**/
public class MyCGLBMethodIntoreceptor implements MethodInterceptor {
UserServiceImpl us=new UserServiceImpl();
//事务增强
Transaction tx=new Transaction();
/**
*
* @param o 生成的代理子类
* @param method 父类方法
* @param objects 方法参数
* @param methodProxy use to invoke super 表示调用父类的的方法
* @return
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
tx.openTx();
//调用目标类的目标方法(父类中的方法)
Object result=methodProxy.invokeSuper(o,objects);
tx.commitTx();
return result;
}
}
测试类:
/**
* @Author haidi8
* @Date 2022/8/26 16:51
**/
public class TestCGLB {
@Test
public void test1(){
// 在指定目录下生成CGLIB动态代理类Class
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\");
//方法拦截器
MyCGLBMethodIntoreceptor h=new MyCGLBMethodIntoreceptor();
//使用CGLIB框架生成目标类的子类(代理类)
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);//生成的代理类的父类
enhancer.setCallback(h); //拦截处理器
/**
* 目标类
*/
UserServiceImpl us= (UserServiceImpl) enhancer.create();//生成子类
System.out.println(us.getClass().getSimpleName());
int count = us.addUser(new User(111, "林志玲"));
System.out.println(count>0?"新增成功":"新增失败");
System.out.println("------------------------------------------------");
int count1 = us.deleteById(1);
System.out.println(count1>0?"删除成功":"删除失败");
System.out.println("------------------------------------------------");
int count2 = us.updateById(new User(111, "林志玲"));
System.out.println(count1>0?"更新成功":"更新失败");
System.out.println("------------------------------------------------");
User user = us.findById(1);
System.out.println("查询:"+user);
}
}
分析:
7、JDK和CGLIB动态代理总结
7.1.原理区别
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。核心是实现InvocationHandler接口,使用invoke()方法进行面向切面的处理,调用相应的通知。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。核心是实现MethodInterceptor接口,使用intercept()方法进行面向切面的处理,调用相应的通知。
-
如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
-
如果目标对象实现了接口,可以强制使用CGLIB实现AOP
-
如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
可以强制使用CGlib(在spring配置中加入
<aop:aspectj-autoproxy proxy-target-class=“true”/>
)
springboot项目配置:spring.aop.proxy-target-class=false
7.2.CGlib比JDK快?
1、CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
2、在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理。
3、在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。
7.3.各自局限:
1、JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理。
2、cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
8、总结
类型 | 机制 | 回调方式 | 适用场景 | 效率 |
---|---|---|---|---|
JDK动态代理 | 委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类原始方法 | 反射 | 目标类是接口 | 效率瓶颈在反射调用稍慢 |
CGLIB动态代理 | 继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑 | 通过FastClass方法索引调用 | 非接口类,非final类,非final方法 | 第一次调用因为要生成多个Class对象较JDK慢,多次调用因为方法索引较反射方式快,如果方法多swtich case过多其效率还需测试 |
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94163.html