Java设计模式——代理模式【Proxy Pattern】 (动态代理)

一、引言

关于代理模式,之前写过一篇是介绍静态代理的。在看这篇文章之前,建议先看一下之前静态代理这篇文章。

Java设计模式——代理模式【Proxy Pattern】 (静态代理)

二、静态代理

关于代理,我们要明白两个概念:代理对象、目标对象。顾名思义,代理对象就是指增强后的对象;目标对象就是指需要被增强的对象。

我们实现静态代理的方式有两种:继承、聚合。

1、继承

继承就是代理对象需要继承目标对象,这样代理对象就拥有了目标对象的所有方法,只需要重写目标对象中需要增强的方法即可实现静态代理。

// 目标对象
public class UserDao {
    public void proxy(){  };
}

// 代理对象
public class UserDaoProxy extends UserDao {
    @Override
    public void proxy() {
        System.out.println("UserDaoProxy  proxy()");
        super.proxy();
    }
}

思考一下,如果我们需要不同的业务都需要代理对象,比如OrderDaoProxy、PayDaoProxy等,是不是要为每个业务类都定义一个类,并继承这个UserDao。这样就会造成代理类非常多。

缺点:会代理类过多,非常复杂,会产生类爆炸

2、聚合

聚合的实现思路是:目标对象和代理对象实现同一个接口,代理对象当中要包含目标对象。

此处的代码,可以参看Java设计模式——代理模式【Proxy Pattern】 (静态代理)这篇文章中的示列代码。

聚合模式的静态代理缺点:也会产生类爆炸,只不过比继承少一点点

总结:如果在不确定的情况下,尽量不要去使用静态代理。因为一旦你写代码,就会产生类,一旦产生类就爆炸。

三、动态代理

静态代理实现的两种方式里,都有一个共同缺点,就是随着业务类的增加,会产生很多很多的代理类,我们称之为类爆炸。那如何解决这个类爆炸的问题呢?

我们可以使用动态代理。那什么是动态代理呢?顾名思义,就是根据目标对象动态的创建我们需要的代理类。

动态代理就是在程序运行时,根据反射,动态的生成所需要的代理类。

1、对象是如何创建的?

在模拟动态代理之前,我们来想一下,在Java中,一个Java对象是如何被创建的?

如果不是很清楚,可以先去看一下,这篇文章一个Java对象是如何被创建的?

简单概括一下,就是我们需要编写一个Java源文件,然后把.java的源文件编译成.class文件,之后才能被类加载器加载到JVM中,最后我们就可以创建一个Java对象啦。

那动态代理实现的思路是啥呢?
我们可以通过接口反射生成一个类文件(Java源文件),然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,继而利用UrlclassLoader(因为这个动态产生的class不在工程当中所以需要使用UrlclassLoader)把这个动态编译的类加载到jvm当中,最后通过反射把这个类实例化。

这就是动态代理的实现思路,废话不多说喽~

2、如何模拟动态代理?

手动模拟一个创建代理类的ProxyUtil类,可以根据传入的目标对象,动态生成目标对象的代理类。


public class ProxyUtil {

	/**
	* targetInf : 目标对象实现的接口
	* CustomInvocationHandler : 自定义InvocationHandler处理类
	**/
    public static Object newInstance(Class targetInf, CustomInvocationHandler h){
        // 需要创建的代理对象
        Object proxy = null;
        // 获取目标对象实现接口中的所有方法
        Method methods[] =targetInf.getDeclaredMethods();
		// 换行符
        String line="\n";
        // 制表符
        String tab ="\t";
        // 接口的名字
        String infName = targetInf.getSimpleName();
        // 生成的源文件内容
        String content ="";
        // 构建源文件的内容
        // package 名
        String packageContent = "package com.scorpios;"+line;
        // import导入包
        String importContent = "import "+targetInf.getName()+";"+line
                          +"import com.scorpios.dao.CustomInvocationHandler;"+line
                          +"import java.lang.Exception;"
                          +"import java.lang.reflect.Method;"+line;
        // 类的第一行定义                  
        String clazzFirstLineContent = "public class $Proxy implements "+infName+"{"+line;
        // 类中定义的变量
        String filedContent = tab +"private CustomInvocationHandler h;"+line;
        // 构造函数
        String constructorContent = tab+"public $Proxy (CustomInvocationHandler h){" +line
                                  +tab+tab+"this.h =h;"
                                  +line+tab+"}"+line;
        // 类中的方法                          
        String methodContent = "";
        // 循环遍历接口中的方法
        for (Method method : methods) {
        	// 方法的返回类型
            String returnTypeName = method.getReturnType().getSimpleName();
            // 方法的名称
            String methodName = method.getName();
            // 方法的参数类型,比如:Sting.class String.class
            Class args[] = method.getParameterTypes();
            // 方法的入参,String p0,Sting p1
            String argsContent = ""; 
            // 方法调用时的参数
            String paramsContent="";
            // 参数的位置
            int flag = 0;
            for (Class arg : args) {
                // String
                String temp = arg.getSimpleName();
                // String p0,Sting p1
                argsContent+=temp+" p"+flag+",";
                // p0,p1
                paramsContent+="p"+flag+",";
                flag++;
            }
            // 截取最后的一个‘,’
            if (argsContent.length()>0){
                argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1);
                paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1);
            }

