静态代理
静态代理相对来说比较简单,无非就是聚合+多态:
参考:设计模式笔记 – Proxy 代理模式 (Design Pattern)
动态代理
我们知道,通过使用代理,可以在被代理的类的方法的前后添加一些处理方法,这样就达到了类似AOP的效果。而JDK中提供的动态代理,就是实现AOP的绝好底层技术。
JDK动态代理
JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
CGLib动态代理
还有一个叫CGLib的动态代理,CGLib全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。
例子:
首先是两个测试类:
interface Animal{
void sound();
}
class Dog implements Animal{
public void sound(){
System.out.println(“wong~”);
}
}
接下来演示CGLib的使用:
public 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("begin...");
// 通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("end...");
return null;
}
public static void main(String[] args){
CglibProxy proxy = new CglibProxy();
Animal dog = (Animal)proxy.getProxy(Dog.class);
dog.sound();
}
}
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。
JDK动态代理和CGLib的比较
CGLib所创建的动态代理对象的性能比JDK所创建的代理对象性能高不少,大概10倍,但CGLib在创建代理对象时所花费的时间却比JDK动态代理多大概8倍,所以对于singleton的代理对象或者具有实例池的代理,因为无需频繁的创建新的实例,所以比较适合CGLib动态代理技术,反之则适用于JDK动态代理技术。另外,由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final,private等方法进行处理。所以,大家需要根据实际的情况选择使用什么样的代理了。
同样的,Spring的AOP编程中相关的ProxyFactory代理工厂内部就是使用JDK动态代理或CGLib动态代理的,通过动态代理,将增强(advice)应用到目标类中。
Spring中的动态代理的使用
Spring定义了org.springframework.aop.framework.AopProxy接口,并提供了如下两种final类型的实现类:
关于Spring中选择代理类型的判断
如果通过ProxyFactory的setInterfaces(Class[] interfaces)指定针对接口进行代理,ProxyFactory就使用JdkDynamicAopProxy,如果是针对类的代理,则使用Cglib2AopProxy。另外,如果使用ProxyFactory的setOptimize(true)方法,则启动了优化代理方式,这样针对接口的代理也会使用Cglib2AopProxy。
在引介增强中就需要强制指定为Cglib2AopProxy,因为引介增强是一种比较特殊的增强类型,不是在目标周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点事类级别的。而非方法级别的。
JDK动态代理原理浅析
下面来探析下JDK动态代理的原理,看看为什么JDK动态代理的性能会比较低。
动态代理的基本原理为反射 + 多态 + 聚合
首先创建一个接口,和一个需要被代理的类:
interface Animal{
void sound();
}
class Dog implements Animal{
public void sound(){
System.out.println(“wong~”);
}
}
接下来是创建一个MyInvocationHandler,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起:
public interface MyInvocationHandler {
public void invoke(Method m,Object… args) throws Exception;
}
接下来是创建一个MyInvocationHandler的实现类:
package com.itzhai.javanote.proxy;
import java.lang.reflect.Method;
public class AnimalHandler implements MyInvocationHandler {
private Object animal;
public AnimalHandler (Object animal){
this.animal = animal;
}
public void invoke(Method m,Object[] args) throws Exception{
System.out.println("before");
m.invoke(animal, args);
System.out.println("after");
}
}
接下里是创建代理类Proxy,里面包含一个newProxy方法,该方法生成需要代理的接口相关的所有方法,通过MyInvocationHandler的invoke方法,调用实际的对象,通过反射机制执行实际的方法,并且需要嵌入的代码也就在invoke方法里面被执行到了:
下面是使用我们创建的动态代理的代码:
package com.itzhai.javanote.proxy;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class MyProxy {
public static Object newProxyInstance(Class interfaces,MyInvocationHandler handler) throws Exception{
String r = "\\n";
Method[] methods = interfaces.getMethods();
StringBuffer sb = new StringBuffer("");
// 生成需要代理的接口中的所有方法 的代码,通过调用MyInvocationHandler的invoke方法实现真实对象方法的调用
for(int i =0;i<methods.length;i++){
sb.append(" public void "+methods[i].getName()+"() {"+ r +
" try{ "+ r +
" Method md = "+interfaces.getName()+".class.getMethod(\\""+methods[i].getName()+"\\");"+ r +
" handler.invoke( md,new Object[]{});"+ r +
" }catch(Exception e){e.printStackTrace();}"+ r +
" }"+r + r
);
}
// 生成完整的类代码
String src = "package com.itzhai.javanote.proxy;"+ r + r +
"import java.lang.reflect.*;"+ r + r +
"public class Proxy$1 implements "+interfaces.getName() +"{"+ r +
" private com.itzhai.javanote.proxy.MyInvocationHandler handler;"+ r +
" public Proxy$1("+handler.getClass().getName() +" handler){"+ r +
" this.handler = handler;"+ r +
" }"\+ r + r +
sb.toString() +
"}" +r;
// 输出Java文件
String dir = System.getProperty("user.dir")+"/src/com/itzhai/javanote/proxy/";
FileWriter writer = new FileWriter(new File(dir+"Proxy$1.java"));
writer.write(src);
writer.flush();
writer.close();
// 编译动态代理类
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> units = fileMgr.getJavaFileObjects(dir+"Proxy$1.java");
Iterable<String> options = Arrays.asList("-d", System.getProperty("user.dir") + "/bin/");
CompilationTask t = compiler.getTask(null, fileMgr, null, options, null, units);
t.call();
fileMgr.close();
// 加载动态代理类,并返回动态代理类的实例
URL[] urls = new URL[]{new URL("file:/"+dir)};
URLClassLoader loader = new URLClassLoader(urls);
Class c = loader.loadClass("com.itzhai.javanote.proxy.Proxy$1");
Constructor ctr = c.getConstructor(handler.getClass());
return ctr.newInstance(handler);
}
}
分析可以得出,当我们通过MyProxy的newProxyInstance()方法返回代理对象时,实际上是通过传入的接口和MyInvocationHandler生成了一个代理类的字节码,并而代理类中的真实对象的方法是通过反射机制进行调用的(我们在实现invoke方法时都是通过反射的APIMethod的invoke调用具体的方法可知),这也是JDK动态代理在创建代理对象快而运行时却比较慢的原因。
感兴趣的朋友可以去看一下JDK的动态代理实现的源代码(JDK中通过Proxy中的getProxyClass(loader, interfaces)方法生成代理代理对象)。
如果要代理具体的业务类,需要使用CGLib动态代理生成子类了。