Archerda's Blog

Programmer. Meditating.


  • 首页

  • 归档

  • 标签

深入理解动态代理

发表于 2018-12-06

深入理解动态代理

代理模式

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为”代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不看到的内容和服务或者添加客户需要的额外服务。

通过引入一个新的对象(如小图片和远程代理对象)来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。

静态代理

  • 程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就已经确定了;
  • 优点
    • 业务类只需要关注业务逻辑本身,保证了业务类的重用性;
  • 缺点
    • 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了;
    • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度;

动态代理

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定;

Java动态代理

类加载机制

动态代理加载机制

JDK

关键代码

  • [创建调用处理类] 实现 java.lang.reflect.InvocationHandler接口,实现它的 java.lang.reflect.InvocationHandler#invoke(Object proxy,Method method, Object[] args) throws Throwable接口;
  • [创建动态代理类实例] 调用
    java.lang.reflect.Proxy#newProxyInstance(ClassLoaderloader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException 方法,传递上面创建的InvocationHandler实例, 创建动态代理实例;
  • jdk动态代理类: public final class $Proxy0 extends Proxy implements TargetInterface

jdk动态代理的机制特点

  • 包:如果所代理的接口都是 public的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了com.ibm.developerworks 包中的某非 public 接口A,那么新生成的代理类所在的包就是com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
  • 类修饰符:该代理类具有 final 和 public修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
  • 类名:格式是”\$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用Proxy 的静态方法创建动态代理类都会使得 N值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率;

代理类实例的特点

  • 每个实例都会关联一个调用处理器对象,可以通过 Proxy 提供的静态方法getInvocationHandler 去获得代理类实例的调用处理器对象;
  • 在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的invoke 方法执行,此外,值得注意的是,委托类的根类 java.lang.Object中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是hashCode,equals 和 toString,可能的原因有:一是因为这些方法为public 且非 final类型,能够被代理类覆盖;二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行;
  • 当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型;

被代理的一组接口的特点

  • 首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误;
  • 其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败;
  • 再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败;
  • 最后,接口的数目不能超过 65535,这是 JVM 设定的限制;

异常处理方面的特点

  • 从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于Throwable接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛Throwable 异常。那么如果在 invoke方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java动态代理类已经为我们设计好了解决方法:它将会抛出UndeclaredThrowableException 异常。这个异常是一个 RuntimeException类型,所以不会引起编译错误。通过该异常的 getCause方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。

动态代理的优点

  • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转;

动态代理的不足

  • 诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class的动态代理,原因是多继承在 Java 中本质上就行不通。

Cglib

cglib是针对类来实现代理的,基于ASM的包装,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

cglib 创建某个类A的动态代理类的模式是:

  1. 查找A上的所有非final 的public类型的方法定义;

  2. 将这些方法的定义转换成字节码;

  3. 将组成的字节码转换成相应的代理的class对象;

  4. 实现 MethodInterceptor接口,用来处理
    对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

差异

  • JDK动态代理
    • 其代理的对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
    • 通过反射来实现对目标方法的调用,效率低一些;
  • CGLIG动态代理
    • 无法通知(advice)final方法,因为它不能被覆写;
    • 真实对象的引用,可以直接调用,效率更高;

Javassist

javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

使用Javassist有两种方式来实现动态代理,一种是使用代理工厂创建,和普通的JDK动态代理和CGLIB类似,另一种则可以使用字节码技术创建。

ASM

ASM 是一个 Java字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM可以直接产生二进制 class 文件,也可以在类被加载入 Java虚拟机之前动态改变类行为。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

不过ASM在创建class字节码的过程中,操纵的级别是底层JVM的汇编指令级别,这要求ASM使用者要对class组织结构和JVM汇编指令有一定的了解。

性能比较

ASM > Javassist Bytecode > Cglib > JDK > Javassist ProxyFactory

  • 差异原因
    • 各方案生成的字节码不一样, 像JDK和CGLIB都考虑了很多因素,以及继承或包装了自己的一些类,所以生成的字节码非常大,而我们很多时候用不上这些, 而手工生成的字节码非常小,所以速度快。

参考文档

  • 图解设计模式:代理模式
  • 动态代理方案性能对比
  • Java动态代理机制详解(JDK
    和CGLIB,Javassist,ASM)

附录

公用类

