代理模式的一切

代理模式的背景#

假如我们有一个用户实现接口,如UserService,和实现类UserServiceImpl。现在想在UserServiceImpl的某些方法前后打印参数日志,可以选择的方式有:

静态代理:#

  • 继承:写一个子类继承这个UserServiceImpl,然后方法前后加上日志功能。但是如果要再实现另外一个增强需求,就需要再次继承。或者对多个接口的方法同时进行增强,就要链式继承。长此以往产生类爆炸。

  • 聚合装饰模式。实现同一个接口UserService,装饰器的构造方法传入一个同样的类,进行包装。在包装方法里面进行前后的增强,再去调用父类。[Java的IO流使用了大量的装饰模式]。和上面的代理很像,但是装饰模式只能装饰一个特定的对象,在构造方法里面传进来,实现同样的接口进行增强。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * 装饰模式增强,实现相同接口
    */
    public class UserServiceDeractor implements UserService {
    UserService userService;

    /**
    * 构造方法传入同样的对象进行包装
    */
    public UserServiceDeractor(UserService userService){
    this.userService = userService;
    }

    @Override
    public User getUser(String name) {
    System.out.println("--------------装饰模式增强,传入参数"+name);
    return userService.getUser(name);
    }
    }
  • 缺点:实现起来简单,但是代理类会比较多,比较复杂。

动态代理#

基于以上的静态代理,会产生大量的class,如何改进?最浅显的想法就是

程序拼装这一段动态代理的java文件代码—>然后生成class字节码—>然后加载到项目中—>创建对象并使用。

人肉朴素思想的手动代理#

基于以上思想我们试着实现一个山寨版

先来一个函数接口,用于动态封装我们的代理增强功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

/**
* 动态代理接口
*/
@FunctionalInterface
public interface MyInvocationHandler {

/**
* 代理增强
* @param proxy 代理类
* @param method 代理方法
* @param target 目标包装对象
* @param args 参数
* @return
* @throws Exception
*/
public Object invoke(Object proxy, Method method,Object target, Object... args) throws Exception;
}

如果需要对某个对象进行增强,就写一个Handler接口的实现,然后在invoke方法中增强。

