前言
如果你点进这篇文章,很有可能你已经接触过了Spring的面向切面的编程思想.所谓代理,就是在原有的代码上进行扩展,简单的来说就是在进入一个方法之前会调用另一个方法.
静态代理
Java中的静态代理是通过和要进入的目标方法实现相同的接口,这也限制了程序的可扩展性.当接口改变时,对应的代理类需要去实现改变后的接口,具体如下:
首先是接口,IUserDao.java
package com.hbu.proxy;
/**
* 接口
*/
public interface IUserDao {
/**
* 保存
*/
void save();
/**
* 运行
*/
void run();
}
被代理的实现类,UserDao.java
package com.hbu.proxy;
/**
* 被代理的目标,要实现在进入save方法之前执行别的方法
*/
public class UserDao implements IUserDao {
@Override
public void save() {
System.out.println("保存成功...");
}
@Override
public void run() {
System.out.println("Bery Run!");
}
}
代理类UserDaoProxy.java
package com.hbu.proxy;
/**
* 代理类
*/
public class UserDaoProxy implements IUserDao {
@Override
public void run() {
//在target.save()之前执行的方法,@Before
System.out.println("开始事务");
target.run();
//在target.save()之后执行的方法,@After
System.out.println("结束事务");
}
/**
* 被代理的对象
*/
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target = target;
}
@Override
public void save() {
//在target.save()之前执行的方法,@Before
System.out.println("开始事务");
target.save();
//在target.save()之后执行的方法,@After
System.out.println("结束事务");
}
}
静态代理测试,Test.java
package com.hbu.proxy;
/**
* 静态代理
*/
public class Test {
public static void main(String[] args) {
UserDao target = new UserDao();
//代理对象,把目标对象传给代理对象,建立代理关系
UserDaoProxy userDaoProxy = new UserDaoProxy(target);
//调用代理对象的方法
userDaoProxy.save();
}
}
JDK动态代理
JDK的动态代理不像静态代理一样,不需要去实现被代理的类的接口,当接口改变时,不需要做任何改变.
动态代理类,ProxyFactory.java
package com.hbu.proxy.dynamic;
import java.lang.reflect.Proxy;
/**
* 动态创建代理对象
* 动态代理不需要实现接口,但是需要指定接口类型
*/
public class ProxyFactory {
/**
* 维护一个目标对象
*/
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance(){
//三个参数
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("开始事务111");
//执行目标对象方法
Object returnValue = method.invoke(target,args);
System.out.println("提交事务111");
return returnValue;
});
}
}
测试:
public static void main(String[] args) {
//目标对象
IUserDao target = new UserDao();
//原始类型
System.out.println(target.getClass());
//给目标对象,创建代理对象
IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();
proxy.save();
proxy.run();
}
更加深入的介绍
JDK的动态代理是通过Java指令集, 生成了一个byte[]数组的class文件, 核心是通过ProxyGenerator.generateProxyClass(String, Class<?>[], int)方法.该方法的返回值就是生成的class文件的byte数组, 如果你将这个byte[]数组通过写文件的形式存储到了磁盘上,然后通过Idea自带的反编译查看该class文件,会看到jdk做动态代理是通过接口的形式做代理,生成的类继承自Proxy类,并且实现了你要代理的接口。再细一些就是内部的InvocationHandler调用的invoke方法,以反射的手段完成动态代理的操作。
要想真正的了解,还是推荐大家将生成的byte[]数组存储到磁盘上,然后通过编译器自带的反编译查看文件内部的内容。
CGLib介绍
CGLIB(Code Generator Library)是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。简单的说,CGLib可以用于修改字节码,可以在动态的为某个引用赋值.
CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。CGLIB和动态代理最大的不同就是它针对某一个类去实现方法,而不是一个实现了接口的类.在动态代理中,我只能代理UserDao中哪些实现了IUserDao接口的方法,而不能代理它自己独有的方法.
CGLib组成结构
CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解.
CGLib总结
在SSM项目的Controller中,一个@AutoWired注解就能够动态绑定bean对象.在RPC中,为了能够找到API对应的接口实现,我们需要动态的创建一个Service接口的实现,在实现中我们进行了发数据和取数据的操作.当然了,这些对于消费者来说,他们毫不知情.
CGLib的代理不像Jdk代理,对代理类有一个接口的限制,CGLib可以代理那些没有接口的类,核心是通过继承重写的方式完成代理,在debug时,同样能够在代码中找到生成出的byte[]数组,反编译之后大概长成这个样子。
package com.ssm.blog;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class StringUtil$$EnhancerByCGLIB$$ffedb4c4 extends StringUtil implements Factory {
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$isEmpty$0$Method;
private static final MethodProxy CGLIB$isEmpty$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
......
}
被代理的StringUtil.java:
package com.ssm.blog;
/**
* @author 申劭明
* @date 2020/10/29 23:36
*/
public class StringUtil {
public boolean isEmpty(String target) {
return target == null || target.isEmpty();
}
}
JDK代理和CGLib的区别
两者同样作为动态代理的框架,在性能上有着一定的区别,在JDK1.6之前,CGLib生成代理类的性能非常高,但在JDK之后的版本二者从性能上没有明显的差别。但是在CGLib中有自带生成缓存的功能,所以如果是多次生成同一个Class,使用CGLib肯定是要快于JDK代理的。在Spring AOP中,默认是如果被代理类没有Implements 接口,就会使用CGLib,否则使用JDK代理,当然了,你也可以显式的声明要求使用JDK代理。
参考文章: