本文为笔者近期在公司进行的技术分享,在此记录总结。
相比于静态代理方式需要开发者自己声明代理类,动态代理能够在运行中生成代理类,实现无侵入的代码拓展,简化了开发,提高代码拓展性。
先声明一个委托接口和委托类:
public interface Delegation {
void doSomething();
}
public class DelegationImpl implements Delegation {
@Override
public void doSomething() {
System.out.println("real Delegation exec");
}
}
动态代理实现方式有以下几种,本文重点讲述前两种:
JDK 动态代理
JDK 1.3开始支持动态代理类创建,常用于 Spring AOP ,核心类 java.lang.reflect.Proxy
和java.lang.reflect.InvocationHandler
- 首先创建实际需要执行的代码逻辑,即实现
InvocationHandler
接口invoke()
方法
public class JdkProxy implements InvocationHandler {
private Object bean;
public JdkProxy(Object bean) {
this.bean = bean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("doSomething")){
System.out.println("before doSomething");
}
return method.invoke(bean, args);
}
}
- 生成动态代理并执行
public static void main(String[] args) {
JdkProxy proxy = new JdkProxy(new DelegationImpl());
Delegation delegation = (Delegation) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Delegation.class}, proxy);
delegation.doSomething();
}
- 正常情况下我们看不到动态代理的具体代码,这里我们可以用以下代码输出动态代理的字节码,然后反编译查看具体代码
byte[] classFile = ProxyGenerator.generateProxyClass("proxyJdk", new Class[]{Delegation.class});
FileOutputStream fos = null;
try {
fos = new FileOutputStream("D:/ProxyJdk.class");
fos.write(classFile);
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// Source code is decompiled from a .class file using FernFlower decompiler.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.soga.yasuo.web.Delegation;
public final class proxyJdk extends Proxy implements Delegation {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
publicproxyJdk(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void doSomething() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("org.soga.yasuo.web.Delegation").getMethod("doSomething");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
从反编译的代码可以看出生成的代理类继承 Proxy 类,所以委托类必须实现一个接口(Java 仅支持单继承)。
InvocationHandler
的 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
方法中,调用 method.invoke(proxy, args)
会出现死循环,所以基本都会传入委托类,反射调用委托类对应的方法。
Cglib 动态代理
Spring 在 5.X 之前默认的动态代理实现一直是jdk动态代理。但是从 5.X 开始,Spring 就开始默认使用 Cglib 来作为动态代理实现。并且 Springboot 从 2.X 开始也转向了 Cglib 动态代理实现。
是什么导致了 Spring 体系整体转投 Cglib 呢,jdk 动态代理又有什么缺点呢?
那么我们现在就要来说下 Cglib 的动态代理。
Cglib是一个开源项目,它的底层是字节码处理框架 ASM,Cglib 提供了比 jdk 更为强大的动态代理。主要相比 jdk 动态代理的优势有:
- Jdk 动态代理只能基于接口,代理生成的对象只能赋值给接口变量,而Cglib就不存在这个问题,Cglib 是通过生成子类来实现的,代理对象既可以赋值给实现类,又可以赋值给接口。
- Cglib 速度比 jdk 动态代理更快,性能更好。
- 创建 CglibProxy :
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
private Object bean;
public CglibProxy(Object bean) {
this.bean = bean;
}
public Object getProxy(){
//设置需要创建子类的类
enhancer.setSuperclass(bean.getClass());
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
if (methodName.equals("doSomething")){
System.out.println("before doSomething");
}
//调用原bean的方法
return method.invoke(bean,args);
}
}
- 执行代理
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy(new DelegationImpl());
Delegation delegation = (Delegation) proxy.getProxy();
delegation.doSomething();
- Cglib 基于继承实现,会动态生成一个委托类的子类,所以对于 static 方法、private 方法、final 方法和 final 类则无法代理(方法无法被复写、类无法被继承)。
- 相比于 jdk,Cglib 既可以代理接口,又可以代理普通类。
- Cglib 会默认代理Object中 equals, toString, hashCode, clone等方法。比JDK代理多了clone。
- Cglib 做了方法访问优化,使用建立方法索引的方式避免了传统JDK动态代理需要通过Method方法反射调用,性能更优
Javassist
Javassist 是一个开源的分析、编辑和创建Java字节码的类库,可以直接编辑和生成Java生成的字节码。相对于bcel, asm等这些工具,开发者不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。性能比ASM稍差些,但是简单。
在日常使用中,javassit 通常被用来动态修改字节码。它也能用来实现动态代理的功能。Dubbo 中使用 Javassist 生成动态类。
public class JavassitProxy {
private Object bean;
public JavassitProxy(Object bean) {
this.bean = bean;
}
public Object getProxy() throws IllegalAccessException, InstantiationException {
ProxyFactory f = new ProxyFactory();
f.setSuperclass(bean.getClass());
f.setFilter(m -> ListUtil.toList("doSomething").contains(m.getName()));
Class c = f.createClass();
MethodHandler mi = (self, method, proceed, args) -> {
String methodName = method.getName();
if (methodName.equals("doSomething")){
System.out.println("before doSomething");
}
return method.invoke(bean, args);
};
Object proxy = c.newInstance();
((Proxy)proxy).setHandler(mi);
return proxy;
}
}
public static void main(String[] args) {
JavassitProxy proxy = new JavassitProxy(new DelegationImpl());
Delegation delegation = (Delegation) proxy.getProxy();
delegation.doSomething();
同样是基于子类的方式代理委托类,不过使用时可以用 filter 过滤需要代理的方法。
ByteBuddy
ByteBuddy也是一个大名鼎鼎的开源库,和Cglib一样,也是基于ASM实现。还有一个名气更大的库叫Mockito,相信不少人用过这玩意写过测试用例,其核心就是基于ByteBuddy来实现的,可以动态生成mock类,非常方便。另外ByteBuddy另外一个大的应用就是java agent,其主要作用就是在class被加载之前对其拦截,插入自己的代码。
ByteBuddy非常强大,是一个神器。可以应用在很多场景。但是这里,只介绍用 ByteBuddy 来做动态代理,关于其他使用方式,鉴于目前还没有学习,暂不深入。
public class ByteBuddyProxy {
private Object bean;
public ByteBuddyProxy(Object bean) {
this.bean = bean;
}
public Object getProxy() throws Exception{
Object object = new ByteBuddy().subclass(bean.getClass())
.method(ElementMatchers.namedOneOf("doSomething"))
.intercept(InvocationHandlerAdapter.of(new AopInvocationHandler(bean)))
.make()
.load(ByteBuddyProxy.class.getClassLoader())
.getLoaded()
.newInstance();
return object;
}
public class AopInvocationHandler implements InvocationHandler {
private Object bean;
public AopInvocationHandler(Object bean) {
this.bean = bean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("doSomething")){
System.out.println("before doSomething");
}
return method.invoke(bean, args);
}
}
// 执行
ByteBuddyProxy proxy = new ByteBuddyProxy (new DelegationImpl());
Delegation delegation = (Delegation) proxy.getProxy();
delegation.doSomething();
}
同样是基于生成子类实现。
总结
动态代理技术对于一个经常写开源或是中间件的人来说,是一个神器。这种特性提供了一种新的解决方式。从而使得代码更加优雅而简单。作为Java开发者都应该有所了解。