Java 动态代理

谈谈实现 Java 动态代理的两种方式


动态代理

能够叫做动态代理,必然是有一个静态代理与之对应。静态代理其实就是我们平时通过直接编码方式实现的代理。

通常步骤是建立一个 Proxy 类,内部持有个 Target 对象(被代理对象),并且 Proxy 类会实现和 Target 对象相同的接口,将对 Proxy 类的方法调用转发给 Target 对象执行,并在 Target 对象方法执行的前前后后加点增强逻辑。

使用静态代理的坏处

  1. 需要手动维护 Proxy 类和 Target 对象所在类实现接口的一致性。如果 Target 对象所在类需要添加或者减少一个接口,Proxy 也需要做相同操作。
  2. 代码重复严重。想象一下 Target 对象有一百个方法需要我们增强,在执行 Target 对象方法前打印一句话,使用静态代理需要在 Proxy 方法实现这一百个方法,并在这每个方法转发到 Target 对象前都增加这么一句打印。
  3. 哪怕静态代理被使用,字节码也要被加载。

使用动态代理则避免了静态代理的缺点。能够做到在不修改原方法的基础上对原方法进行增强,并且字节码随用随创建,随用随加载。


基于接口的动态代理

基于接口的动态代理技术的实现原理是构建一个实现了与被代理对象相同接口的类对象,并将其实例化后返回

需要用到 JDK 提供的 Proxy 类中的 newProxyInstance 方法:

1
2
3
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException;

ClassLoader :类加载器,用于加载代理对象字节码。需要和被代理对象使用相同的类加载器。换句话说,需要代理谁就写谁的类加载器

Class[] :字节码数组,为保证代理对象和被代理对象能实现相同功能(可调用相同方法)。通常传入被代理对象实现的接口

InvocationHandler :传入实现了 InvocationHandler 接口的对象。该接口只有一个方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; 需要实现。为了方便一般直接传入匿名内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void main(String[] args) {
// java 8 之前需要使用 final 修饰才能够被闭包所使用
Service service = new ServiceImpl();

Service o = (Service) Proxy.newProxyInstance(service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("before");
result = method.invoke(service, args);
System.out.println("after");
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
return result;
}
}
});

o.doSomething();
}

Object proxy :代理对象本身

Method method :被调用的方法引用

Object[] args :被调用方法的参数

至此,一个基于接口的动态代理对象 o 已经创建完成。所有对 o 对象调用的方法都会先被转发到 invoke 方法,再在 invoke 内部转发至真实的被代理对象执行,而在执行被代理对象方法的前前后后我们可以添加对应的拦截逻辑(其实也就是对应了 Spring AOP 中的几个通知类型),上述例子会出现两种情况的打印:

  1. method.invoke(service, args) 正常执行:before -> after -> finally
  2. method.invoke(service, args) 执行过程中遭到异常:before -> catch -> finally

最后提醒一下,基于接口实现的动态代理是要求被代理对象必须实现了接口,如果被代理对象没有实现接口的话,newProxyInstance 方法的第二个参数无法拿到有效信息,无法真正实现代理,并抛出代理异常。

最后提醒一下,基于接口实现的动态代理是要求被代理对象必须实现了接口,如果被代理对象没有实现接口的话,newProxyInstance 方法的第二个参数无法拿到有效信息,无法真正实现代理,并抛出代理异常。


基于子类的动态代理

如果被代理对象没有实现任何接口,仅仅是普通的 Java 类对象,该如何实现代理?这时候需要用到基于子类的动态代理技术,原理是构建一个被代理对象所在类的子类,将其实例化后返回。也正因为此,使用基于子类的动态代理技术要求被代理对象所在类不能够被 final 修饰,否则无法动态构建子类。

那么该如何代理呢?这时候依靠 JDK 提供的 Proxy 已经不能满足了,需要依靠第三方包 cglib 的支持,使用 cglib 提供的 Enhancer 类中的 create 方法:

1
public static Object create(Class type, Callback callback);

type :被代理对象的字节码

callback :传入实现了 Callback 接口的对象,但是由于 Callback 是个空接口,所以通常传入 Callback 接口的子接口 MethodInterceptor 的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void main(String[] args) {
// java 8 之前需要使用 final 修饰才能够被闭包所使用
Service service = new ServiceImpl();

Service o = (Service) Enhancer.create(service.getClass(),
new MethodInterceptor() {
@Override
public Object intercept(Object o,
Method method,
Object[] objects,
MethodProxy methodProxy) throws Throwable {
Object result = null;
try {
System.out.println("before");
result = method.invoke(service, args);
System.out.println("after");
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
return result;
}
}
});

o.doSomething();
}

Object o :代理对象本身

Method method :被调用的方法引用

Object[] objects :被调用方法的参数

MethodProxy methodProxy :生成的代理类对方法的代理引用

SQL中的关键字的执行顺序 操作系统和内核的关系

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×