1
2
3
4
5
6
7
8
9
package com.github.archerda.designpattern.proxy;
/**
* @author archerda
* @date 2018/7/9
*/
public interface TargetInterface {
void sayHi();
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.github.archerda.designpattern.proxy;
/**
* @author archerda
* @date 2018/04/24
*/
public class TargetClass {
public void sayHi() {
System.out.println("Hi");
}
}

静态代理 demo

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
package com.github.archerda.designpattern.proxy;
/**
* @author archerda
*/
public class StaticMain {
public static void main(String[] args) {
TargetInterface targetInterface = new TargetInterfaceImpl();
TargetInterface target = new ProxyClass(targetInterface);
// Client
target.sayHi();
}
private static class TargetInterfaceImpl implements TargetInterface {
@Override
public void sayHi() {
System.out.println("Hi");
}
}
public static class ProxyClass implements TargetInterface {
// 存储真实对象
private TargetInterface targetInterface;
public ProxyClass(TargetInterface targetInterface) {
this.targetInterface = targetInterface;
}
@Override
public void sayHi() {
System.out.println("Invoke start.");
targetInterface.sayHi();
System.out.println("Invoke end.");
}
}
}

JDK demo

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
package com.github.archerda.designpattern.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK动态代理Demo;
*
* @author archerda
* @date 2018/01/08
*/
public class JdkMain {
public static void main(String[] args) {
/*
生成$Proxy0的class文件
*/
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
TargetInterface targetInterface = new TargetInterfaceImpl();
/*
生成一个代理类,这个代理类签名如下:public final class $Proxy0 extends Proxy implements TargetInterface
它继承了 Proxy 类(这也是JDK代理不能代理类的原因),并实现了 TargetInterface 接口;
并且这个类是 final 的,不能被继承;
类名:$Proxy0;($Proxy + 自增数字)
*/
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(targetInterface.getClass().getClassLoader(),
targetInterface.getClass().getInterfaces(), new Handler(targetInterface));
/*
调用ProxyHolder的sayHi方法,JVM会把这个方法转交给 InvocationHandler 的 invoke 方法;
实际的调用方法如下:
public final void sayHi() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
*/
proxy.sayHi();
}
private static class TargetInterfaceImpl implements TargetInterface {
@Override
public void sayHi() {
System.out.println("Hi");
}
}
private static class Handler implements InvocationHandler {
/**
* JDK的代理,Handler要持有一个实例的引用;
*/
private TargetInterface targetInterface;
public Handler(TargetInterface targetInterface) {
this.targetInterface = targetInterface;
}
/**
* @param proxy 代理对象实例,比如"@Proxy0"
* @param method 原始方法(被拦截的方法),比如"public abstract void com.github.archerda.designpattern.proxy.TargetInterface.sayHi()"
* @param args 参数集(被拦截方法的参数);
* @return Object
* @throws Throwable 异常;
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Invoke start.");
// 调用 ProxyInstance 的方法;
// 注意,这里 invoke 方法的 第一个参数,必须是ProxyInstance,如果是ProxyHolder,将会出现死循环;
// method.invoke(proxy, args); 死循环,因为JVM会一直委托给该方法;
method.invoke(targetInterface, args);
System.out.println("Invoke end.");
return null;
}
}
}

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.github.archerda.designpattern.proxy.TargetInterface;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements TargetInterface {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
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 void sayHi() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
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 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);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.github.archerda.designpattern.proxy.TargetInterface").getMethod("sayHi");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

Cglib demo

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
package com.github.archerda.designpattern.proxy;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB动态代理Demo;
*
* @author archerda
* @date 2018/04/24
*/
public class CglibMain {
public static void main(String[] args) {
/*
生成代理类的class文件;
*/
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/Archerda/Desktop/java-example/com/sun/proxy");
/*
生成Cglib的增强器;
*/
Enhancer enhancer = new Enhancer();
/*
给增强器设置父类;
*/
enhancer.setSuperclass(TargetClass.class);
/*
给增强器设置会拦截器;
*/
enhancer.setCallback(new Interceptor());
/*
使用增强器创建代理类;
*/
TargetClass target = (TargetClass) enhancer.create();
/*
调用代理类方法;
*/
target.sayHi();
}
private static class Interceptor implements MethodInterceptor {
/**
*
* @param o 代理对象,比如:TargetClass$EnhancerByCGLIB$72cfe419
* @param method 原始方法(要被拦截的方法);比如:public void com.github.archerda.designpattern.proxy.TargetClass.sayHi()
* @param objects 参数集;
* @param methodProxy 代理方法(要触发父类的方法对象);
* @return Object
* @throws Throwable 异常
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Invoke start");
/*
这里不能使用 method#invoke 或者 methodProxy#invoke,否则可能会导致死循环;
*/
methodProxy.invokeSuper(o, objects);
System.out.println("Invoke end.");
return null;
}
}
}

Cglib动态代理字节码源码

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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.github.archerda.designpattern.proxy;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class TargetClass$$EnhancerByCGLIB$$92457a4a extends TargetClass implements Factory {
private boolean CGLIB$BOUND;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static final Method CGLIB$sayHi$0$Method;
private static final MethodProxy CGLIB$sayHi$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$finalize$1$Method;
private static final MethodProxy CGLIB$finalize$1$Proxy;
private static final Method CGLIB$equals$2$Method;
private static final MethodProxy CGLIB$equals$2$Proxy;
private static final Method CGLIB$toString$3$Method;
private static final MethodProxy CGLIB$toString$3$Proxy;
private static final Method CGLIB$hashCode$4$Method;
private static final MethodProxy CGLIB$hashCode$4$Proxy;
private static final Method CGLIB$clone$5$Method;
private static final MethodProxy CGLIB$clone$5$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.github.archerda.designpattern.proxy.TargetClass$$EnhancerByCGLIB$$92457a4a");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$finalize$1$Method = var10000[0];
CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
CGLIB$equals$2$Method = var10000[1];
CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
CGLIB$toString$3$Method = var10000[2];
CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
CGLIB$hashCode$4$Method = var10000[3];
CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
CGLIB$clone$5$Method = var10000[4];
CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
CGLIB$sayHi$0$Method = ReflectUtils.findMethods(new String[]{"sayHi", "()V"}, (var1 = Class.forName("com.github.archerda.designpattern.proxy.TargetClass")).getDeclaredMethods())[0];
CGLIB$sayHi$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHi", "CGLIB$sayHi$0");
}
final void CGLIB$sayHi$0() {
super.sayHi();
}
public final void sayHi() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$sayHi$0$Method, CGLIB$emptyArgs, CGLIB$sayHi$0$Proxy);
} else {
super.sayHi();
}
}
final void CGLIB$finalize$1() throws Throwable {
super.finalize();
}
protected final void finalize() throws Throwable {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$finalize$1$Method, CGLIB$emptyArgs, CGLIB$finalize$1$Proxy);
} else {
super.finalize();
}
}
final boolean CGLIB$equals$2(Object var1) {
return super.equals(var1);
}
public final boolean equals(Object var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var2 = var10000.intercept(this, CGLIB$equals$2$Method, new Object[]{var1}, CGLIB$equals$2$Proxy);
return var2 == null ? false : (Boolean)var2;
} else {
return super.equals(var1);
}
}
final String CGLIB$toString$3() {
return super.toString();
}
public final String toString() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$3$Method, CGLIB$emptyArgs, CGLIB$toString$3$Proxy) : super.toString();
}
final int CGLIB$hashCode$4() {
return super.hashCode();
}
public final int hashCode() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var1 = var10000.intercept(this, CGLIB$hashCode$4$Method, CGLIB$emptyArgs, CGLIB$hashCode$4$Proxy);
return var1 == null ? 0 : ((Number)var1).intValue();
} else {
return super.hashCode();
}
}
final Object CGLIB$clone$5() throws CloneNotSupportedException {
return super.clone();
}
protected final Object clone() throws CloneNotSupportedException {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? var10000.intercept(this, CGLIB$clone$5$Method, CGLIB$emptyArgs, CGLIB$clone$5$Proxy) : super.clone();
}
public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
String var10000 = var0.toString();
switch(var10000.hashCode()) {
case -2012941911:
if (var10000.equals("sayHi()V")) {
return CGLIB$sayHi$0$Proxy;
}
break;
case -1574182249:
if (var10000.equals("finalize()V")) {
return CGLIB$finalize$1$Proxy;
}
break;
case -508378822:
if (var10000.equals("clone()Ljava/lang/Object;")) {
return CGLIB$clone$5$Proxy;
}
break;
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
return CGLIB$equals$2$Proxy;
}
break;
case 1913648695:
if (var10000.equals("toString()Ljava/lang/String;")) {
return CGLIB$toString$3$Proxy;
}
break;
case 1984935277:
if (var10000.equals("hashCode()I")) {
return CGLIB$hashCode$4$Proxy;
}
}
return null;
}
public TargetClass$$EnhancerByCGLIB$$92457a4a() {
CGLIB$BIND_CALLBACKS(this);
}
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
CGLIB$THREAD_CALLBACKS.set(var0);
}
public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
CGLIB$STATIC_CALLBACKS = var0;
}
private static final void CGLIB$BIND_CALLBACKS(Object var0) {
TargetClass$$EnhancerByCGLIB$$92457a4a var1 = (TargetClass$$EnhancerByCGLIB$$92457a4a)var0;
if (!var1.CGLIB$BOUND) {
var1.CGLIB$BOUND = true;
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
if (var10000 == null) {
var10000 = CGLIB$STATIC_CALLBACKS;
if (var10000 == null) {
return;
}
}
var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
}
}
public Object newInstance(Callback[] var1) {
CGLIB$SET_THREAD_CALLBACKS(var1);
TargetClass$$EnhancerByCGLIB$$92457a4a var10000 = new TargetClass$$EnhancerByCGLIB$$92457a4a();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}
public Object newInstance(Callback var1) {
CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
TargetClass$$EnhancerByCGLIB$$92457a4a var10000 = new TargetClass$$EnhancerByCGLIB$$92457a4a();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}
public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
CGLIB$SET_THREAD_CALLBACKS(var3);
TargetClass$$EnhancerByCGLIB$$92457a4a var10000 = new TargetClass$$EnhancerByCGLIB$$92457a4a;
switch(var1.length) {
case 0:
var10000.<init>();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
default:
throw new IllegalArgumentException("Constructor not found");
}
}
public Callback getCallback(int var1) {
CGLIB$BIND_CALLBACKS(this);
MethodInterceptor var10000;
switch(var1) {
case 0:
var10000 = this.CGLIB$CALLBACK_0;
break;
default:
var10000 = null;
}
return var10000;
}
public void setCallback(int var1, Callback var2) {
switch(var1) {
case 0:
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
default:
}
}
public Callback[] getCallbacks() {
CGLIB$BIND_CALLBACKS(this);
return new Callback[]{this.CGLIB$CALLBACK_0};
}
public void setCallbacks(Callback[] var1) {
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
}
static {
CGLIB$STATICHOOK1();
}
}
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.github.archerda.designpattern.proxy;
import com.github.archerda.designpattern.proxy.TargetClass..EnhancerByCGLIB..92457a4a;
import java.lang.reflect.InvocationTargetException;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.reflect.FastClass;
public class TargetClass$$EnhancerByCGLIB$$92457a4a$$FastClassByCGLIB$$b8e5bdfc extends FastClass {
public TargetClass$$EnhancerByCGLIB$$92457a4a$$FastClassByCGLIB$$b8e5bdfc(Class var1) {
super(var1);
}
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -2055565910:
if (var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
return 10;
}
break;
case -2012941911:
if (var10000.equals("sayHi()V")) {
return 6;
}
break;
case -1937024608:
if (var10000.equals("CGLIB$sayHi$0()V")) {
return 14;
}
break;
case -1725733088:
if (var10000.equals("getClass()Ljava/lang/Class;")) {
return 24;
}
break;
case -1457535688:
if (var10000.equals("CGLIB$STATICHOOK1()V")) {
return 19;
}
break;
case -1411812934:
if (var10000.equals("CGLIB$hashCode$4()I")) {
return 18;
}
break;
case -1026001249:
if (var10000.equals("wait(JI)V")) {
return 21;
}
break;
case -894172689:
if (var10000.equals("newInstance(Lnet/sf/cglib/proxy/Callback;)Ljava/lang/Object;")) {
return 4;
}
break;
case -623122092:
if (var10000.equals("CGLIB$findMethodProxy(Lnet/sf/cglib/core/Signature;)Lnet/sf/cglib/proxy/MethodProxy;")) {
return 13;
}
break;
case -419626537:
if (var10000.equals("setCallbacks([Lnet/sf/cglib/proxy/Callback;)V")) {
return 8;
}
break;
case 243996900:
if (var10000.equals("wait(J)V")) {
return 22;
}
break;
case 374345669:
if (var10000.equals("CGLIB$equals$2(Ljava/lang/Object;)Z")) {
return 16;
}
break;
case 560567118:
if (var10000.equals("setCallback(ILnet/sf/cglib/proxy/Callback;)V")) {
return 7;
}
break;
case 811063227:
if (var10000.equals("newInstance([Ljava/lang/Class;[Ljava/lang/Object;[Lnet/sf/cglib/proxy/Callback;)Ljava/lang/Object;")) {
return 3;
}
break;
case 946854621:
if (var10000.equals("notifyAll()V")) {
return 26;
}
break;
case 973717575:
if (var10000.equals("getCallbacks()[Lnet/sf/cglib/proxy/Callback;")) {
return 12;
}
break;
case 1116248544:
if (var10000.equals("wait()V")) {
return 23;
}
break;
case 1221173700:
if (var10000.equals("newInstance([Lnet/sf/cglib/proxy/Callback;)Ljava/lang/Object;")) {
return 5;
}
break;
case 1230699260:
if (var10000.equals("getCallback(I)Lnet/sf/cglib/proxy/Callback;")) {
return 11;
}
break;
case 1365077639:
if (var10000.equals("CGLIB$finalize$1()V")) {
return 15;
}
break;
case 1517819849:
if (var10000.equals("CGLIB$toString$3()Ljava/lang/String;")) {
return 17;
}
break;
case 1584330438:
if (var10000.equals("CGLIB$SET_STATIC_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
return 9;
}
break;
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
return 0;
}
break;
case 1902039948:
if (var10000.equals("notify()V")) {
return 25;
}
break;
case 1913648695:
if (var10000.equals("toString()Ljava/lang/String;")) {
return 1;
}
break;
case 1984935277:
if (var10000.equals("hashCode()I")) {
return 2;
}
break;
case 2011844968:
if (var10000.equals("CGLIB$clone$5()Ljava/lang/Object;")) {
return 20;
}
}
return -1;
}
public int getIndex(String var1, Class[] var2) {
switch(var1.hashCode()) {
case -2083498450:
if (var1.equals("CGLIB$finalize$1")) {
switch(var2.length) {
case 0:
return 15;
}
}
break;
case -1776922004:
if (var1.equals("toString")) {
switch(var2.length) {
case 0:
return 1;
}
}
break;
case -1334646347:
if (var1.equals("CGLIB$sayHi$0")) {
switch(var2.length) {
case 0:
return 14;
}
}
break;
case -1295482945:
if (var1.equals("equals")) {
switch(var2.length) {
case 1:
if (var2[0].getName().equals("java.lang.Object")) {
return 0;
}
}
}
break;
case -1053468136:
if (var1.equals("getCallbacks")) {
switch(var2.length) {
case 0:
return 12;
}
}
break;
case -1039689911:
if (var1.equals("notify")) {
switch(var2.length) {
case 0:
return 25;
}
}
break;
case -124978608:
if (var1.equals("CGLIB$equals$2")) {
switch(var2.length) {
case 1:
if (var2[0].getName().equals("java.lang.Object")) {
return 16;
}
}
}
break;
case -60403779:
if (var1.equals("CGLIB$SET_STATIC_CALLBACKS")) {
switch(var2.length) {
case 1:
if (var2[0].getName().equals("[Lnet.sf.cglib.proxy.Callback;")) {
return 9;
}
}
}
break;
case -29025554:
if (var1.equals("CGLIB$hashCode$4")) {
switch(var2.length) {
case 0:
return 18;
}
}
break;
case 3641717:
if (var1.equals("wait")) {
switch(var2.length) {
case 0:
return 23;
case 1:
if (var2[0].getName().equals("long")) {
return 22;
}
break;
case 2:
if (var2[0].getName().equals("long") && var2[1].getName().equals("int")) {
return 21;
}
}
}
break;
case 85179481:
if (var1.equals("CGLIB$SET_THREAD_CALLBACKS")) {
switch(var2.length) {
case 1:
if (var2[0].getName().equals("[Lnet.sf.cglib.proxy.Callback;")) {
return 10;
}
}
}
break;
case 109213260:
if (var1.equals("sayHi")) {
switch(var2.length) {
case 0:
return 6;
}
}
break;
case 147696667:
if (var1.equals("hashCode")) {
switch(var2.length) {
case 0:
return 2;
}
}
break;
case 161998109:
if (var1.equals("CGLIB$STATICHOOK1")) {
switch(var2.length) {
case 0:
return 19;
}
}
break;
case 495524492:
if (var1.equals("setCallbacks")) {
switch(var2.length) {
case 1:
if (var2[0].getName().equals("[Lnet.sf.cglib.proxy.Callback;")) {
return 8;
}
}
}
break;
case 1154623345:
if (var1.equals("CGLIB$findMethodProxy")) {
switch(var2.length) {
case 1:
if (var2[0].getName().equals("net.sf.cglib.core.Signature")) {
return 13;
}
}
}
break;
case 1543336190:
if (var1.equals("CGLIB$toString$3")) {
switch(var2.length) {
case 0:
return 17;
}
}
break;
case 1811874389:
if (var1.equals("newInstance")) {
switch(var2.length) {
case 1:
String var10001 = var2[0].getName();
switch(var10001.hashCode()) {
case -845341380:
if (var10001.equals("net.sf.cglib.proxy.Callback")) {
return 4;
}
break;
case 1730110032:
if (var10001.equals("[Lnet.sf.cglib.proxy.Callback;")) {
return 5;
}
}
case 2:
default:
break;
case 3:
if (var2[0].getName().equals("[Ljava.lang.Class;") && var2[1].getName().equals("[Ljava.lang.Object;") && var2[2].getName().equals("[Lnet.sf.cglib.proxy.Callback;")) {
return 3;
}
}
}
break;
case 1817099975:
if (var1.equals("setCallback")) {
switch(var2.length) {
case 2:
if (var2[0].getName().equals("int") && var2[1].getName().equals("net.sf.cglib.proxy.Callback")) {
return 7;
}
}
}
break;
case 1902066072:
if (var1.equals("notifyAll")) {
switch(var2.length) {
case 0:
return 26;
}
}
break;
case 1905679803:
if (var1.equals("getCallback")) {
switch(var2.length) {
case 1:
if (var2[0].getName().equals("int")) {
return 11;
}
}
}
break;
case 1950568386:
if (var1.equals("getClass")) {
switch(var2.length) {
case 0:
return 24;
}
}
break;
case 1951977611:
if (var1.equals("CGLIB$clone$5")) {
switch(var2.length) {
case 0:
return 20;
}
}
}
return -1;
}
public int getIndex(Class[] var1) {
switch(var1.length) {
case 0:
return 0;
default:
return -1;
}
}
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
92457a4a var10000 = (92457a4a)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
return new Boolean(var10000.equals(var3[0]));
case 1:
return var10000.toString();
case 2:
return new Integer(var10000.hashCode());
case 3:
return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
case 4:
return var10000.newInstance((Callback)var3[0]);
case 5:
return var10000.newInstance((Callback[])var3[0]);
case 6:
var10000.sayHi();
return null;
case 7:
var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
return null;
case 8:
var10000.setCallbacks((Callback[])var3[0]);
return null;
case 9:
92457a4a.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]);
return null;
case 10:
92457a4a.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]);
return null;
case 11:
return var10000.getCallback(((Number)var3[0]).intValue());
case 12:
return var10000.getCallbacks();
case 13:
return 92457a4a.CGLIB$findMethodProxy((Signature)var3[0]);
case 14:
var10000.CGLIB$sayHi$0();
return null;
case 15:
var10000.CGLIB$finalize$1();
return null;
case 16:
return new Boolean(var10000.CGLIB$equals$2(var3[0]));
case 17:
return var10000.CGLIB$toString$3();
case 18:
return new Integer(var10000.CGLIB$hashCode$4());
case 19:
92457a4a.CGLIB$STATICHOOK1();
return null;
case 20:
return var10000.CGLIB$clone$5();
case 21:
var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
return null;
case 22:
var10000.wait(((Number)var3[0]).longValue());
return null;
case 23:
var10000.wait();
return null;
case 24:
return var10000.getClass();
case 25:
var10000.notify();
return null;
case 26:
var10000.notifyAll();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
public Object newInstance(int var1, Object[] var2) throws InvocationTargetException {
92457a4a var10000 = new 92457a4a;
92457a4a var10001 = var10000;
int var10002 = var1;
try {
switch(var10002) {
case 0:
var10001.<init>();
return var10000;
}
} catch (Throwable var3) {
throw new InvocationTargetException(var3);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
public int getMaxIndex() {
return 26;
}
}
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.github.archerda.designpattern.proxy;
import java.lang.reflect.InvocationTargetException;
import net.sf.cglib.core.Signature;
import net.sf.cglib.reflect.FastClass;
public class TargetClass$$FastClassByCGLIB$$4e2d847b extends FastClass {
public TargetClass$$FastClassByCGLIB$$4e2d847b(Class var1) {
super(var1);
}
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case -2012941911:
if (var10000.equals("sayHi()V")) {
return 0;
}
break;
case -1725733088:
if (var10000.equals("getClass()Ljava/lang/Class;")) {
return 7;
}
break;
case -1026001249:
if (var10000.equals("wait(JI)V")) {
return 1;
}
break;
case 243996900:
if (var10000.equals("wait(J)V")) {
return 2;
}
break;
case 946854621:
if (var10000.equals("notifyAll()V")) {
return 9;
}
break;
case 1116248544:
if (var10000.equals("wait()V")) {
return 3;
}
break;
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
return 4;
}
break;
case 1902039948:
if (var10000.equals("notify()V")) {
return 8;
}
break;
case 1913648695:
if (var10000.equals("toString()Ljava/lang/String;")) {
return 5;
}
break;
case 1984935277:
if (var10000.equals("hashCode()I")) {
return 6;
}
}
return -1;
}
public int getIndex(String var1, Class[] var2) {
switch(var1.hashCode()) {
case -1776922004:
if (var1.equals("toString")) {
switch(var2.length) {
case 0:
return 5;
}
}
break;
case -1295482945:
if (var1.equals("equals")) {
switch(var2.length) {
case 1:
if (var2[0].getName().equals("java.lang.Object")) {
return 4;
}
}
}
break;
case -1039689911:
if (var1.equals("notify")) {
switch(var2.length) {
case 0:
return 8;
}
}
break;
case 3641717:
if (var1.equals("wait")) {
switch(var2.length) {
case 0:
return 3;
case 1:
if (var2[0].getName().equals("long")) {
return 2;
}
break;
case 2:
if (var2[0].getName().equals("long") && var2[1].getName().equals("int")) {
return 1;
}
}
}
break;
case 109213260:
if (var1.equals("sayHi")) {
switch(var2.length) {
case 0:
return 0;
}
}
break;
case 147696667:
if (var1.equals("hashCode")) {
switch(var2.length) {
case 0:
return 6;
}
}
break;
case 1902066072:
if (var1.equals("notifyAll")) {
switch(var2.length) {
case 0:
return 9;
}
}
break;
case 1950568386:
if (var1.equals("getClass")) {
switch(var2.length) {
case 0:
return 7;
}
}
}
return -1;
}
public int getIndex(Class[] var1) {
switch(var1.length) {
case 0:
return 0;
default:
return -1;
}
}
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
TargetClass var10000 = (TargetClass)var2;
int var10001 = var1;
try {
switch(var10001) {
case 0:
var10000.sayHi();
return null;
case 1:
var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
return null;
case 2:
var10000.wait(((Number)var3[0]).longValue());
return null;
case 3:
var10000.wait();
return null;
case 4:
return new Boolean(var10000.equals(var3[0]));
case 5:
return var10000.toString();
case 6:
return new Integer(var10000.hashCode());
case 7:
return var10000.getClass();
case 8:
var10000.notify();
return null;
case 9:
var10000.notifyAll();
return null;
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
public Object newInstance(int var1, Object[] var2) throws InvocationTargetException {
TargetClass var10000 = new TargetClass;
TargetClass var10001 = var10000;
int var10002 = var1;
try {
switch(var10002) {
case 0:
var10001.<init>();
return var10000;
}
} catch (Throwable var3) {
throw new InvocationTargetException(var3);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
public int getMaxIndex() {
return 9;
}
}

Javassist demo

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
package com.github.archerda.designpattern.proxy;
import javassist.*;
import java.io.File;
import java.io.FileOutputStream;
/**
* Javassist动态代理: 采用 字节码 技术创建;
*
* @author archerda
*/
public class JavassistByteCodeMain {
public static void main(String[] args) throws Exception {
/*
创建类池
*/
ClassPool classPool = ClassPool.getDefault();
String className = TargetClass.class.getName();
CtClass ctClass = classPool.makeClass(className + "JavassitProxy");
/*
添加接口,可选
*/
ctClass.addInterface(classPool.get(TargetInterface.class.getName()));
/*
添加超类
*/
// ctClass.setSuperclass(classPool.get(TargetClass.class.getName()));
/*
添加默认构造函数
*/
// ctClass.addConstructor(new CtConstructor(new CtClass[]{}, ctClass));
/*
添加屬性
*/
CtField enameField = new CtField(classPool.getCtClass(className), "targetClass", ctClass);
enameField.setModifiers(Modifier.PRIVATE);
ctClass.addField(enameField);
/*
添加构造函数
*/
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass);
/*
为构造函数设置函数体
*/
ctConstructor.setBody("{\n" + "targetClass = new " + className + "();" + "\n}");
/*
把构造函数添加到新的类中
*/
ctClass.addConstructor(ctConstructor);
/*
添加方法,里面进行动态代理logic
*/
CtMethod ctMethod = new CtMethod(CtClass.voidType, "sayHi", new CtClass[]{}, ctClass);
/*
为自定义方法设置修饰符
*/
ctMethod.setModifiers(Modifier.PUBLIC);
/*
为自定义方法设置函数体
*/
String buffer2 = "{\n" +
"targetClass.sayHi();" +
"\n}";
ctMethod.setBody(buffer2);
ctClass.addMethod(ctMethod);
/*
把生成的class文件写入文件
*/
byte[] byteArr = ctClass.toBytecode();
FileOutputStream fos = new FileOutputStream(new File("/Users/Archerda/Desktop/java-example/com/sun/proxy/JavassitProxy.class"));
fos.write(byteArr);
fos.close();
/*
生成代理类对象实例
*/
TargetInterface targetClass = (TargetInterface)ctClass.toClass().newInstance();
/*
代理类方法调用
*/
targetClass.sayHi();
}
}
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
package com.github.archerda.designpattern.proxy;
import javassist.*;
import java.io.File;
import java.io.FileOutputStream;
/**
* Javassist动态代理: 采用 字节码 技术创建;
*
* @author archerda
*/
public class JavassistByteCodeMain {
public static void main(String[] args) throws Exception {
/*
创建类池
*/
ClassPool classPool = ClassPool.getDefault();
String className = TargetClass.class.getName();
CtClass ctClass = classPool.makeClass(className + "JavassitProxy");
/*
添加接口,可选
*/
ctClass.addInterface(classPool.get(TargetInterface.class.getName()));
/*
添加超类
*/
// ctClass.setSuperclass(classPool.get(TargetClass.class.getName()));
/*
添加默认构造函数
*/
// ctClass.addConstructor(new CtConstructor(new CtClass[]{}, ctClass));
/*
添加屬性
*/
CtField enameField = new CtField(classPool.getCtClass(className), "targetClass", ctClass);
enameField.setModifiers(Modifier.PRIVATE);
ctClass.addField(enameField);
/*
添加构造函数
*/
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass);
/*
为构造函数设置函数体
*/
ctConstructor.setBody("{\n" + "targetClass = new " + className + "();" + "\n}");
/*
把构造函数添加到新的类中
*/
ctClass.addConstructor(ctConstructor);
/*
添加方法,里面进行动态代理logic
*/
CtMethod ctMethod = new CtMethod(CtClass.voidType, "sayHi", new CtClass[]{}, ctClass);
/*
为自定义方法设置修饰符
*/
ctMethod.setModifiers(Modifier.PUBLIC);
/*
为自定义方法设置函数体
*/
String buffer2 = "{\n" +
"targetClass.sayHi();" +
"\n}";
ctMethod.setBody(buffer2);
ctClass.addMethod(ctMethod);
/*
把生成的class文件写入文件
*/
byte[] byteArr = ctClass.toBytecode();
FileOutputStream fos = new FileOutputStream(new File("/Users/Archerda/Desktop/java-example/com/sun/proxy/JavassitProxy.class"));
fos.write(byteArr);
fos.close();
/*
生成代理类对象实例
*/
TargetInterface targetClass = (TargetInterface)ctClass.toClass().newInstance();
/*
代理类方法调用
*/
targetClass.sayHi();
}
}

Javassist动态代理字节码源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.github.archerda.designpattern.proxy;
public class TargetClassJavassitProxy implements TargetInterface {
private TargetClass targetClass = new TargetClass();
public TargetClassJavassitProxy() {
}
public void sayHi() {
this.targetClass.sayHi();
}
}

Spring源码解析(1)-IoC初始化

发表于 2018-09-20

概览

IoC初始化主要包括:

  • Resource定位,载入DOM解析和注册到IoC中;

    org.springframework.context.support.AbstractApplicationContext#obtainFreshBeanFactory

  • 调用所有注册的 BeanFactoryPostProcessor;

  • 注册所有的 BeanPostProcessor;

  • 单例Bean的初始化; org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization

关键类

  • BeanDefinition
    XML中bean配置的Java映射, 也就是说XML配置了一个Bean, 就会有一个BeanDefinition对象与之对应, 它保存了Bean的各种信息, 包括scope/lazy-init等.
  • BeanFactory
    Bean工厂, 保存了所有的Bean并管理它们的生命周期和依赖关系.
  • ApplicationContext
    更高级的Bean工厂, 除了BeanFactory提供的能力之外, 还支持包括国际化/事件消息等.

Resource定位,载入和注册

  • 入口
1
2
3
4
5
6
7
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

ContextLoaderListener 实现了 javax.servlet.ServletContextListener, 这个是JavaEE中Servlet的一个类, 在初始化Servlet上下文之后的时候, 会执行它的 contextInitialized方法. 这里是Spring初始化的入口.

  • 创建WebApplicationContext

默认是 XmlWebApplicationContext.

  • refresh [重点]

org.springframework.context.support.AbstractApplicationContext#refresh
不论是BeanFactory还是ApplicationContext, 都会执行 org.springframework.context.support.AbstractApplicationContext#refresh 进行真正的初始化工作.

其中 obtainFreshBeanFactory是进行 Resource定位,载入和注册, 执行整个方法之后, XML中所有Bean的配置都被转化成BeanDefinition, 并保存在 DefaultListableBeanFactory 的 beanDefinitionNames 和 beanDefinitionMap 中;

  • Resource定位

    org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.xml.XmlBeanDefinitionReader)
    这个过程就是Spring找到XML文件, 这个文件可能在文件系统/类路径/网络路径中.

    ResourceLoader, 如果我们使用的是 FileSystemXmlApplicationContext, 那么 ResourceLoader 就是 FileSystemXmlApplicationContext, 它本身就是一个DefaultResourceLoader. 如果我们使用的是XmlWebApplicationContext, 那就是使用 XmlWebApplicationContext了.

  • 载入(Bean解析)

    org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource…)
    这个过程就是把找到的XML中的Bean定义转化为BeanDefinition.

    真正的解析工作在 org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions, 这里有个比较重要的点: XML的命名空间.
    如果是Spring默认的命名空间 http://www.springframework.org/schema/beans, 这个Bean的定义的命名空间, 明显Spring是知道怎么去解析的. 但是有一些自定义的命名空间, 比如Dubbo的配置文件, 这种的话, Spring是不知道要怎么去解析的. 所有Spring扩展了解析器, 自定义的命名空间就交给自定义的解析器去解析成BeanDefinition. 那么Spring是怎么找到这个自定义命名空间的解析器的呢? META-INF/spring.handlers中 http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler. 这里有个约定的规范, 比如spring的context命名空间, 那么它的解析器类名是 ContextNamespaceHandler, aop命名空间的是AopNamespaceHandler, dubbo的是DubboNamespaceHandler.

    • 解析Bean
      org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement
  • 注册

