目录
  1. 1. 简介
  2. 2. 静态代理
    1. 2.1. 代理模式
  3. 3. 动态代理
    1. 3.1. Java SDK动态代理
      1. 3.1.1. 用法
      2. 3.1.2. 基本原理
      3. 3.1.3. 动态代理优点
    2. 3.2. cglib 动态代理
      1. 3.2.1. cglib 如何使用
      2. 3.2.2. cglib 实现机制
    3. 3.3. Java SDK代理与cglib代理比较
  4. 4. 动态代理的应用-AOP
    1. 4.1. 简单AOP框架实现
    2. 4.2. 小结
重拾Android-Java进阶之动态代理

本篇,我们来探讨Java中另外一个动态特性:动态代理。动态代理是一种强大的功能,它可以在运行时动态创建一个类,实现一个或多个接口,可以在不修改原有类的基础上动态为通过该类获取的对象添加方法、修改行为,接下来详细介绍。

简介

动态代理是实现面向切面的编程 AOP (Aspect Oriented Programming)的基础。切面的例子有很多,比如:日志监控、性能监控、权限检查、数据库事务等等。它们在程序的很多地方都会用到,代码编写也差不多。如果在每个用到的地方都写,代码会很冗余,也难以维护,AOP讲这些切面与主体逻辑相分离,代码简单优雅很多。

要理解动态代理,首先要理解静态代理,了解静态代理之后,就可以很好理解动态代理。动态代理有两种实现方式:一种是Java SDK提供的;另一种是第三方库(如cglib)提供的。

静态代理

代理模式

作为软件设计模式,其基本概念和日常生活中的概念是类似的。代理背后一般至少有一个concrete对象,代理的外部功能和实际对象一般都是一样的,用户与代理打交道,不直接接触实际对象。虽然外部功能和实际对象一直,但代理有其存在的价值:

  • 1)节省成本比较高的实际对象的创建开销,按需延迟加载,创建代理时并不会真正创建实际对象,而只是保存实际对象的地址,在需要时再加载或创建。

  • 2)执行权限检查,代理检查权限后,再调用实际对象

  • 3)屏蔽网络差异和复杂性,代理在本地,而实际对象在其他服务器上,调用本地代理时,本地大力请求其他服务器。

静态代理代码示例

public class SampleStaticProxy {

interface IService {
void sayHello();
}

static class ConcreteService implements IService {

@Override
public void sayHello() {
System.out.println("hello");
}
}

static class ProxyService implements IService {

private IService mService;

public ProxyService(IService service) {
mService = service;
}

@Override
public void sayHello() {
System.out.println("entering method");
mService.sayHello();
System.out.println("leaving method");
}
}

public static void main(String[] args) {
IService concreteService = new ConcreteService();
ProxyService proxyService = new ProxyService(concreteService);
proxyService.sayHello();
}
}

分析一下,代理和实际对象一般有相同的接口,在这个demo中,共同的接口是IService,实际对象是 ConcreteService,代理是 ProxyServiceProxyService 内部有一个 IService 的成员变量,指向实际对象,在构造方法中被初始化,对于方法 sayHello 的调用,它转发给了实际对象,在调用前后输出了一些跟踪调试信息,应该很好理解。上述例子是固定的所以称为静态代理。

输出跟踪调试信息可能是一个通用的需求,可以想象,如果每个类都需要,而又不希望修改类定义,我们需要为每个类创建代理,实现所有接口,那这个工作就太繁琐了,如果再有其他的切面需求,整个工作可能又要重新再来。这时,就需要动态代理了,接下来介绍两种实现方式:Java SDK 和 cglib。

动态代理

Java SDK动态代理

用法

在静态代理中,代理类是直接定义在代码中的,在动态代理中,代理类是动态生成的,那么怎么动态生成呢?先看示例代码:

