关于代理模式,之前写过一篇是介绍静态代理的。在看这篇文章之前,建议先看一下之前静态代理这篇文章。
Java设计模式——代理模式【Proxy Pattern】 (静态代理)
关于代理,我们要明白两个概念:代理对象、目标对象。顾名思义,代理对象就是指增强后的对象;目标对象就是指需要被增强的对象。
我们实现静态代理的方式有两种:继承、聚合。
继承就是代理对象需要继承目标对象,这样代理对象就拥有了目标对象的所有方法,只需要重写目标对象中需要增强的方法即可实现静态代理。
// 目标对象
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。这样就会造成代理类非常多。
缺点:会代理类过多,非常复杂,会产生类爆炸
聚合的实现思路是:目标对象和代理对象实现同一个接口,代理对象当中要包含目标对象。
此处的代码,可以参看Java设计模式——代理模式【Proxy Pattern】 (静态代理)这篇文章中的示列代码。
聚合模式的静态代理缺点:也会产生类爆炸,只不过比继承少一点点
总结:如果在不确定的情况下,尽量不要去使用静态代理。因为一旦你写代码,就会产生类,一旦产生类就爆炸。
静态代理实现的两种方式里,都有一个共同缺点,就是随着业务类的增加,会产生很多很多的代理类,我们称之为类爆炸。那如何解决这个类爆炸的问题呢?
我们可以使用动态代理。那什么是动态代理呢?顾名思义,就是根据目标对象动态的创建我们需要的代理类。
动态代理就是在程序运行时,根据反射,动态的生成所需要的代理类。
在模拟动态代理之前,我们来想一下,在Java中,一个Java对象是如何被创建的?
如果不是很清楚,可以先去看一下,这篇文章一个Java对象是如何被创建的?
简单概括一下,就是我们需要编写一个Java源文件,然后把.java的源文件编译成.class文件,之后才能被类加载器加载到JVM中,最后我们就可以创建一个Java对象啦。
那动态代理实现的思路是啥呢?
我们可以通过接口反射生成一个类文件(Java源文件),然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,继而利用UrlclassLoader(因为这个动态产生的class不在工程当中所以需要使用UrlclassLoader)把这个动态编译的类加载到jvm当中,最后通过反射把这个类实例化。
这就是动态代理的实现思路,废话不多说喽~
手动模拟一个创建代理类的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。
通过接口反射得到字节码,然后把字节码转成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提供的动态代理是基于接口的。