org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition
把BeanName和BeanDefinition注册到 DefaultListableBeanFactory.

单例类的初始化

org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization

  • 遍历容器中所有的beanDefinitionNames, 逐个初始化.

Bean的生命周期

入口:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

  1. Constructor
  2. InstantiationAwareBeanPostProcessor#postProcessPropertyValues (org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean)
    • 触发@Autowired/@Value/@Inject 依赖注入
    • 触发@Resouce的依赖注入
  3. BeanNameAware#setBeanName (org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition))
  4. BeanFactoryAware#setBeanFactory
  5. ApplicationContextAware#setApplicationContext
  6. BeanPostProcessor#postProcessBeforeInitialization
  7. @PostConstruct
  8. InitializingBean#afterPropertiesSet
  9. init-method
  10. BeanPostProcessor#postProcessAfterInitialization

Bean is Ok
…
Container shutdown

  1. @PreDestroy
  2. DisposableBean#destroy
  3. destory-method

附录

Spring如何解决循环依赖的问题

  • 三级缓存: org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
  • Setter依赖 [可以解决]
    通过提前暴露一个单例工厂方法(ObjectFactory),从而使其他bean能引用到该bean;
  • 构造器依赖 [无法解决]
    此循环依赖无法解决,只能抛出 BeanCurrentlyInCreationException 表示循环依赖;
  • prototype范围的依赖 [无法解决]
    无法解决,因为Spring容器不进行缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean;对于“singleton”作用域的bean,可以通过设置“setAllowCircularReferences(false)”来禁用循环依赖;