public class SampleDynamicProxy {

interface IService {
void sayHello();
}

static class ConcreteService implements IService {

@Override
public void sayHello() {
System.out.println("hello");
}
}

static class SampleInvocationHandler implements InvocationHandler {

private Object mRealObj;

public SampleInvocationHandler(Object obj) {
mRealObj = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("entering method: " + method.getName());
Object result = method.invoke(mRealObj, args);
System.out.println("leaving method: " + method.getName());
return result;
}
}

public static void main(String[] args) {
IService concreteService = new ConcreteService();
IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(),
new Class<?>[]{IService.class}, new SampleInvocationHandler(concreteService));
proxyService.sayHello();
}
}

分析一下,IService 和 ConcreteService 依旧没变,程序输出也没变化,但是代理对象 proxyService 的创建方式改变了。其使用 java.lang.reflect包中的Proxy类的静态方法 newProxyInstance 来创建代理对象,这个方法的声明如下:

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

有三个参数,具体如下:

  • 1)loader表示类加载器,实现类加载功能

  • 2)interfaces表示代理类要实现的接口列表,是一个数组,元素的类型只能是接口,不能是普通的类

  • 3)h的类型是InvocationHandler,它是一个接口,也定义在java.lang.reflect包中,它只定义了一个方法invoke,对代理接口所有方法的调用都会转给该方法。

newProxyInstance 的返回值类型为 Object,可以强制转换为 interfaces 数组中的某个接口类型。但是记住,它不能强制转换为某个类类型,比如这里的 ConcreteService,即使它实际代理的对象类型确实是 ConcreteService

SampleInvocationHandler 实现了 InvocationHandler,它的构造方法接受一个参数 obj 表示被代理的对象,invoke方法处理所有的接口调用,它有三个参数:

  • 1)proxy表示代理对象本身,需要注意,它不是被代理的对象,这个参数一般用处不大。

  • 2)method表示正在被调用的方法。

  • 3)args表示方法的参数。

SampleInvocationHandlerinvoke 实现中,我们调用了 methodinvoke方法,传递了实际对象 mRealObj 作为参数,达到了调用实际对象对应方法的目的,在调用任何方法前后,输出跟踪调试语句。需要注意,不能将proxy作为参数传递给method.invoke,比如:

Object result = method.invoke(proxy, args);

上面的语句会出现死循环,因为proxy表示当前代理对象,这又会调用到 SampleInvocationHandler 的 invoke 方法。

接下来,我们看一下这里面的基本原理

基本原理

Proxy.newProxyInstance 在了解其内部源码实现之前,我们先用一段代码替代之前的创建 ProxyService 的代码。

Class<?> proxyClass = Proxy.getProxyClass(IService.class.getClassLoader(), new Class<?>[]{ISearchView.class});
try {
Constructor<?> constructor = proxyClass.getConstructor(new Class<?>[]{InvocationHandler.class});
SampleInvocationHandler handler = new SampleInvocationHandler(concreteService);
IService proxyService = (IService) constructor.newInstance(handler);
proxyService.sayHello();

// 该方法用于获取JDK动态代理生成类$Proxy0的内容
saveProxy0();
} catch (Exception e) {
e.printStackTrace();
}

分为三步:

  • 1)通过Proxy.getProxyClass创建代理类定义,类定义会被缓存;

  • 2)获取代理类的构造方法,构造方法有一个 InvocationHandler 类型的参数;

  • 3)创建 InvocationHandler 对象,创建代理对象。

Proxy.getProxyClass 需要两个参数:一个是ClassLoader;另一个是接口数组。它会动态生成一个类,类名以 $Proxy 开头,后跟一个数字。对于上述示例,动态生成的类定义如下所示,为简化起见,忽略异常处理代码:

【注】这里需要手动去获取JDK动态代理生成类$Proxy0的内容