将这个invoke传入到我们下面的ProxyUtil中去创建一个代理对象:

  • public static <T> T getProxy(Object target, MyInvocationHandler handler, Class<T> ... interfaces) throws Exception 有三个参数,目标对象、增强实现(或者Lambda)、要实现的接口列表。
  • 这个方法会把所有target的public方法进行重新生成java,每个方法里面调用handler.invoke进行增强
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* 人工模拟动态代理, 不用任何第三方jar,也不用JDK
*/
public class ProxyUtil {

static JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();

/**
* 需要生成一个代理对象,必然要:
* 1.先得到java代码
* 2.然后把java代码变成class
* 3.然后把class变成对象
*
* 我们动态实现那个装饰模式的静态代理
* @param <T>
*/
public static <T> T getProxy(Object target, MyInvocationHandler handler, Class<T> ... interfaces) throws Exception {
//Class clazz = target.getClass();
if(interfaces == null || interfaces.length==0){
System.out.println("该对象没有实现接口,无法代理!");
return null;
}
//Class interfaceClazz = clazz.getInterfaces()[0];
System.out.println("即将针对接口"+interfaces+"进行动态代理...");
// 拼装java类
StringBuffer java = new StringBuffer();
String proxyPackage = "com.sam.proxy";
java.append("package "+proxyPackage+";\n\n");
// 引入接口
java.append(Arrays.stream(interfaces).map(c->"import "+c.getName()).collect(Collectors.joining(";\n"))+";\n");
java.append(
"import "+target.getClass().getName()+";\n" +
"import java.util.Arrays;\n" +
"import "+MyInvocationHandler.class.getName()+";\n\n" + // import MyInvocationHandler
"// TODO 这个类是由ProxyUtil自动生成的\n"+
// 接口$Myproxy 此处也可以使用
"public class MyProxyOf"+target.getClass().getSimpleName()+" implements "+ Arrays.stream(interfaces).map(Class::getSimpleName).collect(Collectors.joining(","))+" {\n" +
// private 接口 target
" private "+target.getClass().getSimpleName()+" target;\n" +
" private "+MyInvocationHandler.class.getSimpleName()+" handler;\n\n" +
// 构造方法包装
" public MyProxyOf"+target.getClass().getSimpleName()+"("+target.getClass().getSimpleName()+" target, "+MyInvocationHandler.class.getSimpleName()+" h){\n" +
" this.target = target;\n" +
" this.handler = h;\n" +
" }\n");
// 不用在每个方法处理,使用动态处理
for(Method method : target.getClass().getDeclaredMethods()){
// public com.xx.xx.User getUser(java.lang.String
java.append("\n\t@Override\n\tpublic "+method.getReturnType().getName()+" "+method.getName()+"(");

// String name){
List<String> params = IntStream.range(0,method.getParameterTypes().length).mapToObj(i->method.getParameterTypes()[i].getName() +" var"+i).collect(Collectors.toList());
java.append(String.join(", ",params));// java.lang.String var1, java.lang.Integer var2
java.append(")");
if(method.getExceptionTypes().length > 0){
java.append("thorws "+ Arrays.stream(method.getExceptionTypes()).map(Class::getName).collect(Collectors.joining(", ")));
}
java.append("{\n");
// 开始调用invoke或者lambda进行代理增强!
java.append("\t\tSystem.out.println(\"代理对象中即将调用invoke.....\");\n");
// 调用包装类target的方法,进行增强
java.append("\t\ttry{\n");
if(method.getParameterTypes().length == 0){
java.append("\t\t\t"+(method.getReturnType()==void.class?"":("return ("+method.getReturnType().getName()+")"))+"handler.invoke(this, target.getClass().getMethod(\""+method.getName()+"\"), target);\n");
}else{
List<String>vars = IntStream.range(0,method.getParameterTypes().length).mapToObj(i->"var"+i).collect(Collectors.toList());
List<String>paramClazz = Arrays.stream(method.getParameterTypes()).map(c->c.getName()+".class").collect(Collectors.toList());
java.append("\t\t\tClass[] paramClazz = new Class[]{"+String.join(",",paramClazz)+"};\n");
java.append("\t\t\t"+(method.getReturnType()==void.class?"":("return ("+method.getReturnType().getName()+")"))+"handler.invoke(this, target.getClass().getMethod(\""+method.getName()+"\", paramClazz), target, "+String.join(",",vars)+");\n");
}

java.append("\t\t}catch(Exception ex){\n");
//method.getExceptionTypes()
if(method.getExceptionTypes().length > 0){
java.append("\t\tList<Class> methodExs = Arrays.asList("+ String.join(",",Arrays.stream(method.getExceptionTypes()).map(c->c.getName()+".class").collect(Collectors.toList()))+");\n");
java.append("\t\t\tif(methodExs.contains(ex.getClass())){throw ex;}\n");
}
java.append("\t\t\tex.printStackTrace();\n");
if(method.getReturnType() != void.class){ // 异常时候返回null
java.append("\t\t\treturn null;\n");
}
java.append("\t\t}\n"); // 结束catch
java.append("\t}\n");// 结束方法
}
java.append("}\n");
//System.out.println(java);

// 落盘
String filePath = System.getProperty("user.dir")+"/src/main/java/"+proxyPackage.replaceAll("\\.","/");
String fileprefix = "MyProxyOf"+target.getClass().getSimpleName();
File dir = new File(filePath);
if(!dir.exists()){
dir.mkdirs();
}
File javaFile = new File(filePath + "/" +fileprefix +".java");
if(javaFile.exists()){
javaFile.delete();
}
FileWriter fw = new FileWriter(javaFile);
fw.write(java.toString());
fw.flush();
fw.close();
boolean result = compilerJavaFile(filePath + "/" + fileprefix +".java",System.getProperty("user.dir")+"/target/classes/");
if(result){
// 因为上一步编译到了当前工程的target中,在classpath里面,所以可以Class.forName
// TODO 如果是线上编译到一个类似/tmp目录,这里需要使用URLClassloader去LoadClass加载进来才行
Class tClass = Class.forName(proxyPackage+".MyProxyOf"+target.getClass().getSimpleName());
// 找到装饰模式的那个构造方法,传入装饰器包装的原始对象
return (T) tClass.getConstructor(target.getClass(),MyInvocationHandler.class).newInstance(target,handler);
}
return null;
}


/**
* 动态编译java文件到class字节码,需要把JDK/lib/tools.jar加入到环境变量中
* @param sourceFileInputPath
* @param classFileOutputPath
* @return
*/
public static boolean compilerJavaFile(String sourceFileInputPath, String classFileOutputPath) {
System.out.println("sourceFileInputPath="+sourceFileInputPath);
System.out.println("classFileOutputPath="+classFileOutputPath);
try{
// 设置编译选项,配置class文件输出路径
System.out.println("输出到:"+classFileOutputPath);
Iterable<String> options = Arrays.asList("-d", classFileOutputPath);
StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(new File(sourceFileInputPath)));
boolean flag = javaCompiler.getTask(null, fileManager, null, options, null, compilationUnits).call();
if(flag){
System.out.println("动态编译成功!");
}
return flag;
}catch (Exception ex){
ex.printStackTrace();
if(ex instanceof ClassNotFoundException){
System.out.println("动态编译失败!");
System.out.println("把JDK/lib/tools.jar加入到环境变量中");
}

return false;
}

}