Autowired与Resource注入的区别;

  • @Autowired(或@Inject)和@Resource都同样适用。 但是意义上存在概念差异或差异
  • @Resource意味着按名称注入一个已知的资源。 该名称是从带注解的setter或字段的名称中提取的,或者是从name参数中获取的。
  • @Inject或@Autowired尝试按类型注入适当的其他组件。
  • 所以,基本上这些是两个截然不同的概念。 不幸的是,@Resource的Spring实现有一个内置的fallback,当按名称解析失败时会启动。 在这种情况下,它会回退到@Autowired类型的解决方式。 虽然这种回退方法很方便,但它引起了很多混淆,因为人们不了解概念上的差异,并倾向于使用@Resource进行基于类型的自动装配。
  • @Autowired 和 @Inject
    1. Matches by Type
    2. Restricts by Qualifiers
    3. Matches by Name
  • @Resource
    1. Matches by Name
    2. Matches by Type
    3. Restricts by Qualifiers (ignored if match is found by name)
  • 最佳实践
    • 用@Component(“beanName”)显式声明你的bean;
    • 使用@Resource并带上name属性,@Resource(name=”beanName”)
    • 除非您想创建一个类似的bean列表,否则请避免使用@Qualifier注解。 例如,您可能想要使用特定的@Qualifier注释来标记一组规则。 这种方法使得将一组规则类插入可用于处理数据的列表变得简单。
    • 使用扫描组件的特定包。 虽然这会导致更多的组件扫描配置,但它会减少您向Spring上下文添加不必要组件的可能性。

