@Catyee
2021-07-23T10:21:35.000000Z
字数 9777
阅读 472
设计模式
代理模式就是为其他对象提供一种代理以控制对这个对象的访问。代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
所谓静态代理就是在程序运行前就已经存在代理类的字节码文件。
/**
* 1、抽象对象接口
*/
public interface Subject {
// 业务A抽象接口
void businessA();
// 业务B抽象接口
void businessB();
}
/**
* 2、被代理类(也是接口实现类,实现具体的业务逻辑)
*/
public class RealSubject implements Subject {
// 业务A的具体实现逻辑
@Override
public void businessA() {
System.out.println("this is a business.");
}
// 业务B的具体实现逻辑
@Override
public void businessB() {
System.out.println("this is b business");
}
}
/**
* 3、代理类 (代理类一般会实现抽象接口,或者继承被代理类)
*/
public class SubjectProxy implements Subject {
private RealSubject realSubject;
public SubjectProxy() {
this.realSubject = new RealSubject();
}
// 代理并扩展业务A
@Override
public void businessA() {
// 调用前扩展
...
try {
// 调用业务A方法
this.realSubject.businessA();
} catch (Exception e) {
// 捕获异常扩展
...
}
// 调用后扩展
...
}
// 代理并扩展业务B
@Override
public void businessB() {
// 调用前扩展
...
try {
// 调用业务B方法
this.realSubject.businessB();
} catch (Exception e) {
// 捕获异常扩展
...
}
// 调用后扩展
...
}
}
/**
* 4、使用类
*/
public class Client {
public static void main(String[] args) {
Subject subject = new SubjectProxy();
subject.businessA();
subject.businessB();
}
}
以上就是一个完整的静态代理的代码示例,可以看到代理类是由程序员直接编写的,就是一个普通的类,在程序运行之前需要编译成具体的字节码文件。
静态代理模式的重用性并不强,主要体现在两方面,一是如果代理对象过多,程序员得手写1:1比例的代理类,繁琐、重复且难以维护。二是假如增强动作都比较类似,那代理类中不同的代理方法中还会有很多类似的代码。
静态代理的另一个缺点是一旦被代理类增加或者删除一个方法,那每个代理类都要手动修改,增加了维护的难度。
为了克服静态代理的缺点,就用了动态代理,所谓动态代理就是不需要再由程序员手动编写代理类,而是将代理类的生成延迟到JVM运行过程中,在需要用到代理类的时候才会按照代理设置自动生成代理类,然后实例化生成代理对象。
现在最为主流的动态代理是JDK的动态代理和CgLib动态代理,他们在实现上略有差异,JDK动态代理产生的代理类和被代理类实现了相同的接口,而cglib动态代理生成的代理类则是继承了被代理类,也就是说代理类是被代理类的子类。
以下是JDK动态代码的使用示例:
/**
* 1、抽象对象接口
*/
public interface Subject {
// 业务A抽象接口
void businessA();
// 业务B抽象接口
void businessB();
}
/**
* 2、被代理类(也是接口实现类,实现具体的业务逻辑)
*/
public class RealSubject implements Subject {
// 业务A的具体实现逻辑
@Override
public void businessA() {
System.out.println("this is a business.");
}
// 业务B的具体实现逻辑
@Override
public void businessB() {
System.out.println("this is b business");
}
}
/**
* 3、拦截器用于功能增强
*/
public class SubjectInterceptor<T extends Subject> implements InvocationHandler {
//目标类
private final T target;
public SubjectInterceptor(T target) {
this.target = target;
}
// 这里proxy就是代理对象本身,所以千万不要这样调用:method.invoke(proxy, args),会导致无限循环
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行前扩展"); // 执行前扩展
Object result = method.invoke(this.target, args);
System.out.println("执行后扩展"); // 执行后扩展
return result;
}
}
/**
* 4、生成代理对象,执行业务
*/
public class Client {
public static void main(String[] args) {
Subject subject = new RealSubject();
SubjectInterceptor<Subject> subjectInterceptor = new SubjectInterceptor<>(subject);
Subject subjectProxy = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(), subjectInterceptor);
subjectProxy.businessA();
subjectProxy.businessB();
// 将生成的代理类持久化到文件
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0.class", new Class[] {Subject.class});
try (FileOutputStream fos = new FileOutputStream("/tmp/$Proxy0.class")) {
fos.write(bytes);;
fos.flush();
}
}
}
首先关注InvocationHandler中的invoke方法,这个方法的第一个参数就是代理对象本身,第二个参数是当前调用方法的Method对象,第三个参数是方法的参数,我们要注意不能在代码中这样调用:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ...
Object result = method.invoke(proxy, args);
// ...
return result;
}
这样调用就会导致方法一直被拦截,然后再调用再拦截,成为死循环。
第56行Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h), 可以看到这个方法有三个参数,第一个参数是被代理对象的classloader,第二个参数是被代理对象实现的接口,第三个参数是拦截器。这里重点关注第二个参数,他是被代理对象所有实现的接口,生成代理对象的时候,代理对象也会实现这些接口,所以JDK的动态代理最终代理类和被代理类都会实现相同的接口,从另一方面来说要想使用JDK动态代理,必须要有至少一个抽象接口,这也是JDK动态代理的使用限制
JDK动态代理采用的是字节重组的方式,重新生成对象来替代原始对象,以达到动态代理的目的。JDK动态代理生成对象的步骤如下:
(1)JDK动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口。
(2)编译新生成的Java代码.class文件。
(3)重新加载到JVM中运行。
以上过程就叫字节码重组。JDK中有一个规范,在ClassPath下只要是$开头的.class文件,一般都是自动生成的,代理类就是自动生成的,可以通过打断点的方式进行调试查看。我们还可以将生成的代理类持久化到磁盘中,然后使用反编译软件进行查看(使用idea即可直接查看字节码文件)方法见上面第61及行之后的代码。
将动态生成的代理类反编译之后会发现:代理类继承了Proxy类,同时实现了所有被代理对象实现的那些接口。生成的代理类中有一个静态代码块,代码块中通过反射获取了被代理类的所有方法,代理对象重写了代理类的所有方法,在重写的方法中用反射调用代理对象的方法。
// 反编译代理类的字节码文件的结果
public final class class extends Proxy implements Subject {
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;
// 以拦截器作为构造参数
public class(InvocationHandler var1) throws {
super(var1);
}
// 重写代理对象的方法
public final void businessA() throws {
try {
// super.h就是拦截器,这里其实就是调用了拦截器的invoke方法
// 在拦截器的invoke方法中会调用增强的代码,以及代理对象的方法
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 省略部分代码
public final void businessB() throws {...}
public final String toString() throws {...}
public final int hashCode() throws {...}
public final boolean equals(Object var1) throws {...}
// 静态代码块:反射获取代理对象的所有方法,并保存引用
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.catyee.experiment.design.proxy.Subject").getMethod("businessB");
m3 = Class.forName("com.catyee.experiment.design.proxy.Subject").getMethod("businessA");
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());
}
}
}
以下是CGLib动态代理的使用示例:
/**
* 1、被代理类(实现具体的业务逻辑)
*/
public class RealSubject {
// 业务A的具体实现逻辑
public void businessA() {
System.out.println("this is a business.");
}
// 业务B的具体实现逻辑
public void businessB() {
System.out.println("this is b business");
}
}
/**
* 2、拦截器用于功能增强
*/
public class CGSubjectInterceptor implements MethodInterceptor {
// 生成代理对象
public <T extends Subject> T getProxy(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return (T) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 执行前扩展
System.out.println("执行前扩展");
Object result = methodProxy.invokeSuper(o, objects);
// 执行后扩展
System.out.println("执行后扩展");
return result;
}
}
/**
* 3、生成代理对象,执行业务
*/
public class CGClient {
public static void main(String[] args) {
// 通过cglib提供的接口将动态生成的代理类的字节码持久化到文件
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/tmp/cglib_proxy_class");
// 生成代理对象,并执行业务
RealSubject subjectProxy = new CGSubjectInterceptor().getProxy(RealSubject.class);
subjectProxy.businessA();
subjectProxy.businessB();
}
}
可以看到CGLib是使用Enhancer来生成代理对象的,在生成之前需要调用setSupperclass方法,作用是设置代理类的父类,这个父类就是被代理类。所以CGLib的被代理类不需要实现任何接口,代理类继承被代理类然后重写被代理类中的所有方法,在方法中调用父类的原方法。
实例化Enhancer对象之后需要设置callback,callback从名字来看含义是"回调",但并不是"原方法执行完了再调用callback",而是"调用原方法的时候会调用到callback",callback全面管理了原方法和增强逻辑的执行,示例中设置的callback是MethodInterceptor,也就是方法拦截器,除了MethodInterceptor,CGLIB还定义了NoOp、LazyLoader等多种callback,具体如下:
我们同样将动态生成的代理类的字节码持久化到文件(方法见上面第46行代码),会发现生成了三个字节码文件,通过代理类的源码可以看到,代理类会重写所有从父类继承来的方法,并且还会生成一个对应的代理方法。
// 重写被代理对象的原方法
public final void businessA() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; // 拦截器
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
// 调用拦截器的intercept方法
// 在拦截器中会调用增强代码,并调用methodProxy.invokeSuper方法
// invokeSuper方法会调到上面的CGLIB$businessA$0()方法,该方法内部调到被代理对象的原方法
var10000.intercept(this, CGLIB$businessA$0$Method, CGLIB$emptyArgs, CGLIB$businessA$0$Proxy);
} else {
super.businessA();
}
}
// 生成一个新的与原方法对应的代理方法
final void CGLIB$businessA$0() {
// 调用被代理对象的原方法
super.businessA();
}
所以整个调用流程就是subjectProxy.businessA()方法(代理对象重写的方法) -> 拦截器的intercept()方法 -> methodProxy.invokeSuper()方法 -> 与原方法对应的CGLIB$businessA$0()代理方法 -> 被代理对象的businessA()方法(被代理对象的原方法)。
这里比较关键的在于MethodProxy的invokeSuper()方法,看一下源码:
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
可以看到它是先获取FastClassInfo,然后通过FastClassInfo来调用到与原方法对应的代理方法的,这个FastClassInfo是CGLib代理的关键,之前说过CGLIB动态代理生成了三个字节码文件,一个是代理类的字节码文件,另外两个一个是代理类的FastClass,一个是被代理类的FastClass,CGLib代理执行代理方法的效率之所以比JDK的高,是因为CGlib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个FastClass类,这两个类会为代理类和被代理类的方法分配一个index(int类型),这个index当作一个入参,FastClass就可以直接定位要调用的方法并直接进行调用,省去了反射调用,所以调用效率比JDK代理通过反射调用高。
以下是反编译的RealSubject的FastClass类:
public class RealSubject$$FastClassByCGLIB$$f5f16011 extends FastClass {
public RealSubject$$FastClassByCGLIB$$f5f16011(Class var1) {
super(var1);
}
// 获取方法的index
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -673380524:
if (var10000.equals("businessA()V")) {
return 0;
}
break;
case -673350733:
if (var10000.equals("businessB()V")) {
return 1;
}
break;
// 省略部分代码
...
}
return -1;
}
// 省略部分代码
...
// var1即为方法的index, var2是被代理对象, var3是参数
// 通过index直接找到对应的方法,然后执行
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
RealSubject var10000 = (RealSubject)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.businessA();
return null;
case 1:
var10000.businessB();
return null;
// 设略部分代码
...
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
// 省略部分代码
...
}
(1)JDK动态代理实现了被代理对象的接口(JDK动态代理的被代理对象必须要实现至少一个抽象接口),CGLib代理继承了被代理对象。
(2)JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,CGlib代理实现更复杂,生成代理类比JDK动态代理效率低。
(3)JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制直接调用方法的,CGLib代理的执行效率更高。