private static void saveProxy0() throws IOException {
byte[] bytes = ProxyGenerator
.generateProxyClass("$Proxy0", new Class<?>[]{IService.class});

String pathDir = "C:\\Users\\SZSS\\Desktop\\out";
String path = "\\$Proxy0.class";
File f = new File(pathDir);
if (!f.exists()) {
f.mkdir();
}
path = f.getAbsolutePath() + path;
f = new File(path);
if (f.exists()) {
f.delete();
}
f.createNewFile();

try (FileOutputStream fos = new FileOutputStream(path)) {
fos.write(bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
}

Java SDK动态生成的代理类示例 $Proxy0.class

public final class $Proxy0 extends Proxy implements IService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;

public $Proxy0(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 sayHello() 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("com.tufusi.lib.SampleDynamicProxy$IService").getMethod("sayHello");
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());
}
}
}

$Proxy0 的父类是 Proxy,它有一个构造方法,接受一个 InvocationHandler 类型的参数,保存为实例变量 h,h定义在父类Proxy中,它实现了 IService 接口,对于每个方法,如 sayHello,它调用 InvocationHandlerinvoke 方法,对于 Object 中的方法,如 equalstoStringhashCode$Proxy0同样转发给了 InvocationHandler

可以看出,这个类定义本身与被代理的对象并没有关系,与 InvocationHandler 的具体实现也没有关系,而主要与接口数组有关,给定这个接口数组,它动态创建每个接口的实现代码,实现就是转发给 InvocationHandler,与被代理对象的关系以及对它的调用由 InvocationHandler 的实现管理。

动态代理优点

相比于静态代理,动态代理看起来复杂很多,但是也有很大优点。使用动态代理,可以编写通用的代理逻辑,用于各种类型的被代理对象,而不需要为每个被代理的类型都创建一个静态代理类。

通用的动态代理类方法模板

private static <T> T getProxy(Class<T> interfaces, T realObj) {
return (T) Proxy.newProxyInstance(interfaces.getClassLoader(),
new Class[]{interfaces},
new SampleInvocationHandler(realObj));
}

cglib 动态代理

JDK动态代理的局限在于,只能为接口创建代理,返回的代理对象也只能转换到某个接口类型。如果一个类没有接口,或者希望代理非接口中定义的方法,那就只能另寻他法。有一个第三方类库cglib,可以做到这一点,并且Spring、Hibernate等都使用该类库。但是但是但是,一个很致命的缺点是:cglib底层采用的是ASM字节码生成框架,使用字节码技术生成代理类,即生成.class文件,而我们在Android中加载的是优化后的.dex文件,也就是说我们需要可以动态生成.dex文件的代理类,因此cglib在Android中是无法使用的。

cglib 如何使用

下面先看个简单的示例,jar包下载地址cglib-3.3.0.jar,这里还会出错,因为会用到ASM,因此还需gradle集成 implementation group: ‘org.ow2.asm’, name: ‘asm’, version: ‘9.0’

public class SampleCglib {

static class ConcreteService {
public void sayHello() {
System.out.println("hello");
}
}

static class SampleInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("entering method: " + method.getName());
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("leaving method: " + method.getName());
return result;
}
}

private static <T> T getProxy(Class<T> cls) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cls);
enhancer.setCallback(new SampleInterceptor());
return (T) enhancer.create();
}

public static void main(String[] args) {
ConcreteService proxyService = getProxy(ConcreteService.class);
proxyService.sayHello();
}
}

这里,ConcreteService是被代理的类,它没有接口。getProxy()为一个雷生成代理对象,这个代理对象可以安全地转换为被代理的类型,它使用了cglib的Enhancer类。

Enhancer类的 setSuperclass 设置被代理的类, setCallback 设置被代理类的public非final方法被调用时的处理类(即示例的SampleInterceptor)。Enhancer支持多种类型,这里使用的类实现了 MethodInterceptor接口,它与 JDK 中 InvocationHandler 类似,方法名变成了 intercept,并且多了个 MethodProxy 类型的参数。

但是与 InvocationHandler 不同的是,SampleInterceptor 中没有被代理的对象,它通过 MethodProxyinvokeSuper 方法调用被代理类的方法:

Object result = methodProxy.invokeSuper(o, objects);

但是注意,不能这样调用被代理类的方法:

