Spring框架可以说是目前在JAVA平台使用最为广泛的框架之一,我甚至认为可以去掉之一。他最重要的两大基本功能是提供IoC容器和AOP概念的实现。之前有过一篇文章介绍过spring的AOP,请参考http://blog.csdn.net/zsh2050/article/details/44523073。spring的AOP是通过动态代理的方式实现的,在企业面试时,面试官也经常爱提问动态代理如何实现。我们就讲一讲动态代理。
讲动态代理之前,先简单讲一下静态代理。在静态代理的实现中,代理对象与被代理对象必须实现同一个接口。调用程序执行的是代理对象,构造代理对象是必须个给它一个被代理对象,在调用程序中,必须用共同实现的接口定义调用对象,将代理对象转换成共同接口对象。这种静态代理的方式,一个接口只服务于一种类型的对象,如果要代理的对象很多,程序规模较大,就会需要编写很多代理类,显然是不能胜任的。好在还有动态代理的实现方式。实现动态代理主要有两种方式,一是使用JDK原生API提供的方式,二是使用cglib字节码工程的方式。
-
JDK原生API方式
在JDK1.3之后加入了可协助开发动态代理功能的API,使用动态代理,不必为特定的对象和方法编写特定的代理对象,一个处理者(Handler)可服务于多个对象。首先,这个处理者的类必须实现java.lang.reflect.InvocationHandler接口。
-
以下面的HelloSpeaker类为例,要使用JDK原生API方式生成动态代理,必须先定义所要代理的接口。
Public interfaceIHello {
Public void hello(String name);
}
Public classHelloSpeaker implements IHello {
Public void hello(String name) {
System.out.println(“Hello, ” + name);
}
}
如果要在方法hello()的前后打印日志,可以设计一个动态代理类LogHandler。
publicclass LogHandler implements InvocationHandler {
privateLogger logger = Logger.getLogger(this.getClass().getName());
privateObject delegate;
publicObject bind(Object delegate) {
this.delegate =delegate;
returnProxy.newProxyInstance(delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(),this);
}
publicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
logger.info("methodstart..." + method);
//执行业务逻辑
result = method.invoke(delegate, args);
logger.info("methodend..." + method);
} catch (Exception e){
logger.info(e.getMessage());
}
return result;
}
}
以上的动态代理代码中,最重要的概念是使用Proxy.newProxyInstance()建立一个代理对象,建立代理时必须告知要代理的接口。在每次操作代理对象时,执行 InvocationHandler 的 invoke() 方法, invoke() 方法会传入被代理对象的方法名称与执行参数,实际上要执行业务逻辑的方法会交由method.invoke() 。method.invoke()传回的对象就是实际方法执行的结果。这样LogHandler不必服务于特定对象和接口,HelloSpeaker也不必插入任何有关日志的代码。
测试执行代码如下:
public static voidmain(String[] args) {
LogHandler logHandler = new LogHandler();
IHello hello = (IHello)logHandler.bind(new HelloSpeaker());
hello.sayHello("cat");
}
-
Cglib字节码工程的方式
使用JDK动态代理有一个前提,就是被代理对象必须实现一个接口,如果没有实现任何接口的类,可以使用Cglib生成动态代理。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。下面还以前面的HelloSpeaker类为例。
publicclass LogHandler implements MethodInterceptor{
privateLogger logger = Logger.getLogger(this.getClass().getName());
privateObject delegate;
public Object bind(Objectdelegate) {
this.delegate =delegate;
Enhancerenhancer = new Enhancer();
//设置需要创建子类的类
enhancer.setSuperclass(this.delegate.getClass());//非final方法
//设置回调对象,该对象必须实现
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Methodmethod, Object[] args,
MethodProxy proxy) throws Throwable {
Object result = null;
try {
logger.info("methodstart..." + method);
//执行业务逻辑
result = proxy.invokeSuper(delegate, args);
logger.info("methodend..." + method);
} catch (Exception e){
logger.info(e.getMessage());
}
return result;
}
}
以上代码用cglib的方式再次实现了LogHandler,除少数地方调用的API不同,其它基本相同。注意enhancer.setSuperclass(this.delegate.getClass()),cglib是使用动态生成子类的方式来实现动态代理的,并覆盖其中方法实现增强,但因为采用的是继承, 所以只能对父类的非final方法进行代理,当然也不能对final修饰的类进行代理。
与CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
Spring就是通过这两种方式的动态代理实现AOP机制的,默认情况下,如果该类实现了接口,spring就用jdk动态代理,如果没有实现接口,spring就使用cglib。当然如果某类实现了接口,仍然可以通过配置,要求spring使用cglib。除了这两种,当然还有其它方式实现动态代理,比如javasist等,dubbo就是使了javasist。
早就想写一篇关于动态代理的文章,一直拖到现在,做事情还是要该做就做,毕竟时间这东西我们都不富裕。好吧2017年9月写了。