Java中的动态代理

JDK 1.3以后,Java提供了动态代理的技术,允许开发者在运行期创建接口的代理实例。在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在我们终于发现动态代理是实现AOP的绝好底层技术。

JDK的动态代理主要涉及到java.lang.reflect包中的两个类:

  • Proxy
  • InvocationHandler。

其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。

一、JDK的动态代理

使用JDK创建代理有一个限制,即它只能为接口创建代理实例。下面写个小例子来验证一下。

1、被代理的接口和实现类

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
package hello.proxy;

interface Interface{
void doSomthing();
void doSomthingElse(String s);
}

class RealObject implements Interface{

@Override
public void doSomthing() {
System.out.println("something");
}

@Override
public void doSomthingElse(String s) {
System.out.println("donthingElse: "+s);
}
}


public class ProxyDemo {
static void consumer(Interface iface){
iface.doSomthing();
iface.doSomthingElse("lalala");
}

public static void main(String[] args) {
Interface real=new RealObject();
consumer(real);
}

输出:

1
2
something
donthingElse: lalala

这是一个很简单的逻辑,如果需要在这个业务逻辑上面增加其他的横切逻辑,并且不需要在业务逻辑中增加代码,你们就需要用到动态代理。

2、Proxy与InvocationHandler

改动代码如下

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;


//被代理的接口
interface Interface{
void doSomthing();
void doSomthingElse(String s);
}

//接口的实现类
class RealObject implements Interface{

@Override
public void doSomthing() {
System.out.println("something");
}

@Override
public void doSomthingElse(String s) {
System.out.println("donthingElse: "+s);
}
}

//InvocationHandler接口的实现
class DynamicProxyHandler implements InvocationHandler{
Object proxied;

public DynamicProxyHandler(Object proxied){
this.proxied=proxied;

}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("****proxy: "+proxy.getClass()+
", method: "+method+
", args: "+Arrays.toString(args));
return method.invoke(proxied, args);
}
}


public class ProxyDemo {
static void consumer(Interface iface){
iface.doSomthing();
iface.doSomthingElse("lalala");
}

public static void main(String[] args) {
Interface real=new RealObject();
consumer(real);
Interface proxy=(Interface) Proxy.newProxyInstance(real.getClass().getClassLoader(), real.getClass().getInterfaces(), new DynamicProxyHandler(real));
consumer(proxy);
}
}

输出:

1
2
3
4
5
6
something
donthingElse: lalala
****proxy: class hello.proxy.$Proxy0, method: public abstract void hello.proxy.Interface.doSomthing(), args: null
something
****proxy: class hello.proxy.$Proxy0, method: public abstract void hello.proxy.Interface.doSomthingElse(java.lang.String), args: [lalala]
donthingElse: lalala

可以发现,在不改动原来代码的基础上实现了横切逻辑的实现。涉及的两个关键的类就是Proxy与InvocationHandler。

通过Proxy.newProxyInstance()方法可以创建动态代理,这个方法需要三个参数:

  • 类加载器:通过可以从已经被加载的对象中获取其类加载器并传递给它
  • 希望该代理实现的接口列表(只能是接口,不能是类或抽象类)
  • InvocationHandler接口的一个实现

动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以请求转发。

调用处理器就是InvocationHandler接口,该接口定义了一个 invoke(Object proxy, Method method, Object[] args)的方法:

  • proxy是最终生成的代理实例,一般不会用到;
  • method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用;
  • args是通过被代理实例某一个方法的入参,在方法反射调用时使用。

二、CGLib

1、CGLib

JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class DoSomethingImp implements Interface{
@Override
public void doSomthing() {
System.out.println("something");
}

@Override
public void doSomthingElse(String s) {
System.out.println("donthingElse: "+s);
}
}

class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz); //① 设置需要创建子类的类
enhancer.setCallback(this);
return enhancer.create(); //②通过字节码技术动态创建子类实例
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

System.out.println("开始");
proxy.invokeSuper(obj, args);
System.out.println("结束");
return null;
}
}

public class CGLibDemo {
static void consumer(Interface iface){
iface.doSomthing();
iface.doSomthingElse("lalala");
}

public static void main(String[] args) {
Interface real=new DoSomethingImp();
consumer(real);
CglibProxy cglib=new CglibProxy();
DoSomethingImp bookCglib=(DoSomethingImp)cglib.getProxy(DoSomethingImp.class);
consumer(bookCglib);
}
}

输出:

1
2
3
4
5
6
7
8
something
donthingElse: lalala
开始
something
结束
开始
donthingElse: lalala
结束

有一点需要注意的就是CGLib需要额外的导入jar包,第一次导入cglib-2.1.3.jar后发现运行有异常,解决办法就是下载并导入cglib-nodep-2.1_3.jar然后删除cglib-2.1.3.jar,这样就可以正确运行了。

2、JDK与CGLib

JDK动态代理所创建的代理对象,在JDK 1.3下,性能强差人意。虽然在高版本的JDK中,动态代理对象的性能得到了很大的提高,但是有研究表明,CGLib所创建的动态代理对象的性能依旧比JDK的所创建的代理对象的性能高不少(大概10倍)。但CGLib在创建代理对象时所花费的时间却比JDK动态代理多(大概8倍),所以对于singleton的代理对象或者具有实例池的代理,因为无须频繁创建代理对象,所以比较适合用CGLib动态代理技术,反之适合用JDK动态代理技术。

值得一提的是,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final方法进行代理。

三、参考地址

http://www.iteye.com/topic/1123293