public static void main(String[] args) throws Exception{
UserService instance = getProxy(new UserServiceImpl(), (proxy,method,target,params)->{ System.out.println("在lambda中的增强代理.....");return method.invoke(target,params); }, UserService.class);
System.out.println("-----------------开始运行");
instance.addUser(new User("wangwu",20));
}
}

生成效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// TODO 这个类是由ProxyUtil自动生成的
public class MyProxyOfUserServiceImpl implements UserService {
private UserServiceImpl target;
private MyInvocationHandler handler;

public MyProxyOfUserServiceImpl(UserServiceImpl target, MyInvocationHandler h){
this.target = target;
this.handler = h;
}

@Override
public com.sam.bootdemo.model.User getUser(java.lang.String var0){
System.out.println("代理对象中即将调用invoke.....");
try{
Class[] paramClazz = new Class[]{java.lang.String.class};
return (com.sam.bootdemo.model.User)handler.invoke(this, target.getClass().getMethod("getUser", paramClazz), target, var0);
}catch(Exception ex){
ex.printStackTrace();
return null;
}
}

@Override
public void addUser(com.sam.bootdemo.model.User var0){
System.out.println("代理对象中即将调用invoke.....");
try{
Class[] paramClazz = new Class[]{com.sam.bootdemo.model.User.class};
handler.invoke(this, target.getClass().getMethod("addUser", paramClazz), target, var0);
}catch(Exception ex){
ex.printStackTrace();
}
}

@Override
public void initService(){
System.out.println("代理对象中即将调用invoke.....");
try{
handler.invoke(this, target.getClass().getMethod("initService"), target);
}catch(Exception ex){
ex.printStackTrace();
}
}
}

可以正常运行并输出:

1
2
3
4
5
6
7
8
动态编译成功!
-----------------开始运行
代理对象中即将调用invoke.....
在lambda中的增强代理.....
UserServiceImpl假装addUser:User(userName=wangwu, age=20)

Process finished with exit code 0

JDK 动态代理#

