java代理模式

代理设计模式

代理模式是一种常见的设计模式,在实际业务实现过程中肯定经常用到。代理模式可以分成宏观、中观和微观的代理模式。例如外部系统要访问数据库,数据库不会把接口直接开放出去,这时候会在数据段放置一个前置系统,该前置系统和数据库直连,外部系统和该系统相连。这是一种宏观的代理模式。在整个业务的角度通过代理实现业务和数据的互联互通,相似的场景还有webapi接口。中观代理模式类似微服务调用,服务内部封装实现细节,通过微服务接口把能力开放出去,其他服务来调用即可。从实现角度也可以理解成是一种代理模式。代理模式的微观视角就是软件设计的一种设计模式,例如延迟加载。在程序设计领域经常讲到的代理模式就是从微观角度来看的。

代理模式角色

代理模式的参与角色主要是有四个,分别是主题接口、真实主题、代理类、调用类(也就是一般Main类)。主题接口定义代理类和真实主题对外的公共方法,也是代理类代理真实主题的方法。真实主题指的是真正实现业务逻辑的类。代理类指的是用来代理和封装真实主题。调用类也就是指客户端获或者是服务调用方。

代理模式实现方式

代理模式的实现方式分为两大类,一种是静态代理,一种是动态代理。静态代理模式非常简单,只需要代理类和真实类共同实现同一个接口,代理类的是方法和真实类方法一致,在系统启动的时候只是加载代理类,只有需要真正调用真实类对象时,才会初始化真实类对象。根据jvm虚拟机的类加载机制,我们会发现只有在对对象进行new操作的时候,才会进行类的初始化。在系统启动的时候知乎初始化proxy代理类,只有系统在真正调用类的方法的时候才会对真实类进行初始化。
java代理模式_第1张图片

//定义接口
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方法。
java代理模式_第2张图片

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的动态代码模式,它是在运行时动态加载代码生成对象,这个不好调试,对代码质量要求比较高,从研发效率上来看可能并不是最优的选择。
java代理模式_第3张图片

附录

maven工程所依赖的jar包


    
      cglib
      cglib
      3.3.0
    
    
      org.javassist
      javassist
      3.25.0-GA
    
  

你可能感兴趣的:(设计模式,java,java,代理模式,性能)