Java 动态代理机制

如果有错,希望指出

动态代理在Java中有着广泛的应用,比如 Spring Aop、Hibernate 数据查询、RPC、Java 注解对象的获取。静态代理的代理关系在编译时确定,而动态代理的代理关系是在编译期确定的。主要了解:JDK Proxy 和 cglib 动态代理。

动态代理可以提供另一个对象的访问,同时可以隐藏实际对象的具体实例。

静态代理

再说动态代理之前,先了解下静态代理。

静态代理是代理模式实现方式之一,在程序运行前,由程序员创建或者特定工具自动生成源代码并对其编译生成 .class 文件。静态代理的实现需要三步:

  • 定义业务接口
  • 实现业务接口
  • 定义代理类并实现业务接口

最后就可以直接通过客户顿进行调用静态代理了。

静态代理总结:

  • 优点:可以做到不对目标对象进行修改的前提下,对目标对象进行功能的扩展和拦截。
  • 缺点:因为代理对象,需要实现和目标对象一样的接口,会导致代理对象十分繁多,不易维护,同时一旦接口增加方法,则目标对象和代理类都需要维护。

JDK Proxy

先实现一个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Hello {
void sayHello();
void sayJava();
}
public class HelloImpl implements Hello {

@Override
public void sayHello() {
System.out.println("Hello Java!");
}
@Override
public void sayJava() {
System.out.println("Hello Java");
}
}

下面使用java.lang.reflect.Proxy 实现动态代理

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
28
29
30
31
32
33
34
35
public class MyInvocationHandler implements InvocationHandler {

private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}

/**
* @param proxy 动态代理类的引用,通常情况下不需要它
* @param method 方法对象的引用,代表被动态代理类调用的方法
* @param args args对象数组,代表被调用方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("正在执行" + method.getName() + "方法");
Object result = method.invoke(target, args);
return result;
}
}

public class MyDynamicProxy {

public static void main(String[] args) {

HelloImpl hello = new HelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
//构造代码实例 Proxy.newProxyInstance(类加载器, 需要实现的接口数组,InvocationHandler接口)
Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(),
HelloImpl.class.getInterfaces(),
handler);
//调用代理
proxyHello.sayHello();
proxyHello.sayJava();
}
}

newProxyInstance()会返回一个实现了指定接口的代理对象,对该对象的所有方法调用都会转发给InvocationHandler.invoke()方法。理解上述代码需要对Java反射机制有一定了解。动态代理神奇的地方就是:

  • 代理对象是在程序运行时产生的,而不是编译期;
  • 对代理对象的所有接口方法调用都会转发到InvocationHandler.invoke()方法,在invoke()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;之后我们通过某种方式执行真正的方法体,示例中通过反射调用了Hello对象的相应方法,还可以通过RPC调用远程方法。

cglib

GCLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许在运行时对字节码进行修改和动态生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//实现MethodInterceptor方法
public class MyMethodInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("执行GCLIB动态代理");
return methodProxy.invokeSuper(o, objects);
}

}

public class CGlibTest {
public static void main(String[] args) {
//通过 CGlib 动态获取代理对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
enhancer.setCallback(new MyMethodInterceptor());
Hello helloProxy = (Hello) enhancer.create();
helloProxy.sayHello();
helloProxy.sayJava();
}
}

总结

JDK Proxy

  • JDK动态代理只能代理有接口的类,并且是只能代理接口方法,不能代理一般的类中的方法
  • 提供了一个使用 InvocationHandler 作为参数的构造方法,在代理类中做一层包装,业务逻辑在 invoke 方法中实现;
  • 重写了 Object 类的 equals、hashCode、toString,它们都只是简单的调用了InvocationHandler 的 invoke 方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法;
  • 在 invoke 方法中我们甚至可以不用 Method.invoke 方法调用实现类就返回。这种方式常常用在 RPC 框架中,在 invoke 方法中发起通信调用远端的接口等。

CGlib

  • CGlib 可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类;
  • 由于是继承方式,如果是 static方法、private方法、final方法等描述的方法是不能被代理的;
  • 做了方法访问优化,使用建立方法索引的方式避免了传统 Method 的方法反射调用;
  • 提供 callback 和 filter 设计,可以灵活地给不同的方法绑定不同的 callback。编码更方便灵活。
  • CGLIB 会默认代理 Object 中 finalize、equals、toString、hashCode、clone等方法。比JDK代理多了finalize和clone。

参考

博主 wechat
钟意作者
客官,赏一杯coffee嘛~~~~
0%