JDK动态代理使用InvocationHandler实现,和我们上面的思想很像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 实现一个增强器,来拦截我们包装target的方法,进行业务增强
public class UserServiceInvocationHandler implements InvocationHandler {
private Object target;

public UserServiceInvocationHandler(Object target){
this.target = target;
}

/**
* 代理方法的增强器
* 调用代理对象的业务对象的时候会来执行这个方法
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("-------UserServiceInvocationHandler.invoke代理增强");
// 此处进行拦截增强
return method.invoke(target, args); // 执行target的真正业务逻辑
}
}

测试JDK的动态代理:

1
2
3
4
5
6
7
8
9
System.out.println("===================JDKProxy InvocationHandler=========================");
// jdk动态代理
// 为啥要classloader?因为JVM启动的时候已经加载了project的所有class。
// 但是项目运行过程中动态生成了calss,所以要传入classloader去加载这个class。
// 为啥不是URLClassLoader去远程加载?因为JDK动态代理产生的项目是在classpath下的
// 传入classloader、接口、和要增强的InvocationHandler
UserService service3 = (UserService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{UserService.class}, new UserServiceInvocationHandler(new UserServiceImpl()));
service3.getUser("小马");

原理:

  • Class使用来描述一个类的,看起来是废话。但是很重要,仔细体会。

  • Class对象如 Class userClazz = Clazz.forname("com.xxx.User")就可以拿到User类的详细信息,包括属性、方法、构造方法等等。

  • 一个java文件--->编译成class文件--->解析成byte[]到JVM中--->构建为类对象Class----->newInstance变成实例

  • 判断两个对象是否相等,首先判断类加载器是否同一个,不是的话就不相等。这块动态代理判断了。传进去的接口列表使用用户的ClassLoader先加载一遍forName的结果,看看和传进来的是否相同。

  • 默认生成的代理类在com.sun.proxy这个包名下如com.sun.proxy.$Proxy0,除非有interface不是public的,会生成到这个interface同包名下(否则无法外部implements访问到)。

1
2
//java.lang.reflect.Proxy中获取到包名后生成class字节流的方法,使用了native方法生成
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

结果

JDK上面那一步动态生成的类,我们反编译后看一眼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

import com.sam.bootdemo.model.User;
import com.sam.bootdemo.service.UserService;
import java.io.FileNotFoundException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.SQLException;

// 继承了JDK的Proxy,所以不能继承我们的目标对象,只能是实现接口
public class UserServiceProxy extends Proxy implements UserService {
private static Method m1;
private static Method m3;
private static Method m5;
private static Method m2;
private static Method m4;
private static Method m0;

// 反射获取了我们UserService接口中需要覆盖的方法,同时反射拿到要覆盖的hashCode、equals、toString方法
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.sam.bootdemo.service.UserService").getMethod("initService");
m5 = Class.forName("com.sam.bootdemo.service.UserService").getMethod("addUser", Class.forName("com.sam.bootdemo.model.User"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.sam.bootdemo.service.UserService").getMethod("getUser", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}

// 构造方法传入了我们写的增强类InvocationHandler,塞到父类Proxy中了
// 这个handler里面有包装我们的原始userServiceImpl对象
public UserServiceProxy(InvocationHandler var1) throws {
super(var1);
}

// 因为实现了同样的UserService接口,这里代理实现
public final User getUser(String var1) throws {
try {
// 调用了invocationHandler的invoke方法,传入 代理对象、method、参数。(但是缺少真正的target,target在invocationhandler中,也就是我们要增强的userServiceImpl对象)
return (User)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

// 同上
public final void initService() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

// 同上
public final void addUser(User var1) throws FileNotFoundException, SQLException {
try {
super.h.invoke(this, m5, new Object[]{var1});
} catch (RuntimeException | FileNotFoundException | SQLException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

// toString、equals、hashCode也调用了invocationHandler的invoke方法。
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

}

可以发现JDK生成代理类的逻辑和我们之前手动山寨版的很像。只是JDK是调用native方法直接生成字节流数组。我们是拼装java,再动态编译为class的。

虽然是native方法,但JDK也是通过反射生成的,他反射读取了我们接口中的方法列表,逐个实现,然后生成到class流里的。

缺点: 必须要有接口,才能生成动态代理。如果对象没有接口就无法进行代理。

JDK动态代理为什是接口不是继承?#

因为Java是单继承的,JDK底层源码已经继承了proxy对象, 里面存放了invocationHandler(invocationHandler里面包装了我们的目标对象)。所以不能再继承我们的目标对象。

只能去和目标对象实现相同的接口,包装一下,具有相同行为。

CGlib#

使用基于继承的方式,使用ASM进行字节码操作完成代理增强。都是直接操作字节码和JDK比起来性能差异不大。

Javassist#

也可以实现字节码增强的代理,使用不太多。