Object result = method.invoke(o, objects);

因为,o 是代理对象,调用这个方法还是会调用 SampleInterceptorintercept 方法,造成死循环。在main方法里,我们并没有创建被代理的对象,创建的对象直接就是代理对象。

【注】如果想保存cglib生成的class文件,可以加入如下代码:

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/SZSS/Desktop/cglib/JavaHelper");

请记住,这段代码一定要设置在获取代理类前面,否则会失效。

cglib 实现机制

cglib的实现机制和JDK不同,它是通过继承实现的,同时也是动态创建一个类,但这个类的父类是被代理的类,代理类重写了父类的所有public 非final方法,改为调用Callback中相关方法,在上例中,调用 SampleInterceptorintercept方法。

当然,可以通过jd-gui查看字节码文件,了解其实现原理,这个等后面字节码专栏写完,回来继续深挖这一块。立个flag吧!

Java SDK代理与cglib代理比较

  • 从面相对象的角度看

JDK代理面向的是一组接口,它为这些接口动态创建了一个实现类。接口的具体实现逻辑是通过自定义的InvocationHandler实现,这个实现是自定义的,也就是说,背后是不一定有真正被代理的对象,也可能是多个实际对象,根据情况动态选择。而cglib代理面向的是一个具体的类,它动态创建了一个新类,继承了该类,通过代理方法重写了其方法。

  • 从代理的角度看

JDK代理的是对象,需要先有一个实际对象,自定义的 InvocationHandler 引用该对象,然后创建一个代理类和代理对象,客户端访问的是代理对象,代理对象最后再调用实际对象的方法;
cglib代理的是,创建的对象只有一个。

如果目的都是为了一个类的方法增强功能,JDK要求该类必须要有接口,且只能处理接口中的方法,但是cglib则没有这个限制。

动态代理的应用-AOP

利用cglib动态代理,我们可以实现一个极简的AOP框架,演示AOP的基本思路和技术原理。首先先简单的过一遍简单AOP框架的用法,然后分析其实现原理。熟悉AspectJ的可以通过这里知其所以然了。

简单AOP框架实现

定义一个新注解 @Aspect,该注解用于注解切面类,指定一个参数,该参数用于标记要增强的类。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
Class<?>[] value();
}

接下来约定,切面类可以声明三个方法 before/after/exception,分别表示在主体类的方法调用前调用后出现异常时调用这三个方法。

定义两个切面类,一个服务日志切面类,一个是异常切面类

ServiceLogAspect.java

@Aspect({ServiceA.class, ServiceB.class})
public class ServiceLogAspect {

public static void before(Object object, Method method, Object[] args) {
System.out.println("entering " + method.getDeclaringClass().getSimpleName()
+ "::" + method.getName() + ", args: " + Arrays.toString(args));
}

public static void after(Object object, Method method, Object[] args, Object result) {
System.out.println("leaving " + method.getDeclaringClass().getSimpleName()
+ "::" + method.getName() + ", result: " + result);
}
}

ExceptionAspect.java

@Aspect(ServiceB.class)
public class ExceptionAspect {

public static void exception(Object object, Method method, Object[] args, Throwable throwable) {
System.out.println("exception when calling: " + method.getName() + ", " + Arrays.toString(args));
}
}

ServiceLogAspect目的是在类ServiceA和serviceB所有方法的执行前后加一些日志,而ExceptionAspect的目的是在类ServiceB的方法执行出现异常时收到通知,并输出一些信息。

它们都没有修改类本身,并且类本身做的事就是比较通用和业务化的,与ServiceA和ServiceB的具体逻辑关系也并不密切,但又想改变 ServiceA 和 ServiceB 的行为,那么这就是AOP的思维。

切面定义好之后,需要实现一个容器类,用来获取被切面注解的类并处理。

再定义一个新注解类,用来实现DI

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject {
}

ServiceA.java

public class ServiceA {

@Inject
ServiceB b;

public void callB(){
b.action();
}
}

ServiceB.java