BeanPostProcessor 与 BeanFactoryPostProcessor

BeanFacotry 与 FactoryBean

参考文档

  • archerda: spring-framework
  • Spring IoC容器分析

性能优化指南

发表于 2018-03-27

性能指标

术语解析

  • TPS/QPS/RPS
    • 每秒事务处理数/每秒查询数/每秒请求数;
    • TPS是软件测试结果的测试单位。一个事务是指一个客户机向服务器发送请求然后服务器作出响应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,依次来计算使用的时间和完成的事务个数;
  • RT
    • Response Time,系统对请求作出响应的时间,一般取平均响应时间;
  • 吞吐量
    • 指一次性能测试过程中网络上传输的数据量的总和。
    • 对于交互式的应用来说,吞吐量反应了服务器承受的压力,能够说明系统级别的负载能力。
  • ThroughPut
    • 吞吐率
    • 我们一般使用单位时间内服务器处理的请求数来描述其并发处理能力。称之为吞吐率(Throughput),单位是 “req/s”。吞吐率特指Web服务器单位时间内处理的请求数。
    • 另一种描述,吞吐率是,单位时间内网络上传输的数据量,也可以指单位时间内处理客户请求数量。它是衡量网络性能的重要指标。通常情况下,吞吐率“字节数/秒”来衡量。当然你也可以用“请求数/秒”和“页面数/秒”来衡量。其实不管一个请求还是一个页面,它的本质都是在网络上传输的数据,那么用来表述数据的单位就是字节数。
  • 并发数
    • 系统同时处理的请求数/事务数;
    • TPS(QPS)=并发数/平均响应时间。假如某个应用的并发数是200,平均响应时间是50ms,那么它的TPS=200/0.05=4000。
  • 错误数

    • 单位时间内请求的错误数;

    一个系统吞吐量通常由QPS(TPS)、并发数两个因素决定,每套系统这两个值都有一个相对极限值,在应用场景访问压力下,只要某一项达到系统最高值,系统的吞吐量就上不去了,如果压力继续增大,系统的吞吐量反而会下降,原因是系统超负荷工作,上下文切换、内存等等其它消耗导致系统性能下降。

