Java动态代理四种实现⽅式详解
代理模式也是⼀种⾮常常见的设计模式。了解Spring框架的都知道,Spring AOP 使⽤的就是动态代理模式。今天就来系统的重温⼀遍代理模式。
在现实⽣活中代理是随处可见的,当事⼈因某些隐私不⽅便出⾯,或者当事⼈不具备某些相关的专业技能,⽽需要⼀个职业⼈员来完成⼀些专业的操作,也可能由于当事⼈没有时间处理事务,⽽聘⽤代理⼈出⾯。⽽在软件设计中,使⽤代理模式的地⽅也很多,由于安全原因,屏蔽客户端直接访问真实对象,或者为了提升系统性能,使⽤代理模式实现延迟加载,还有就是AOP,对委托类的功能进⾏增强等。
⼀、代理模式的结构
代理模式的主要参与者有4个,如下图所⽰:
⾓⾊作⽤
Subject主题接⼝,定义了代理类和委托类的公共对外⽅法,也是代理类代理委托类的⽅法
RealSubject委托类,真实主题,真正实现业务逻辑的类
Proxy代理类,代理和封装委托类
Client客户端,使⽤代理类和主题接⼝完成业务逻辑
loading="lazy" alt="" />⾓⾊作⽤Subject主题接⼝,定义了代理类和委托类的公共对外⽅法,也是代理类代理委托类的⽅法RealSubject委托类,真实主题,真正实现业务逻辑的类Proxy代理类,代理和封装委托类Client客户端,使⽤代理类和主题接⼝完成业务逻辑
⼆、代理模式的实现
代理模式⼀般分为静态代理和动态代理两种:
静态代理,顾名思义,就是提前创建好代理类⽂件并在程序运⾏前已经编译成字节码。
动态代理,是指在运⾏时动态⽣成代理类,即代理类的字节码将在运⾏时⽣成并载⼊到ClassLoader中。
了解了两种代理模式⼤概区别后,接下来就以⼀个短信发送功能增强的⽰例来详细阐述两种代理的实现⽅式。
1、静态代理实现
第⼀步,定义主题接⼝,该接⼝只有⼀个send⽅法:
public interface ISender {
public boolean send();
}
第⼆步,定义主题真正实现类:
public class SmsSender implements ISender {
public boolean send() {
System.out.println("sending msg");
return true;
}
}
第三步,创建代理类,封装实现类:
public class ProxySender implements ISender {
private ISender sender;
public ProxySender(ISender sender){
this.sender = sender;
}
public boolean send() {
System.out.println("处理前");
boolean result = sender.send();
System.out.println("处理后");
return result;
}
}
第四步,客户端调⽤:
@Test
public void testStaticProxy(){
ISender sender = new ProxySender(new SmsSender());
boolean result = sender.send();
System.out.println("输出结果:" + result);
}
以上就实现了⼀个简单的静态代理,很明显,静态代理需要为每个真实主题定义⼀个形式上完全⼀样的封装类,
如果真实主题⽅法有所修改,那代理类也需要跟着修改,不利于系统的维护。
2、动态代理实现
与静态代理相⽐,动态代理有更多优势,动态代理不仅不需要定义代理类,甚⾄可以在运⾏时指定代理类的执⾏逻辑,从⽽⼤⼤提升系统的灵活性。
⽬前动态代理类的⽣成⽅法有很多,有JDK⾃带的动态代理、CGLIB、Javassist和ASM库等。
JDK动态代理:内置在JDK中,不需要引⼊第三⽅jar,使⽤简单,但功能⽐较弱。
CGLIB/Javassist:这两个都是⾼级的字节码⽣成库,总体性能⽐JDK动态代理好,且功能强⼤。
ASM:低级字节码⽣成⼯具,近乎使⽤bytecode编码,对开发⼈员要求最⾼。当然性能也是最好(相⽐前⼏种也不是很⼤的提升,这⾥不做具体介绍)。
以下实例依然以SmsSender和ISender作为被代理对象和接⼝进⾏试验。
1) JDK动态代理
JDK的动态代理需要实现⼀个处理⽅法调⽤的Handler,⽤于实现代理⽅法的内部逻辑,实现InvocationHandler接⼝。
public class JdkProxyHandler implements InvocationHandler {
private Object target;
public JdkProxyHandler(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("处理前");
Object result = method.invoke(target,args);
System.out.println("处理后");
return result;
}
}
客户端调⽤:
@Test
public void testJdkProxy(){
ISender sender = (ISender) SystemClassLoader(),
new Class[]{ISender.class},
new JdkProxyHandler(new SmsSender()));
boolean result = sender.send();
System.out.println("代理对象:" + Class().getName());
System.out.println("输出结果:" + result);
}
输出结果:
处理前
sending msg
处理后
代理对象:com.sun.proxy.$Proxy4
输出结果:true
这样实现⼀个简单的AOP就完成了,我们看到代理类的类型是com.sun.proxy.$Proxy4。那JDK是如何创建代理类?
⾸先从wProxyInstance⼊⼿,来研究JDK是如何⽣成代理类:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
该⽅法有3个参数:
loader:⽤哪个类加载器去加载代理对象,⽣成⽬标对象的代理需要确保其类加载器相同,所以需要将⽬标对象的类加载器作为参数传递。
interfaces:代理类需实现的接⼝列表,JDK动态代理技术需要代理类和⽬标对象都继承⾃同⼀接⼝,所以需要将⽬标对象的接⼝作为参数传递。
h:调⽤处理器,调⽤实现了InvocationHandler类的⼀个回调⽅法,对⽬标对象的增强逻辑在这个实现类中。
具体代码如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
//1.检查
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = SecurityManager();
if (sm != null) {
CallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
//获取代理类类型
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
CallerClass(), cl);
}
//通过反射创建代理对象
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.Modifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
wInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new String(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new String(), t);
}
java反射获取父类属性} catch (NoSuchMethodException e) {
throw new String(), e);
}
}
总结:具体代码细节就不在这⾥深究,但可以明显的看出,JDK的动态代理底层是通过Java反射机制实现的,并且需要⽬标对象继承⾃⼀个接⼝才能⽣成它的代理类。2) CGLIB(Code Generation Library)动态代理
使⽤CGLIB动态代理前需要引⼊依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
和JDK代理不同,CGLib动态代理技术不需要⽬标对象实现⾃⼀个接⼝,只需要实现⼀个处理代理逻辑的切⼊类,并实现MethodInterceptor接⼝。定义真实主题实现类:
public class BdSender {
public boolean send() {
System.out.println("sending msg");
return true;
}
}
代理类逻辑处理类:
public class CglibProxyInterceptor implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
/**
* 获取代理类
* @param clazz
* @return
*/
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
ate();
}
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("处理前");
Object result = methodProxy.invokeSuper(object,args);
System.out.println("处理后");
return result;
}
}
客户端调⽤:
@Test
public void testCglibProxy(){
BdSender sender = (BdSender) new CglibProxyInterceptor().getProxy(BdSender.class);
boolean result = sender.send();
System.out.println("代理对象:" + Class().getName());
System.out.println("输出结果:" + result);
}
输出结果:
处理前
sending msg
处理后
代理对象:org.yd.proxy.BdSender$$EnhancerByCGLIB$$d65f9e34
输出结果:true
总结CgLib的特点:
使⽤CGLib实现动态代理,完全不受代理类必须实现接⼝的限制
CGLib底层采⽤ASM字节码⽣成框架,使⽤字节码技术⽣成代理类,⽐使⽤Java反射效率要⾼
CGLib不能对声明为final的⽅法进⾏代理,因为CGLib原理是动态⽣成被代理类的⼦类
3)Javassist动态代理
Javassist是⼀个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和⽣成Java⽣成的字节码。
相对于bcel, asm等这些⼯具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态⽣成类。
使⽤avassist动态代理前需要引⼊依赖:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
使⽤Javassist⽣成动态代理可以有以下两种⽅式:
代理⼯⼚创建:需要实现MethodHandler⽤于代理逻辑处理,实现与CGLib⾮常类似
动态代码创建:可通过Java代码⽣成字节码,这种⽅式创建的动态代理⾮常灵活,甚⾄可以在运⾏时⽣成业务逻辑
代理⼯⼚创建 — 代理逻辑处理类
public class JavassistProxyHandler implements MethodHandler {
private ProxyFactory proxyFactory = new ProxyFactory();
/**
* 获取代理对象
* @param clazz 被代理类
* @return
* @throws Exception
*/
public Object getProxy(Class clazz) throws Exception {
proxyFactory.setSuperclass(clazz);
Class<?> factoryClass = ateClass();
Object proxy = wInstance();
((ProxyObject)proxy).setHandler(this);
return proxy;
}
public Object invoke(Object object, Method method, Method method1, Object[] args) throws Throwable {
System.out.println("处理前");
Object result = method1.invoke(object,args);
System.out.println("处理后");
return result;
}
}
客户端调⽤:
@Test
public void testJavassistProxy() throws Exception {
BdSender sender = (BdSender) new JavassistProxyHandler().getProxy(BdSender.class);
boolean result = sender.send();
System.out.println("代理对象:" + Class().getName());
System.out.println("输出结果:" + result);
}
输出结果
处理前
sending msg
处理后
代理对象:org.yd.proxy.BdSender_$$_jvstbce_0
输出结果:true
动态代码创建 — 代理逻辑处理类:
public static Object getProxy(Class clazz) throws Exception {
ClassPool mPool = Default();
CtClass c0 = (Name());
//定义代理类名称
CtClass mCtc = mPool.Name() + "$$BytecodeProxy");
/
/添加⽗类继承
mCtc.setSuperclass(c0);
//添加类的字段信息
CtField field = new CtField(c0, "real", mCtc);
field.setModifiers(AccessFlag.PRIVATE);
mCtc.addField(field);
//添加构造函数
CtConstructor constructor = new CtConstructor(new CtClass[]{c0},mCtc);
constructor.setBody("{$0.real = $1;}"); // $0代表this, $1代表构造函数的第1个参数
mCtc.addConstructor(constructor);
//添加⽅法
CtMethod ctMethod = Superclass().getDeclaredMethod("send");
CtMethod newMethod = new ReturnType(), Name(),ParameterTypes(), mCtc);
newMethod.setBody("{" +
"System.out.println(\"处理前\");" +
"boolean result = $0.real.send();" +
"System.out.println(\"处理后\");" +
"return result;}");
mCtc.addMethod(newMethod);
//⽣成动态类
Class().getConstructor(clazz).wInstance());
}
客户端调⽤:
@Test
public void testJavassisBytecodetProxy() throws Exception {
BdSender sender = (BdSender) Proxy(BdSender.class);
boolean result = sender.send();
System.out.println("代理对象:" + Class().getName());
System.out.println("输出结果:" + result);
}
输出结果:
处理前
sending msg
处理后
代理对象:org.yd.proxy.BdSender$$BytecodeProxy
输出结果:true
Javassist被⽤于struts2和hibernate中,都⽤来做动态字节码修改使⽤。⼀般开发中不会⽤到,但在封装框架时⽐较有⽤。
以上介绍了静态代理和动态代理创建的⼏种⽅法与优缺点介绍,希望可以帮到⼤家。
到此这篇关于Java四种动态代理实现⽅式的⽂章就介绍到这了,更多相关Java动态代理内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!