代理模式是一种很常见的设计模式。使用代理对象完成用户请求,屏蔽用户对真实对象的访问。
在软件设计中,使用代理模式的意图也很多,比如因为安全原因,需要屏蔽客户端直接访问真实对象;或者在远程调用中,需要使用代理类处理远程方法调用的技术细节;也可能是为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。
代理模式的主要参与者有4个:主题接口—定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;真实主题—真正实现业务逻辑的类;代理类—用来代理和封装真实主题;main—客户端,代理类和主题接口的使用者。
使用代理模式进行延迟加载的核心为:当没有使用组件时,使用代理类来替换真实类的实例化,当真正使用时才实例化真实类。因为在真实类中可能会消耗大量的资源,使用代理模式延迟加载,可以将消耗资源最多的方法分离出来,可以加快系统的启动速度,减少用户的等待时间。
动态代理是指在运行时,动态生成代理类。即代理类的字节码将在运行时生成并载入当前的ClassLoader。好处:不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也是非常烦人的事情,如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;使用一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑,从而提高系统的灵活性。
生成动态代理类的方法很多,如JDK自带的动态代理/CGLIB/Javassist/ASM库。JDK的动态代理使用简单,它内置在JDK中,不需要带入第三方jar包,但相对功能较弱;CGLIB和Javassist都是高级的字节码生成库,总体性能比JDK自带的动态代理好,而且功能十分强大。ASM是低级的字节码生成工具,使用ASM已经近乎在使用Java bytecode编程,对开发人员要求最高,也是性能最好的一种动态代理生成工具。但ASM的使用过于繁琐,性能上也没有数量级的提升,与CGLIB等高级字节码生成工具相比,维护性差。
定义主题接口:
public interface IDBQuery {
String request();
}
定义真实主题,业务逻辑的真实实现类:
public class DBQuery implements IDBQuery{
public DBQuery(){
//可能会进行一些数据库的链接等操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//真实的业务逻辑处理
public String request() {
return "request string ";
}
}
静态代理方法的实现:
public class DBQueryProxy implements IDBQuery{
private DBQuery real;
public String request() {
//创建真实类
if(real == null){
real = new DBQuery();
}
//调用真实业务
return real.request();
}
}
静态代理的测试方法:
@Test
public void testStaticProxy(){
IDBQuery query = new DBQueryProxy();
System.out.println(query.request());
}
动态代理的实现–JDK动态代理
Jdk动态代理需要实现InvocationHandler接口。参考代码如下:
public class JdkDBQueryHandler implements InvocationHandler {
private DBQuery real;// 真实主题
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(real == null){
real = new DBQuery();//创建真实主题
}
//调用真实主题
Object result = method.invoke(real, args);
return result;
}
}
创建JDK动态代理对象:
/**
* 获取jdk 动态代理对象
* @return
*/
public static IDBQuery createJDKProxy(){
IDBQuery query = (IDBQuery)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), //类加载器
new Class[]{IDBQuery.class},//主题接口
new JdkDBQueryHandler());//handler
return query;
}
动态代理的实现–Cglib动态代理
使用Cglib动态代理需要引入Cglib的jar包,其中Maven配置文件如下:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
Cglib动态代理需要实现MethodInterceptor接口。参考代码如下:
public class CglibDBQueryProxy implements MethodInterceptor{
private DBQuery real;
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if(real == null){
real = new DBQuery();
}
Object result = proxy.invoke(real, args);
return result;
}
}
创建Cglib动态代理对象:
/**
* 使用Cglib创建动态代理对象
* @return
*/
public static IDBQuery createCglibProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibDBQueryProxy());//指定切入点 回掉
enhancer.setInterfaces(new Class[]{IDBQuery.class});//设置主题接口
IDBQuery query = (IDBQuery) enhancer.create(); //创建代理对象
return query;
}
动态代理的实现–Javassist动态代理
创建Javassist动态代理有两种方式:一通过代理工厂的方式;二通过动态代码创建。不管是哪种方式,需要引入对应的jar包,Maven配置如下:
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.3</version>
</dependency>
代理工厂创建Javassist,需要实现MethodHandler接口:
public class JavassistProxyHandler implements MethodHandler{
private DBQuery real;
public Object invoke(Object arg0, Method arg1, Method arg2, Object[] arg3) throws Throwable {
if(real == null){
real = new DBQuery();
}
return real.request();
}
}
创建Javassist动态代理对象:
/**
* 使用MethodHandler创建动态代理对象Javassist
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static IDBQuery createJavassistProxyHandler() throws InstantiationException, IllegalAccessException{
ProxyFactory factory = new ProxyFactory();
factory.setInterfaces(new Class[]{IDBQuery.class});
Class proxyClass = factory.createClass();
IDBQuery query = (IDBQuery) proxyClass.newInstance();
((ProxyObject)query).setHandler(new JavassistProxyHandler());
return query;
}
使用动态代码创建:
/**
* 字节码创建动态代理
* @return
* @throws NotFoundException
* @throws CannotCompileException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static IDBQuery createJavassistBytecodeProxy() throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException{
ClassPool mPool = new ClassPool(true);
//定义类名
CtClass mCtc = mPool.makeClass(IDBQuery.class.getName() + "Javassist-BytecodeProxy");
//需要实现的接口
mCtc.addInterface(mPool.get(IDBQuery.class.getName()));
//添加构造函数
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
//设置字段信息
mCtc.addField(CtField.make("public " + IDBQuery.class.getName() + " real;", mCtc));
String dbqueryname = DBQuery.class.getName();
mCtc.addMethod(CtNewMethod.make("public String request(){if(real==null)real = new " + dbqueryname + "();return real.request();}", mCtc));
Class pc = mCtc.toClass();
IDBQuery bytecodeproxy = (IDBQuery)pc.newInstance();
return bytecodeproxy;
}
测试性能:
/**
* 测试几种动态代理的效率
* @throws InstantiationException
* @throws IllegalAccessException
* @throws NotFoundException
* @throws CannotCompileException
*/
@Test
public void testDynamicProxy() throws InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException{
int circle = 300000000;
IDBQuery query = null;
System.out.println("========JdkProxy============");
long begin = System.currentTimeMillis();
query = DynamicProxy.createJDKProxy();
System.out.println("createJDKProxy:" + (System.currentTimeMillis() - begin));
System.out.println("JdkProxy class:" + query.getClass().getName());
begin = System.currentTimeMillis();
for(int i = 0; i<circle; i++){
query.request();
}
System.out.println("call JdkProxy:" + (System.currentTimeMillis() - begin));
System.out.println("========CglibProxy============");
begin = System.currentTimeMillis();
query = DynamicProxy.createCglibProxy();
System.out.println("createCglibProxy:" + (System.currentTimeMillis() - begin));
System.out.println("CglibProxy class:" + query.getClass().getName());
begin = System.currentTimeMillis();
for(int i = 0; i<circle; i++){
query.request();
}
System.out.println("call CglibProxy:" + (System.currentTimeMillis() - begin));
System.out.println("========JavassistHandler============");
begin = System.currentTimeMillis();
query = DynamicProxy.createJavassistProxyHandler();
System.out.println("createJavassistProxyHandler:" + (System.currentTimeMillis() - begin));
System.out.println("JavassistHandler class:" + query.getClass().getName());
begin = System.currentTimeMillis();
for(int i = 0; i<circle; i++){
query.request();
}
System.out.println("call JavassistHandler:" + (System.currentTimeMillis() - begin));
System.out.println("========JavassistBytecodeProxy============");
begin = System.currentTimeMillis();
query = DynamicProxy.createJavassistBytecodeProxy();
System.out.println("createJavassistBytecodeProxy:" + (System.currentTimeMillis() - begin));
System.out.println("JavassistBytecodeProxy class:" + query.getClass().getName());
begin = System.currentTimeMillis();
for(int i = 0; i<circle; i++){
query.request();
}
System.out.println("call JavassistBytecodeProxy:" + (System.currentTimeMillis() - begin));
}
执行结果:
========JdkProxy============
createJDKProxy:4
JdkProxy class:com.sun.proxy.$Proxy2
call JdkProxy:2323
========CglibProxy============
createCglibProxy:98
CglibProxy class:com.performance.optimization.design.proxy.IDBQuery$$EnhancerByCGLIB$$9106cb9d
call CglibProxy:1827
========JavassistHandler============
createJavassistProxyHandler:43
JavassistHandler class:com.performance.optimization.design.proxy.IDBQuery_$$_javassist_0
call JavassistHandler:1039
========JavassistBytecodeProxy============
createJavassistBytecodeProxy:73
JavassistBytecodeProxy class:com.performance.optimization.design.proxy.IDBQueryJavassist-BytecodeProxy
call JavassistBytecodeProxy:1011
从上述执行结果可以看到:使用JDK动态代理创建对象花费时间最短,但是在函数调用的性能上,比其他三种花费的时间较多。
以上几种实现动态代理的基本步骤为:根据指定的回调类生成Class字节码;通过defineClass()将字节码定义为类;使用反射机制生成该类的实例。