图谱分析

TPS折线下滑

TPS异常

现象:横坐标代表TPS,纵坐标代表自然时间。可以发现,在9:00时,TPS突然开始急剧下滑,到11:00时,TPS接近为0;

分析:

  • 网路IO问题。如果机器负载(CPU和内存)正常,有可能是网络出现阻塞,延时现象严重,进而导致TPS急剧下降;
  • 程序阻塞问题。如果机器负载正常,并且IO也正常,那么把堆栈信息打印出来,看是否是死锁导致TPS急剧下降;
  • 内存溢出。如果机器的内存不足,极大可能是内存溢出了,把堆信息打印出来看下;
  • 数据库连接数过小。导致很多请求处于等待状态,导致TPS下滑;
  • 应用服务器线程数过小。导致很多请求处于等待状态,导致TPS下滑;

TPS波动

TPS波动

现象:在9:00到15:00之间,TPS一直来回波动;

分析:

  • 定时任务。查看程序代码,看是否有定时任务在这个时间点运行,如果这个定时任务消耗了过多的资源,会导致TPS稳定地来回波动;
  • FullGC。JVM执行FullGC的时候,会StopTheWorld,导致短时间内应用停顿,会造成TPS来回波动;
  • 日志输出。有时间规律的小幅度波动,可能是日志写硬盘造成的;
  • Redis持久化。持久化时,会将内存中的数据写入到硬盘中,这时如果很有规律的波动,也有可能是redis有规律的写入硬盘造成硬盘那个点无法提供服务造成的波动;
  • 网络问题。如果是无规律的波动,可能是网络问题,这个比较难排查,可以根据其他服务的TPS来协助定位;
  • 黑白名单。一个用户1秒请求次数太多,导致被拒绝;