			// 构建出接口中具体的方法
            methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+")throws Exception {"+line
                           +tab+tab+"Method method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\");"+line
                            +tab+tab+"return ("+returnTypeName+")h.invoke(method);"+line;
            methodContent+=tab+"}"+line;
        }

		// 整个源文件的结构  
		content = packageContent+importContent+clazzFirstLineContent+
			filedContent+constructorContent+methodContent+"}";

		// 将构建好的源文件输出到指定目录下,与package对应
        File file =new File("c:\\com\\scorpios\\$Proxy.java");
        try {
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }

            if (!file.exists()) {
                file.createNewFile();
            }

            FileWriter fw = new FileWriter(file);
            fw.write(content);
            fw.flush();
            fw.close();


			// 将源文件构建好之后,需要将.java文件编译成.class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

            StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
            Iterable units = fileMgr.getJavaFileObjects(file);

            JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
            t.call();
            fileMgr.close();
			// 此处用URLClassLoader是因为文件是从磁盘加载,不是从项目路径加载的
            URL[] urls = new URL[]{new URL("file:c:\\\\")};
            URLClassLoader urlClassLoader = new URLClassLoader(urls);
            Class clazz = urlClassLoader.loadClass("com.scorpos.$Proxy");

			// 加载类之后,调用类的构造方法创建出代理对象
            Constructor constructor = clazz.getConstructor(CustomInvocationHandler.class);
            proxy = constructor.newInstance(h);
        }catch (Exception e){
            e.printStackTrace();
        }
        return proxy;
    }
}

下面就用ProxyUtil来测试一下,创建一个代理对象吧。

public interface UserDao {
    public String proxy() throws Exception;
}
public interface CustomInvocationHandler {
    public Object invoke(Method method);
}
public class TestCustomHandler implements CustomInvocationHandler {

	// 目标对象
    Object target;
    public TestCustomHandler(Object target){
        this.target=target;
    }

    @Override
    public Object invoke(Method method) {
        try {
            System.out.println("----------------");
            // 执行目标对象的方法
            return  method.invoke(target);
        } catch (Exception e) {
            e.printStackTrace();
        } 
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
        // 自定义
        UserDao proxy = (UserDao ) ProxyUtil.newInstance(UserDao.class,new TestCustomHandler(new UserDaoImpl()));
        try {
            proxy.proxy();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

使用ProxyUtil生成的类,这样是不是动态的生成了代理类啦。

package com.scorpios;
import com.scorpios.dao.UserDao;
import com.scorpios.dao.CustomInvocationHandler;
import java.lang.Exception;
import java.lang.reflect.Method;
public class $Proxy implements UserDao{
	private CustomInvocationHandler h;
	public $Proxy (CustomInvocationHandler h){
		this.h = h;
	}
	public String proxy()throws Exception {
		Method method = Class.forName("com.scorpios.dao.UserDao").getDeclaredMethod("proxy");
		return (String)h.invoke(method);
	}
}

上面就是手动模拟的一个动态代理创建的过程,从这个过程中,我们可以看出手动创建这种方式有如下的缺点,首先要生成文件、动态编译文件 class、需要一个URLClassLoader。

3、JDK动态代理

通过接口反射得到字节码,然后把字节码转成class。

public class Test {
    public static void main(String[] args) {

		// 生成的动态代理,将class字节码输入到文件
        byte[] bytes = ProxyGenerator.generateProxyClass("",new Class[]{UserDao.class});

        try {
            FileOutputStream fileOutputStream = new FileOutputStream("c:\\com\\scorpios\\$Proxy18.class");
            fileOutputStream.write(bytes);
            fileOutputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // Jdk的动态代理
		/**
		*	类加载器
		*	接口类
		*	InvocationHandler处理器,并把接口实现类传入
		**/
        UserDao jdkproxy = (UserDao) Proxy.newProxyInstance(Test.class.getClassLoader(),
                            new Class[]{UserDao.class},new TestCustomHandler(new UserDaoImpl()));
        try {
            jdkproxy.proxy();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JDK动态代理生成的字节码文件。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.scorpios.dao.UserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy18 extends Proxy implements UserDao {
    private static Method m3;

    public $Proxy18 (InvocationHandler var1) throws  {
        super(var1);
    }

    public final String proxy() throws Exception {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (Exception | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m3 = Class.forName("com.scorpios.dao.UserDao").getMethod("proxy");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

上面是使用JDK提供的方式,动态生成的代理对象类。从生成的代码中可以看出,继承了Proxy类,所以JDK的动态代理只能传递接口进去,而不能传递具体的实现类,因为Java的单继承。

四、小结

本文主要介绍了动态代理,并使用反射机制自己手动的实现了一个动态代理ProxyUtil,这对于我们理解JDK底层的动态代理机制有很好的帮助。

最后使用了JDK提供的动态代理生成了字节码文件,发现代理类会继承Proxy类,所以我们说,JDK提供的动态代理是基于接口的。

你可能感兴趣的:(设计模式)