设计模式--代理模式

代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型模式。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理模式分为静态代理和动态代理。

使用代理模式的主要目的是:保护目标对象;增强目标对象。

代理模式的通用写法

java 复制代码
interface ISubject{
    void resquest();
}

class RealSubject implements ISubject{

    @Override
    public void resquest() {
        System.out.println("real service is called.");
    }
}

class Proxy implements ISubject{
    private ISubject subject;

    public Proxy(ISubject subject){
        this.subject = subject;
    }

    @Override
    public void resquest() {
        before();
        subject.resquest();
        after();
    }

    public void before(){
        System.out.println("called before request().");
    }

    public void after(){
        System.out.println("called after request().");
    }
}

public class Test {
    public static void main(String[] args) {
        Proxy proxy = new Proxy(new RealSubject());
        proxy.resquest();
    }
}

由静态代理到动态代理

在Java生态中,目前最普遍使用的是JDK自带的代理和Cglib提供的类库。

首先,采用JDK的动态代理优化上述代码:

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface ISubject{
    void resquest();
}


class RealSubject implements ISubject{

    @Override
    public void resquest() {
        System.out.println("real service is called.");
    }
}

/**
 * JDK动态代理
 */
class ProxyJDK implements InvocationHandler {
    private ISubject subject;

    public ISubject getInstance(ISubject subject){
        this.subject = subject;
        Class<?> clazz = subject.getClass();
        return (ISubject) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }


    public void before(){
        System.out.println("called before request().");
    }

    public void after(){
        System.out.println("called after request().");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.subject, args);
        after();
        return result;
    }
}

public class Test {
    public static void main(String[] args) {
        ISubject instance = new ProxyJDK().getInstance(new RealSubject());
        instance.resquest();
    }
}

手动实现JDK动态代理:

JDK 动态代理采用字节码重组,重新生成对象来替代原始对象,以达到动态代理的目的。JDK 动态代理生成对象的步骤如下:(JDK代理通过反射调用)

  • 获取被代理对象的引用,并且获取它的所有接口,反射获取;
  • JDK 动态代理类重新生成一个新的类,同时新的类要实现被代理类实现的所有接口;
  • 动态生成 Java 代码,新加的业务逻辑方法由一定的逻辑代码调用(在代码中体现);
  • 编译新生成的 Java 代码.class 文件;
  • 重新加载到 JVM 中运行。
java 复制代码
/**
 * 自己实现JDK动态代理
 */
interface GPInvocationHandler{
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

/**
 * 用来生成源码的工具类
 */
class GPProxy{
    public static final String ln = "\r\n";

    public static Object newProxyInstance(GPClassLoader classLoader, Class<?>[] interfaces, GPInvocationHandler h){
        try{
            //动态生成源代码.java文件
            String src = generateSrc(interfaces);

            //2、Java文件输出磁盘
            String filePath = URLDecoder.decode(GPProxy.class.getResource("").getPath(), "UTF-8");
            System.out.println("GpProxy filePath: "+filePath);
            File f = new File(filePath + "$Proxy0.java");
            System.out.println("GpProxy File: "+f);
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

            //3、把生成的.java文件编译成.class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manage.getJavaFileObjects(f);

            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
            task.call();
            manage.close();

            //4、编译生成的.class文件加载到JVM中来
            Class proxyClass = classLoader.findClass("$Proxy0", interfaces);
            Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
            System.out.println("GpProxy Constructor: "+c );
            f.delete();

            //5、返回字节码重组以后的新的代理对象
            return c.newInstance(h);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces){
        StringBuffer sb = new StringBuffer();
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
        sb.append("GPInvocationHandler h;" + ln);
        sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
        sb.append("this.h = h;");
        sb.append("}" + ln);

        for (Method m : interfaces[0].getMethods()) {
            Class<?>[] params = m.getParameterTypes();
            StringBuffer paramNames = new StringBuffer();
            StringBuffer paramValues = new StringBuffer();
            StringBuffer paramClasses = new StringBuffer();

            for (int i = 0; i < params.length; i++) {
                Class clazz = params[i];
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName());
                paramNames.append(type + " " + paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName() + ".class");
                if (i > 0 && i < params.length - 1) {
                    paramNames.append(",");
                    paramValues.append(",");
                    paramClasses.append(",");
                }

            }
            System.out.println("paramNames: "+paramNames);
            System.out.println("paramValues: "+paramValues);
            System.out.println("paramClasses: "+paramClasses);

            sb.append("public " + m.getReturnType().getName() + " " + m.getName()
                    + "(" + paramNames.toString() + ") {" + ln);
            sb.append("try{" + ln);
            sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\""
                    + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
            sb.append((hasReturnValue(m.getReturnType()) ? "return " : "")
                    + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType())
                    + ";" + ln);
            sb.append("}catch(Error _ex) { }");
            sb.append("catch(Throwable e){" + ln);
            sb.append("throw new UndeclaredThrowableException(e);" + ln);
            sb.append("}");
            sb.append(getReturnEmptyCode(m.getReturnType()));
            sb.append("}");
        }
        sb.append("}" + ln);
        return sb.toString();
    }

    private static Map<Class,Class> mappings = new HashMap<>();
    static{
        mappings.put(int.class,Integer.class);
    }