LR图区域分析

LR图区域分析

  • 性能平稳期:在不进行更多性能调优情况下所能达到的最佳性能,在与压力区域的拐点所对应的并发数为最佳并发;
  • 压力区域:RT轻微上升的区域,可以通过对应需求的RT阈值来得到应用可容忍的最大并发;
  • 性能拐点:RT急剧上升,性能急剧下降;

性能优化的准备

目标制定

比如:

  • TPS在1000以上;单就静态页面,TPS大概能到1W+,简单数据库操作大概2K+的样子,用Cache大概能到5K+。
  • TPS波动在一定范围内;TPS应该是一个比较平稳的曲线,而不是上下波动,TPS波动范围 = TPS标准差/TPS平均值 * 100%,在5%内算是正常的
  • 平均响应时间在200ms内;
  • 超时概率小于1/10000;
  • 并发数在500以上;
  • 错误概率小于1/10000;
  • FullGC频率平均大于半小时1次;
  • CPU利用率小于75%;
  • 平均每core的CPU的load小于1;

了解系统架构

了解使用的组件

tomcat

//TODO
tomcat最大tps、最大并发

mysql

//TODO
mysql最大TPS、最大并发

redis

//TODO
reids最大TPS

了解工具

LR

JMeter

Top

jstack

jstat

