代理模式是一种常见的设计模式,它使用代理对象完成用户请求,屏蔽用户对真实对象的访问。例如,处于安全的原因,需要屏蔽客户端直接访问真实对象;在远程调用中,需要使用代理类处理远程方法调用的技术细节;为了提升系统性能,对真实对象进行封装,从而达到延迟加载的目的。在实际业务中,我们需要查询数据库数据,在查询数据库时需要拿到连接信息,这里面就会涉及到一些XML解析工作,在系统启动时,如果加载这些解析操作可能会导致系统启动缓慢,我们可以使用代理类,当系统启动时初始化这个代理类,只有真正需要做数据查询的时候才启动真正的XML解析类。所以,一般在系统启动时,我们可以将消耗资源最多的方法都使用代理模式,这样就可以加快系统的启动速度,从而减少用户的等待时间。
静态代理指的是在设计时就定义好了代理和真实对象之间的对应关系。例如在做数据查询业务时,在系统启动时不启动真正的查询对象,只是初始化代理对象,只有在真正需要做数据查询时,才初始化真正的查询实现。在设计过程中,先定义好一个接口IDbQuery接口,它只有一个request方法,返回请求。定义一个真实的查询对象DBQuery,它是一个重量级对象,构造会比较慢。再定义一个代理实现类DBQueryProxy,它是轻量级对象,创建很快,可以替换系统中DBQuery的位置。
package pdfdemo;
public class MainClass {public static void main(String args[]) {IDBQuery q = new IDBQueryProxy();System.out.println(q.request());}
}
package pdfdemo;
public interface IDBQuery {String request();
}
package pdfdemo;
public class DBQuery implements IDBQuery{public DBQuery() {try {Thread.sleep(1000);}catch(InterruptedException e) {e.printStackTrace();}}@Overridepublic String request() {// TODO Auto-generated method stubreturn "request string";}
}
package pdfdemo;
public class IDBQueryProxy implements IDBQuery{private DBQuery realObject = null;@Overridepublic String request() {if(realObject == null) {realObject = new DBQuery();}return realObject.request();}
}
动态代理是指正在运行时动态生成代理类,即代理类的字节码将在运行时自动生成并载入当前的ClassLoader。和静态代理类象比,动态类有很多好处。首先,不需要为真实主题写一个形式上完全一样的封装类。如果写了完全一样的封装类,则意味着如果接口有变动,则真实类和代理类都需要同步修改,不利于系统维护。其次,使用一些动态代理的生成方法甚至可以再运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。
生成动态代理的方法很多,如JDK自带的动态代理、CGLIB、Javassist或者ASM库。JDK的动态代理使用简单,内置再JDK中,因此不需要引入第三方Jar包,但功能比较弱。CGLIB和Javassist都是高级字节码生成库,总体性能要比JDK自带的动态代理要好,且功能更加强大。ASM是低级字节码生成工具,使用ASM已经近乎在使用Java字节码编程,对开发人员要求高,也是一种性能最好的动态代理生成工具。但ASM使用过于烦琐,而且性能也没有数量级的提升,与CGLIB等高级别字节码生成工具比,ASM程序的可维护性也比较差。
依然以数据库数据查询为例,使用动态代理生成动态类。
JDK的动态代理需要实现一个处理方法调用Handler,用于实现代理方法的内部逻辑。
package pdfdemo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkDBQueryHandler implements InvocationHandler{IDBQuery real = null;@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if(real == null) {real = new DBQuery();}return real.request();}public static IDBQuery createJdkProxy() {IDBQuery jdkProxy = (IDBQuery) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[] {IDBQuery.class},new JdkDBQueryHandler());return jdkProxy;}
}package pdfdemo;
public class MainClass {public static void main(String args[]) {IDBQuery dBQuery = JdkDBQueryHandler.createJdkProxy();Object q = dBQuery.request();System.out.println(q.toString());}
}
动态代理模式还有一个很大的场景就是方法的增强,也就是经常讲的AOP面向切面编程。例如,我们需要对对象的某个一个方法进行增强,我们可以在InvocationHandler中的Invoke方法中对目标对象的方法进行增强,将增强后的结果进行返回。
@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if(real == null) {real = new DBQuery();}if(method.getName().equals("request")) {//这里可以写增强逻辑return real.request();}else if(method.getName().equals("dd")) {return real.dd();}return null;}
使用CGli生成代理对象跟JDK本身自带的生成代理对象代码实现方式类似。只是通过代理生成目标对象的方式不同。
package pdfdemo;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibDBQueryInterceptor implements MethodInterceptor{IDBQuery real = null;@Overridepublic Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {// TODO Auto-generated method stubif(real == null) {real = new DBQuery();}if(arg1.getName().equals("request")) {return real.request();}else if(arg1.getName().equals("dd")) {return real.dd();}return null;}public static IDBQuery createCglibProxy() {Enhancer enhancer = new Enhancer();enhancer.setCallback(new CglibDBQueryInterceptor());enhancer.setInterfaces(new Class[] {IDBQuery.class});IDBQuery proxy = (IDBQuery) enhancer.create();return proxy;}
}
使用Javassist生成代理对象有两种方法,一种是工厂模式,一种是使用动态代码创建。工厂模式就和Cglib比较像。
package pdfdemo;
import java.lang.reflect.Method;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
public class JavassistDynDBQueryHandler implements MethodHandler{IDBQuery real = null;@Overridepublic Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {if(real == null){real = new DBQuery();}if(thisMethod.getName().equals("request")) {return real.request();}else if(thisMethod.getName().equals("dd")) {return real.dd();}return null;} public static IDBQuery createJavassistDynProxy() throws Exception {ProxyFactory proxyFactor = new ProxyFactory();proxyFactor.setInterfaces(new Class[] {IDBQuery.class});Class proxyClass = proxyFactor.createClass();IDBQuery proxy = (IDBQuery) proxyClass.newInstance();ProxyObject object = (ProxyObject) proxy;object.setHandler(new JavassistDynDBQueryHandler());return (IDBQuery) object;}
}
Javassist内部还可以通过动态java代码生成字节码,用这种方式创建动态代理的方式比较灵活。在系统运行时,自动生成代理类的字段和方法。和Javassist或者Cglib生成模式相比,Cglib需要定义Method方法,而这种模式不需要实现Method方法,而是通过动态字节码模式生成类似于Cglib的Method方法。
package pdfdemo;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
public class JavassistDynDBQueryByte {public static IDBQuery createJavassistBytecodeDynamicProxy() throws Exception {ClassPool mPool = new ClassPool(true);CtClass mCtc = mPool.makeClass(IDBQuery.class.getName()+"JavassistBytecodeProxy");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;}
}package pdfdemo;
public class MainClass {public static void main(String args[]) throws Exception {doJavassitDynDBQueryByte();}public static void doJavassitDynDBQueryByte() throws Exception {IDBQuery dquery = JavassistDynDBQueryByte.createJavassistBytecodeDynamicProxy();Object q = dquery.request();System.out.println(q.toString());}
}
实现代理的方式有静态和动态之分,静态代理实现简单,也比较容易看懂,但代码量会比较大,因为静态代理的代理类需要和真实类继承同一个接口,也意味着需要复写接口中所有的方法,一般接口出现变动,代理类也同样需要变化。动态代理不需要和真实类继承同样的接口,复写接口中的所有方法,所以灵活性相对高一些,代码量也更少一点,但可读性稍微差一点。其次,使用一些动态代理的生成方法甚至可以在运行时指定代理类的执行逻辑,从而大大提升系统的灵活性。
动态代理可以通过多种方式实现,但就不同实现方式的性能而言,CGLIB和Javassist的基于动态代码的代理都优于JDK自带的动态代理。此外,JDK的动态代理要求代理类和真实主题都实现同一个接口,而CGLIB和Javassist则没有强制要求。所以,一般CGLIB应用会更广一些。