代理模式是一种常见的设计模式,在实际业务实现过程中肯定经常用到。代理模式可以分成宏观、中观和微观的代理模式。例如外部系统要访问数据库,数据库不会把接口直接开放出去,这时候会在数据段放置一个前置系统,该前置系统和数据库直连,外部系统和该系统相连。这是一种宏观的代理模式。在整个业务的角度通过代理实现业务和数据的互联互通,相似的场景还有webapi接口。中观代理模式类似微服务调用,服务内部封装实现细节,通过微服务接口把能力开放出去,其他服务来调用即可。从实现角度也可以理解成是一种代理模式。代理模式的微观视角就是软件设计的一种设计模式,例如延迟加载。在程序设计领域经常讲到的代理模式就是从微观角度来看的。
代理模式的参与角色主要是有四个,分别是主题接口、真实主题、代理类、调用类(也就是一般Main类)。主题接口定义代理类和真实主题对外的公共方法,也是代理类代理真实主题的方法。真实主题指的是真正实现业务逻辑的类。代理类指的是用来代理和封装真实主题。调用类也就是指客户端获或者是服务调用方。
代理模式的实现方式分为两大类,一种是静态代理,一种是动态代理。静态代理模式非常简单,只需要代理类和真实类共同实现同一个接口,代理类的是方法和真实类方法一致,在系统启动的时候只是加载代理类,只有需要真正调用真实类对象时,才会初始化真实类对象。根据jvm虚拟机的类加载机制,我们会发现只有在对对象进行new操作的时候,才会进行类的初始化。在系统启动的时候知乎初始化proxy代理类,只有系统在真正调用类的方法的时候才会对真实类进行初始化。
//定义接口
package book.performance.part2.proxy;
public interface IDBQuery {
String request();
}
//真实实现的类
package book.performance.part2.proxy;
public class DBQuery implements IDBQuery{
public String request() {
return "request string";
}
public DBQuery(){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
@Override
public String toString() {
return "hello world";
}
}
静态代理模式需要代理类封装真实的类,方法和参数保持一致,并且实现同一个接口。如果真实类的方法比较多,意味着代理类要封装的方法也很多。在业务方发起调用时,不会直接调用真实的DBQuery类方法,而是通过调用DBQueryProxy的方法,通过代码再发起真实对象的调用。具体代码逻辑可参考DBQueryProxy类的createStaticProxy方法。
package book.performance.part2.proxy;
import javassist.*;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import net.sf.cglib.proxy.Enhancer;
import java.lang.reflect.Proxy;
public class DBQueryProxy implements IDBQuery{
private DBQuery real = null;
public String request() {
if(real == null){
real = new DBQuery();
}
return real.request();
}
public String createStaticProxy(){
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 JdkDbQeuryHandler());
return jdkProxy;
}
public static IDBQuery createCglibProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibDBQueryInterceptor());
enhancer.setInterfaces(new Class[]{IDBQuery.class});
IDBQuery cglibproxy = (IDBQuery) enhancer.create();
return cglibproxy;
}
public static IDBQuery createJavassistDynProxy() throws Exception {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setInterfaces(new Class[]{IDBQuery.class});
Class proxyClass = proxyFactory.createClass();
IDBQuery javassistQuery = (IDBQuery) proxyClass.newInstance();
((ProxyObject)javassistQuery).setHandler(new JavassistDynDBQueryHandler());
return javassistQuery;
}
public static IDBQuery createJavassistBytecodeDynamicProxy() throws Exception{
ClassPool mPool = new ClassPool(true);
CtClass mCtc = mPool.makeClass(IDBQuery.class.getName()+"" +
"JavasssitBytecodeProxy");
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;
}
}
动态代理是指运行时,动态生成代理类。即代理类的字节码将在运行时生成并载入当前的ClassLoader。和静态代理模式相比,它不需要写一个形式上完全一致的封装类。这样即使真实类接口或者方法发生变化,也不一定需要修改代理类。动态代理有几种方式可以实现,如jdk自带的动态代理、第三方的CGLIB、Javassist等。多种动态代理实现模式的效果是一致的,但实现方法以及性能是有差异的。使用jdk自带的动态代理实现代码可参考DBQueryProxy的createJdkProxy方法。使用CGLIB实现动态代理可参考DBQueryProxy的createCglibProxy方法。Javassist实现动态代理是有两种方式可以支持,一种是通过代理工厂创建(参考DBQueryProxy的createJavassistDynProxy方法),一种是可以在Javassist内通过动态java代码生成字节码(参考DBQueryProxy的createJavassistBytecodeDynamicProxy方法)。
1.jdk自带的动态代理需要用户自定义实现InvocationHandler接口,复写invoke方法,在invoke方法中调用在真实类真实方法。
2.cglib是通过intercept方式实现,用户需实现MethodInterceptor接口,复写intercept方法,在intercept方法中调用真实类的真实方法。
3.Javassist通过工厂模式创建代理类的实现方式和前两个是类似的,用户也是实现MethodHandler接口,复写invoke方法,在invoke方法中调用真实类的真实方法。
4.Javassist通过动态java代码生成字节码的方式会有点差异,是程序在运行时自动定义接口、定义对象,生成目标对象,从而完成真实类的真实方法调用。
package book.performance.part2.proxy;
/*
启动类
性能测试类
*/
public class DBQueryMainClass {
public static void main(String[] args) throws Exception{
//makeObejctTest();
performanceTest();
}
public static void makeObejctTest() throws Exception{
DBQueryProxy proxy = new DBQueryProxy();
System.out.println(proxy.request());
System.out.println("static proxy success");
System.out.println("*****************");
IDBQuery query = JdkDBQueryProxy.createJdkProxy();
System.out.println(query.request());
System.out.println("jdk dynamic proxy");
System.out.println("---------------");
IDBQuery cglibQuery = DBQueryProxy.createCglibProxy();
System.out.println(cglibQuery.request());
System.out.println("cglibProxy success");
System.out.println("*****************");
IDBQuery javassistDynQuery = DBQueryProxy.createJavassistDynProxy();
System.out.println(javassistDynQuery.toString());
System.out.println("javassistProxy success");
System.out.println("---------------");
IDBQuery javassistByteCodeProxy = DBQueryProxy.createJavassistBytecodeDynamicProxy();
System.out.println(javassistByteCodeProxy.request());
System.out.println("javassistByteCodeProxy success");
System.out.println("*****************");
}
public static final int CIRCLE = 30000000;
public static void performanceTest() throws Exception{
IDBQuery dbquery = null;
long begin = System.currentTimeMillis();
DBQueryStaticProxy proxy = new DBQueryStaticProxy();
System.out.println("createstaticProxyTime:" + (System.currentTimeMillis()-begin));
System.out.println("staticProxy className:"+proxy.getClass().getName());
begin = System.currentTimeMillis();
for(int i=0;i
package book.performance.part2.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//jdk自带代理的InvocationHandler实现类
public class JdkDbQeuryHandler implements InvocationHandler {
IDBQuery real = null;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(real == null){
real = new DBQuery();
}
return real.request();
}
}
package book.performance.part2.proxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//cglib自定义实现MethodInterceptor类
public class CglibDBQueryInterceptor implements MethodInterceptor {
IDBQuery real = null;
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if(real == null){
real = new DBQuery();
}
return real.request();
}
}
package book.performance.part2.proxy;
import javassist.util.proxy.MethodHandler;
import java.lang.reflect.Method;
//Javassist通过工厂模式实现代理实现的MethodHandler接口
public class JavassistDynDBQueryHandler implements MethodHandler {
IDBQuery real = null;
public Object invoke(Object o, Method method, Method method1, Object[] objects) throws Throwable {
if(real == null){
real = new DBQuery();
}
return real.request();
}
}
从最后的性能来看静态代理类创建代理对象是耗时是最少的,这个一般在真实系统中也就在创建代理类时消耗资源一次,对整个系统的性能影响不大。核心在于对真实主题真实方法调用的耗时。假设循环3000000次,对静态代理、动态代理分别做性能测试,我们会发现创建对象时,静态资源性能是最优的。在真实对象的方法调用时,静态代理和jdk自带的动态代理性能是差不多的,性能也比较好,其次是Javassist通过动态代码模式以及cglib,最后是Javassist的创建模式,但差异不是特别大。从一般程序实现角度来看,用静态代理或者是jdk自带的代理模式就可以了。如果一些特殊场景可以考虑cglib或者Javasssit工厂模式。至于Javassist的动态代码模式,它是在运行时动态加载代码生成对象,这个不好调试,对代码质量要求比较高,从研发效率上来看可能并不是最优的选择。
maven工程所依赖的jar包
cglib
cglib
3.3.0
org.javassist
javassist
3.25.0-GA