性能优化定理

资源来源

服务器

  • IO资源
    • 压榨:是否已经充分利用(达到极限)
    • 转移:批量(Buffer)输入/输出,顺序写/预读;
    • 增强:固态硬盘、磁盘阵列、PCIE接口;
  • CPU资源
    • 压榨:是否已经充分利用(达到极限)
    • 转移:减少线程切换(sy -> us),优化算法;
    • 增强:增加CPU核心/频率;
  • 内存资源
    • 压榨:是否已经充分利用(达到极限)
    • 转移:内存 -> 磁盘(LRU)
    • 增强:增加内存;

网络

  • IO资源
    • 压榨:是否已经充分利用(达到极限)
    • 转移:压缩、批量(Buffer)输入输出,TCP参数调优;
    • 增强:增加带宽;

性能平衡

  • 掌握好平衡度,可以让整体性能提升更大
  • 优化是个双刃剑,在性能提升的同时可能带来更多的麻烦

难度与性能

二八原则

80%的性能提升在20%的优化点上

原则

  • 过早优化是万恶之源
  • 尽量使用硬件优化代替深度的代码优化
  • 尽量保证简单

参考文档

  • 压力测试tps性能下降问题解决方案
  • 网站性能测试PV到TPS的转换以及TPS的波动和淘宝性能测试要点
  • 聊下并发和Tomcat线程数

设计原则以及模式分类

发表于 2018-01-13

面向对象设计基础(OO基础)

  • 抽象
  • 封装
  • 多态
  • 继承

面向对象设计原则

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,不针对实现编程(里氏替换原则)
  • 为交互对象之间的松耦合设计而努力
  • 类应该对扩展开放,对修改关闭(开闭原则)
  • 依赖抽象,不要依赖具体类(依赖倒置原则)
  • 只和朋友交谈(迪米特原则/最少知识原则)
  • 别找我,我会找你
  • 类应该只有一个改变的理由(单一职责原则)
阅读全文 »

设计模式六之适配器模式(Adapter)

发表于 2017-11-24

什么是适配者模式

适配者模式 将一个类的接口,装换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

  • 《Head First 设计模式》

适配者别名为包装器(Wrapper)。

阅读全文 »

Zookeeper常用命令

发表于 2017-11-16

连接

  • 连接本地Server:bash zkCli.sh
  • 连接远程Server:bash zkCli.sh –server <ip>:<port>

配置

  • 获取服务器配置信息:
    • telnet <host> <port>, conf
    • echo conf | nc <host> <port>
  • 获取所有客户端连接的详细信息:echo cons | nc <host> <port>
  • 重置所有客户端连接的统计信息:echo crst | nc <host> <port>
  • 输出当前集群的所有会话信息:echo dump | nc <host> <port>
  • 获取服务器运行时的环境信息:echo envi | nc <host> <port>
  • 判断服务器是否正在运行:echo ruok | nc <host> <port>
  • 获取服务器运行时状态信息:echo stat | nc <host> <port>
  • 获取当前服务器上管理的Watcher的概要信息:echo wchs | nc <host> <port>
  • 获取当前服务器上管理的Watcher的详细信息:echo wchc | nc <host> <port>
  • 获取比stat更详细的服务器统计信息:echo mntr | nc <host> <port>
    -

数据操作

  • 查看节点数据:ls <path> [watch],比如ls / 则查看根目录节点数据
  • 查看节点数据并能看到更新次数等数据:ls2 <path>,输出字段含义如下:
    • cZxid:创建节点的事务id
    • ctime:创建节点的时间
    • mZxid:修改节点的事务id
    • mtime:修改节点的时间
    • pZxid:子节点列表最后一次修改的事务id。删除或添加子节点,不包含修改子节点的数据
    • cversion:子节点的版本号,删除或添加子节点,版本号会自增
    • dataVersion:节点数据版本号,数据写入操作,版本号会递增
    • aclVersion:节点ACL权限版本,权限写入操作,版本号会递增
    • ephemeralOwner:临时节点创建时的事务id,如果节点是永久节点,则它的值为0
    • dataLength:节点数据长度(单位:byte),中文占3个byte
    • numChildren:子节点数量
  • 创建节点:create [-s] [-e] <path> <data> [acl],其中-s指定为顺序节点,-e为临时节点,不指定则为持久性节点;
  • 获取节点,包含数据和更新次数等数据:get <path>
  • 修改节点:set <path> <data> [version]
  • 删除节点:delete <path>,如果有子节点存在则删除失败

设计模式系列五之命令模式(Command)

发表于 2017-11-16

什么是命令模式:封装调用

将”请求”封装成对象,以便使用不同的请求、队列、或者日志来参数化其他对象。命令模式也支持可撤销的操作。
–《Head Fist设计模式》

阅读全文 »

单例模式的破坏

发表于 2017-11-13

反射破坏单例模式

破坏方法

在Java中,反射技术可以获取类的所有方法、成员变量,还能访问私有的变量和方法。所以private的构造器也是可以被获取到并执行的。这样一来,单例模式中的的私有构造器被反射多次调用时,就会创建多个实例,进而破坏单例模式。下面是sample:

阅读全文 »

设计模式系列四之工厂模式(Factory)

发表于 2017-11-08

什么是工厂模式

在我们日常编码中,到处都遍布着new操作。当看到new的时候,可能你就会想到这是一个”具体”了,那么new有什么不对劲吗?从设计原则我们可以知道,我们的代码应该“对扩展开放,对修改关闭(Open Close Priciple)”。当针对具体来编程时,我们的老朋友“改变”也就会经常来捣乱了,因为一旦加入新的具体类型的时候,就必须改变代码,也就是说,我们的代码,并非“对修改关闭”,想用新的具体类型的时候,必须重新打开它。

而这,就是工厂模式可以解决的问题。将实例化具体类型的代码从应用中抽离出来,或者封装起来,使它们不会干扰应用的其他部分。

阅读全文 »

设计模式系列三之装饰者模式(Decorator)

发表于 2017-10-13

什么是装饰者模式:给爱用继承的人一个全新的设计眼界

装饰者模式,是面向对象编程领域中,一种动态地往一个类中添加新行为的设计模式。就功能而言,装饰者模式相比生成子类更加灵活,这样可以给某个对象而不是整个类添加一些功能。- 摘自维基百科

装饰者模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。- 摘自《HeadFirst设计模式》

阅读全文 »
12…8
archerda

archerda

71 日志
37 标签
GitHub Email
© 2015 - 2019 archerda
由 Hexo 强力驱动
主题 - NexT.Muse