    private static String getReturnEmptyCode(Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "return 0;";
        } else if (returnClass == void.class) {
            return "";
        } else {
            return "return null;";
        }
    }

    private static String getCaseCode(String code, Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")."
                    + returnClass.getSimpleName() + "Values()";
        }
        System.out.println("code: "+code);
        return code;
    }

    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }

    private static String toLowerFirstCase(String src) {
        char[] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

}


class GPClassLoader extends ClassLoader{
    private File classPathFile;
    public GPClassLoader(){
        String classPath = GPClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    protected Class<?> findClass(String name, Class<?>[] interfaces)throws ClassNotFoundException {
        String className;
        if(StringUtils.isNotBlank(GPClassLoader.class.getPackage().getName())){
            className = GPClassLoader.class.getPackage().getName() + "." + name;
        }else{
            className = name;
        }
        if(classPathFile != null){
            File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class");
            if(classFile.exists()){
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try{
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte[] buff = new byte[1024];
                    int len;
                    while((len=in.read(buff)) != -1){
                        out.write(buff,0,len);
                    }
                    return defineClass(className,out.toByteArray(),0,out.size());
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    if(null != in){
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    if(null != out){
                        try {
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return null;
    }
}

/**
 * JDK动态代理
 */
class ProxyJDK2 implements GPInvocationHandler {
    private ISubject subject;

    public ISubject getInstance(ISubject subject){
        this.subject = subject;
        Class<?> clazz = subject.getClass();
        return (ISubject) GPProxy.newProxyInstance(new GPClassLoader(),clazz.getInterfaces(),this);
    }


    public void before(){
        System.out.println("called before request().");
    }

    public void after(){
        System.out.println("called after request().");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.subject, args);
        after();
        return result;
    }
}

public class Test {
    public static void main(String[] args) {
//        ISubject instance = new ProxyJDK().getInstance(new RealSubject());
//        System.out.println(instance.getClass());
//        instance.resquest();

        ISubject instance2 = new ProxyJDK2().getInstance(new RealSubject());
        System.out.println(instance2.getClass());
        instance2.resquest();
    }
}

注意:ISubject接口必须单独放一个文件里,不然会报错:class Proxy0 cannot access its superinterface ISubject (Proxy0 is in unnamed module of loader GPClassLoader @7d4793a8; ISubject is in unnamed module of loader 'app'),应该是保护域问题ProtectionDomain。反射机制的应用必须要求该类是public访问权限的。有朋友可能会说,可以调用setAccessible(true)方式来改变可见性,但这是一个概念混淆导致的错误。setAccessible(true)函数是反射机制用于改变类内属性访问权限的,而不是改变类本身的可见性。

CGLib动态代理

CGlib代理的目标对象不需要实现任何接口,它通过动态继承目标对象实现动态代理。

java 复制代码
/**
 * CGLib创建动态代理
 * 这是由于 JDK 8 中有关反射相关的功能自从 JDK 9 开始就已经被限制了,为了兼容原先的版本,需要在运行项目时添加 --add-opens java.base/java.lang=ALL-UNNAMED 选项来开启这种默认不被允许的行为。
 */

class ProxyCGLib implements MethodInterceptor {
    public Object getInstance(Class<?> clazz){
        Enhancer enhancer = new Enhancer();
        //要把设置为即将生成的新类的父类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);

        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }

    public void before(){
        System.out.println("called before request().");
    }

    public void after(){
        System.out.println("called after request().");
    }
}
public class Test {
    public static void main(String[] args) {
        ISubject instance = (ISubject) new ProxyCGLib().getInstance(RealSubject.class);
        instance.resquest();
    }
}

CGLib和JDK动态代理对比

  • JDK动态代理实现了被代理对象的接口,CGLib代理继承了被代理对象
  • JDK动态代理和CGLib代理都在运行期生成字节码,JDK动态代理直接写Class字节码,CGLib代理使用ASM框架写Class字节码,CGLib代理实现更复杂,生成代理类比JDK动态代理效率低
  • JDK动态代理调用代理方法是通过反射机制调用的,CGLib代理是通过FastClass机制调用方法,CGLib效率更高。

代理模式的优缺点:

优点:

  • 代理模式能将代理对象与真实被调用目标对象分离
  • 在一定程度上降低了系统的耦合性,扩展性好
  • 可以起到保护目标对象的作用
  • 可以增强目标对象的功能

缺点:

  • 代理模式会造成系统设计中类的数量
  • 在客户端和目标对象中增加一个代理对象,会导致请求速度变慢
  • 增加了系统的复杂性。
相关推荐
李广坤3 小时前
状态模式(State Pattern)
设计模式
李广坤5 小时前
观察者模式(Observer Pattern)
设计模式
李广坤6 小时前
中介者模式(Mediator Pattern)
设计模式
李广坤6 小时前
迭代器模式(Iterator Pattern)
设计模式
李广坤6 小时前
解释器模式(Interpreter Pattern)
设计模式
阿无,9 小时前
java23种设计模式之前言
设计模式
Asort10 小时前
JavaScript设计模式(八):组合模式(Composite)——构建灵活可扩展的树形对象结构
前端·javascript·设计模式
数据智能老司机10 小时前
数据工程设计模式——数据基础
大数据·设计模式·架构
笨手笨脚の12 小时前
设计模式-代理模式
设计模式·代理模式·aop·动态代理·结构型设计模式
Overboom20 小时前
[C++] --- 常用设计模式
开发语言·c++·设计模式