public class ServiceB {

public void action(){
System.out.println("this is classB! ");
}
}

枚举类 InterceptPoint.java

public enum InterceptPoint {
BEFORE, AFTER, EXCEPTION
}

容器处理类如下

public class CglibContainer {

static Map<Class<?>, Map<InterceptPoint, List<Method>>> interceptMethodsMap = new HashMap<>();
static Class<?>[] aspects = new Class[]{
ServiceLogAspect.class,
ExceptionAspect.class
};

static {
init();
}

private static void init() {
for (Class<?> cls : aspects) {
Aspect aspect = cls.getAnnotation(Aspect.class);
if (aspect != null) {
try {
Method before = getMethod(cls, "before", new Class<?>[]{Object.class, Method.class, Object[].class});
Method after = getMethod(cls, "after", new Class<?>[]{Object.class, Method.class, Object[].class, Object.class});
Method exception = getMethod(cls, "exception", new Class<?>[]{Object.class, Method.class, Object[].class, Throwable.class});

Class<?>[] interceptedArr = aspect.value();
for (Class<?> intercepted : interceptedArr) {
addInterceptMethod(intercepted, InterceptPoint.BEFORE, before);
addInterceptMethod(intercepted, InterceptPoint.AFTER, after);
addInterceptMethod(intercepted, InterceptPoint.EXCEPTION, exception);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}

private static void addInterceptMethod(Class<?> cls, InterceptPoint point, Method method) {
if (method == null) {
return;
}

Map<InterceptPoint, List<Method>> map = interceptMethodsMap.get(cls);
if (map == null) {
map = new HashMap<>();
interceptMethodsMap.put(cls, map);
}
List<Method> methods = map.get(point);
if (methods == null || methods.size() == 0) {
methods = new ArrayList<>();
map.put(point, methods);
}
methods.add(method);
}

public static <T> T getInstance(Class<T> cls) {
try {
T obj = createInstance(cls);
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
if (f.isAnnotationPresent(Inject.class)) {
if (!f.isAccessible()) {
f.setAccessible(true);
}
Class<?> fieldCls = f.getType();
f.set(obj, getInstance(fieldCls));
}
}
return obj;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}

private static <T> T createInstance(Class<T> cls)
throws InstantiationException, IllegalAccessException {

if (!interceptMethodsMap.containsKey(cls)) {
return (T) cls.newInstance();
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cls);
enhancer.setCallback(new AspectInterceptor());
return (T) enhancer.create();
}

static class AspectInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
// 执行before方法
List<Method> beforeMethods = getInterceptMethods(o.getClass().getSuperclass(), InterceptPoint.BEFORE);
for (Method m : beforeMethods) {
m.invoke(null, new Object[]{o, method, objects});
}
try {
// 调用原始方法
Object result = methodProxy.invokeSuper(o, objects);
// 执行after方法
List<Method> afterMethods = getInterceptMethods(o.getClass().getSuperclass(), InterceptPoint.AFTER);
for (Method m : afterMethods) {
m.invoke(null, new Object[]{o, method, objects, result});
}

return result;
} catch (Throwable e) {
// 执行exception方法
List<Method> exceptionMethods = getInterceptMethods(o.getClass().getSuperclass(), InterceptPoint.EXCEPTION);
for (Method m : exceptionMethods) {
m.invoke(null, new Object[]{o, method, objects, e});
}
throw e;
}
}
}

static List<Method> getInterceptMethods(Class<?> cls, InterceptPoint point) {
Map<InterceptPoint, List<Method>> map = interceptMethodsMap.get(cls);
if (map == null) {
return Collections.emptyList();
}
List<Method> methods = map.get(point);
if (methods == null) {
return Collections.emptyList();
}
return methods;
}
}

小结

相比完整AOP框架,这个是非常粗粒度的实现的,还有很多细节需要打磨,这里主要用于解释动态代理的应用以及AOP的一些基本思路和原理。

打赏
  • 微信
  • 支付宝

评论