本文是《轻量级 Java Web 框架架构设计》的系列博文。
大家是否还记得《Proxy 那点事儿》中提到的 CGLib 动态代理吗?我就是使用这个工具来实现了 Smart AOP 的,原以为这样 AOP 就轻松搞定了,但万万没想到的是,自己太傻太天真。
昨天刚发现 Smart AOP 的 AOPHelper 类有个严重的 Bug,导致同一个目标类不能同时被多个切面类横切,运行时会报错。深入了解才知道,如果使用 CGLib 写了一个增强类,该增强类就不能再次被 CGLib 自己进行增强。换言之,CGLib 不支持嵌套增强!
我喜欢用一个简单的示例来说明一个深刻的道理,请往下看,请确保您有足够的耐心与探索的欲望。系好安全带吧!
还是有个接口:Greeting
1
2
3
4
|
public
interface
Greeting {
void
sayHello(String name);
}
|
仍然有个实现类:GreetingImpl
1
2
3
4
5
6
7
|
public
class
GreetingImpl
implements
Greeting {
@Override
public
void
sayHello(String name) {
System.out.println(
"Hello! "
+ name);
}
}
|
恐怕这个例子已经在我的博客中出现过无数次了,但百看不厌。
我们知道,CGLib 可以代理目标类,而该对目标类有无接口根本就不在意。所以我们忽略掉 Greeting 接口吧,直接面向实现编程(故意违背一下“依赖倒置原则”)。
下面,我要写两个 Proxy,一个是 BeforeProxy(用于实现“前置增强”),另一个是 AfterProxy(用于实现“后置增强”)。这些所谓的“增强类型”其实都是 AOP 的术语,对 AOP 需要温习一下的同学,回头可阅读这篇博文《AOP 那点事儿》。
先上一个 BeforeProxy:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
BeforeProxy
implements
MethodInterceptor {
public
Object getProxy(Class cls) {
return
Enhancer.create(cls,
this
);
}
@Override
public
Object intercept(Object target, Method method, Object[] args, MethodProxy proxy)
throws
Throwable {
before();
return
proxy.invokeSuper(target, args);
}
private
void
before() {
System.out.println(
"Before"
);
}
}
|
再搞一个 AfterProxy:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public
class
AfterProxy
implements
MethodInterceptor {
public
Object getProxy(Class cls) {
return
Enhancer.create(cls,
this
);
}
@Override
public
Object intercept(Object target, Method method, Object[] args, MethodProxy proxy)
throws
Throwable {
Object result = proxy.invokeSuper(target, args);
after();
return
result;
}
private
void
after() {
System.out.println(
"After"
);
}
}
|
下面用一个 Client 类完成“BeforeProxy + AfterProxy”的功能,也就是说,我想同时让以上两个 Proxy 去增强目标类 GreetingImpl。
1
2
3
4
5
6
7
8
9
10
11
|
public
class
Client {
public
static
void
main(String[] args) {
Object greetingImplBeforeProxy =
new
BeforeProxy().getProxy(GreetingImpl.
class
);
Object greetingImplAfterBeforeProxy =
new
AfterProxy().getProxy(greetingImplBeforeProxy.getClass());
GreetingImpl greetingImpl = (GreetingImpl) greetingImplAfterBeforeProxy;
greetingImpl.sayHello(
"Jack"
);
}
}
|
应该就是这样写吧?先用 BeforeProxy 去增强 GreetingImpl,得到一个代理对象 greetingImplBeforeProxy。然后再用 AfterProxy 去增强上一步得到的代理对象,得到一个新的代理对象 greetingImplAfterBeforeProxy。最后将这个新的代理对象强制转型为目标类对象,并调用目标类对象的 sayHello() 方法。
这样能行吗?反正编译是没有报错的,管它的,老子先运行一把再说。
Exception in thread "main" net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:663)
at com.smart.lab.proxy.cglib_proxy.AfterProxy.getProxy(AfterProxy.java:11)
at com.smart.lab.proxy.cglib_proxy.Client.main(Client.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:384)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:219)
... 10 more
Caused by: java.lang.ClassFormatError: Duplicate method name&signature in class file com/smart/lab/proxy/GreetingImpl$$EnhancerByCGLIB$$76346a2$$EnhancerByCGLIB$$8930dbed
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
... 16 more
运行时报错!而且抛出了一个 net.sf.cglib.core.CodeGenerationException,可见这是 CGLib 的内部异常。从 Caused by 中可见,报错信息是“Duplicate method name&signature”(重复的方法签名)。遇到这种异常,一般都是很让人抓狂。
看来 CGLib 自动生成的类,不能被自己再次增强了。证实了我之前说的那一点:CGLib 不支持嵌套增强!
Smart AOP 中也会遇到以上的报错现象,说明如果解决了 CGLib 的嵌套增强问题,也就解决了 Smart AOP 的问题。
如何解决呢?下面才是精华!请检查一下您的安全带是否系牢?
不妨借鉴 Servlet 的 Filter Chain 的设计模式,它是“责任链模式”的一种变体,在 JavaEE 设计模式中命名为“拦截过滤器模式”。我们可以将每个 Proxy 用一根链子串起来,形成一个 Proxy Chain。然后调用这个 Proxy Chain,让它去依次调用 Chain 中的每个 Proxy。
第一步:定义一个 Proxy 接口
1
2
3
4
|
public
interface
Proxy {
void
doProxy(ProxyChain proxyChain);
}
|
该接口中,只有一个 doProxy() 方法,该方法中关联了 ProxyChain 这个类,这样做是为了让 ProxyChain 来调用 Proxy。
第二步:编写 ProxyChain 类
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
|
public
class
ProxyChain {
private
List<Proxy> proxyList;
private
int
currentProxyIndex =
0
;
private
Class<?> targetClass;
private
Object targetObject;
private
Method targetMethod;
private
Object[] methodParams;
private
MethodProxy methodProxy;
private
Object methodResult;
public
ProxyChain(Class<?> targetClass, Object targetObject, Method targetMethod, Object[] methodParams, MethodProxy methodProxy, List<Proxy> proxyList) {
this
.targetClass = targetClass;
this
.targetObject = targetObject;
this
.targetMethod = targetMethod;
this
.methodParams = methodParams;
this
.methodProxy = methodProxy;
this
.proxyList = proxyList;
}
public
Class<?> getTargetClass() {
return
targetClass;
}
public
Object getTargetObject() {
return
targetObject;
}
public
Method getTargetMethod() {
return
targetMethod;
}
public
Object[] getMethodParams() {
return
methodParams;
}
public
MethodProxy getMethodProxy() {
return
methodProxy;
}
public
Object getMethodResult() {
return
methodResult;
}
public
void
doProxyChain() {
if
(currentProxyIndex < proxyList.size()) {
proxyList.get(currentProxyIndex++).doProxy(
this
);
}
else
{
try
{
methodResult = methodProxy.invokeSuper(targetObject, methodParams);
}
catch
(Throwable throwable) {
throw
new
RuntimeException(throwable);
}
}
}
}
|
先判断当前指针有没有走完所有的 proxyList,若没有走完,则从 proxyList 中获取一个 Proxy,同时让指针往后走一步(指向下一个 Proxy),然后调用 Proxy 的 doProxy() 方法,此时需要将 this(也就是 ProxyChain 实例)传递到该方法中;若已经走完了,使用 CGLib API 调用目标方法,并初始化方法返回值。
以上步骤中,最难理解的就是最后一步,多看几遍,多思考几遍。如果理解了,您就掌握了该模式的精华!
第三步:编写 ProxyManager 类
现在 Proxy 与 ProxyChain 都有了,下面的仍然是精华,我们通过一个“工厂模式”来创建 Proxy。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
class
ProxyManager {
private
Class<?> targetClass;
private
List<Proxy> proxyList;
public
ProxyManager(Class<?> targetClass, List<Proxy> proxyList) {
this
.targetClass = targetClass;
this
.proxyList = proxyList;
}
@SuppressWarnings
(
"unchecked"
)
public
<T> T createProxy() {
return
(T) Enhancer.create(targetClass,
new
MethodInterceptor() {
@Override
public
Object intercept(Object target, Method method, Object[] args, MethodProxy proxy)
throws
Throwable {
ProxyChain proxyChain =
new
ProxyChain(targetClass, target, method, args, proxy, proxyList);
proxyChain.doProxyChain();
return
proxyChain.getMethodResult();
}
});
}
}
|
在 ProxyManager 中,定义了两个成员变量,targetClass 表示目标类,proxyList 也就是代理列表了。通过一个简单的构造器,将这两个成员变量进行初始化。最后提供一个 createProxy() 方法,创建代理对象。在该方法中,封装了 CGLib 的 Enhancer 类,只需提供两个参数:目标类与拦截器。后者在 CGLib 中称为 Callback。特别要注意第二个参数,这里使用了匿名内部类的方式进行实现。
通过一个匿名内部类来实现 CGLib 的 MethodInterceptor 接口,并填充 intercept() 方法。将该方法的所有入口参数都传递到创建的 ProxyChain 对象中,外加该类的两个成员变量:targetClass 与 proxyList。然后调用 ProxyChain 的 doProxyChain() 方法,可以想象,调用是一连串的,当调用完毕后,可直接获取方法返回值。
第四步:编写 AbstractProxy 类
不要忘了,我们的目标不是为了实现 Proxy,而是为了实现 AOP。为了实现 AOP,我采用了“模板方法模式”,弄一个 AbstractProxy 抽象类,让它去实现 Proxy 接口,并在其中定义方法调用模板,在需要横向拦截的地方,定义一些“钩子方法”。Spring 源码中大量使用了这一技巧。
还等什么?直接上 AbstractProxy 吧!
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
|
public
abstract
class
AbstractProxy
implements
Proxy {
@Override
public
final
void
doProxy(ProxyChain proxyChain) {
Class<?> cls = proxyChain.getTargetClass();
Method method = proxyChain.getTargetMethod();
Object[] params = proxyChain.getMethodParams();
begin();
try
{
if
(filter(cls, method, params)) {
before(cls, method, params);
proxyChain.doProxyChain();
after(cls, method, params);
}
else
{
proxyChain.doProxyChain();
}
}
catch
(Throwable e) {
error(cls, method, params, e);
}
finally
{
end();
}
}
public
void
begin() {
}
public
boolean
filter(Class<?> cls, Method method, Object[] params) {
return
true
;
}
public
void
before(Class<?> cls, Method method, Object[] params) {
}
public
void
after(Class<?> cls, Method method, Object[] params) {
}
public
void
error(Class<?> cls, Method method, Object[] params, Throwable e) {
}
public
void
end() {
}
}
|
相信您已经看明白了,这里提供了一些列的钩子方法,例如:begin()、filter()、before()、after()、error()、end() 等,这些方法可延迟到子类中去实现,并且实现哪个,完全有用户自己决定。
下面我们就利用 AbstractProxy 重新实现 BeforeProxy 与 AfterProxy 吧。
先看 BeforeProxy:
1
2
3
4
5
6
7
|
public
class
BeforeProxy
extends
AbstractProxy {
@Override
public
void
before(Class<?> cls, Method method, Object[] params) {
System.out.println(
"Before"
);
}
}
|
再看 AfterProxy:
1
2
3
4
5
6
7
|
public
class
AfterProxy
extends
AbstractProxy {
@Override
public
void
after(Class<?> cls, Method method, Object[] params) {
System.out.println(
"After"
);
}
}
|
是不是比之前的更加简洁了呢?以上搞出了许多类,其实是非常有必要的,它们将一个复杂的问题,简化为多个简单的问题,这就是“ 关注点分离 ”设计原则。
第五步:编写 Client 类
好了!您终于快熬到头了,当看到最新的 Client 类,相信您会激动不已!
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
class
Client {
public
static
void
main(String[] args) {
List<Proxy> proxyList =
new
ArrayList<Proxy>();
proxyList.add(
new
BeforeProxy());
proxyList.add(
new
AfterProxy());
ProxyManager proxyManager =
new
ProxyManager(GreetingImpl.
class
, proxyList);
GreetingImpl greetingProxy = proxyManager.createProxy();
greetingProxy.sayHello(
"Jack"
);
}
}
|
先构造一个空的 List<Proxy> proxyList,然后往里面依次放入需要增强的 Proxy 类,随后使用 ProxyManager 去创建代理实例,最后调用代理实例的方法,完成对目标方法的横切。
运行结果正确!
Before
Hello! Jack
After
Smart AOP 也是采用了以上思路进行了改造,如果您有兴趣的话,不妨 clone 一份 Smart Framework 的源码吧,新功能都在 dev 分支上,可能目前还没有 merge 到 master 分支上。
最后,感谢网友 黎明伟 提供的解决方案!如果没有他的帮助,恐怕我至今都没有解决这个棘手的问题,他在我们的 QQ 群中是一名青春阳光的美少男。
如果您也想和我们一起交流,请加入此 QQ 群:120404320。
补充(2013-11-02)
非常感谢网友 Bieber 提出的建议:使用“链式 AOP”实现事务控制。他也实现了一个 Smart Cache Plugin,非常有特色!
现在事务控制的机制已经统一为“链式 AOP”了,下面记录一下是如何实现的。
首先,做了一个 TransactionAspect,用于实现事务控制的切面,代码如下:
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
|
public
class
TransactionAspect
extends
BaseAspect {
private
static
final
Logger logger = Logger.getLogger(TransactionAspect.
class
);
private
static
final
DBHelper dbHelper = DBHelper.getInstance();
@Override
public
boolean
filter(Class<?> cls, Method method, Object[] params) {
return
method.isAnnotationPresent(Transaction.
class
);
}
@Override
public
void
before(Class<?> cls, Method method, Object[] params)
throws
Exception {
// 开启事务
dbHelper.beginTransaction();
}
@Override
public
void
after(Class<?> cls, Method method, Object[] params, Object result)
throws
Exception {
// 提交事务
dbHelper.commitTransaction();
}
@Override
public
void
error(Class<?> cls, Method method, Object[] params, Exception e) {
// 回滚事务
dbHelper.rollbackTransaction();
}
}
|
注意,这里有个 filter 方法,仅用于过滤出带有 Transaction 注解的目标方法进行事务控制。在目标方法执行前(before 方法)开启事务,在目标方法执行后(after 方法)提交事务,在遇到了异常时(error 方法)回滚事务。
然后,在 AOPHelper 中修改如下代码:
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
|
public
class
AOPHelper {
...
private
Map<Class<?>, List<Class<?>>> createAspectMap()
throws
Exception {
// 定义 Aspect Map
Map<Class<?>, List<Class<?>>> aspectMap =
new
LinkedHashMap<Class<?>, List<Class<?>>>();
// 获取切面类
List<Class<?>> aspectClassList = ClassHelper.getInstance().getClassListBySuper(BaseAspect.
class
);
// 排序切面类
sortAspectClassList(aspectClassList);
// 遍历切面类
for
(Class<?> aspectClass : aspectClassList) {
// 判断 @Aspect 注解是否存在
if
(aspectClass.isAnnotationPresent(Aspect.
class
)) {
// 获取 @Aspect 注解
Aspect aspect = aspectClass.getAnnotation(Aspect.
class
);
// 创建目标类列表
List<Class<?>> targetClassList = createTargetClassList(aspect);
// 初始化 Aspect Map
aspectMap.put(aspectClass, targetClassList);
}
}
// 添加事务控制切面(最后一个切面)
addTransactionAspect(aspectMap);
// 返回 Aspect Map
return
aspectMap;
}
private
void
addTransactionAspect(Map<Class<?>, List<Class<?>>> aspectMap) {
// 使用 TransactionAspect 横切所有 Service 类
List<Class<?>> serviceClassList = ClassHelper.getInstance().getClassListBySuper(BaseService.
class
);
aspectMap.put(TransactionAspect.
class
, serviceClassList);
}
}
|
见以上代码片段中,创建 Aspect Map(用于存放切面类与目标类列表的映射关系)时,在末尾添加了一个事务控制切面类(TransactionAspect),让这个类横切所有继承了 BaseService 的类。
最后,删除 TransactionProxy 与 ServiceHelper 这两个类。
现在就可以将事务控制通过统一的 AOP 方式来实现了,而且 AOP 控制能力与范围更加强大!
需要补充说明的是,在 ContainerListener 中,需要注意初始化 Helper 类的顺序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@WebListener
public
class
ContainerListener
implements
ServletContextListener {
@Override
public
void
contextInitialized(ServletContextEvent sce) {
// 初始化 Helper 类
initHelperClass();
// 添加 Servlet 映射
addServletMapping(sce.getServletContext());
}
private
void
initHelperClass() {
DBHelper.getInstance().init();
EntityHelper.getInstance().init();
ActionHelper.getInstance().init();
BeanHelper.getInstance().init();
AOPHelper.getInstance().init();
IOCHelper.getInstance().init();
}
...
}
|
一定要先加载 AOPHelper,后加载 IOCHelper,大家可以思考一下这是为什么?欢迎评论。
补充(2013-11-09)
网友 V神 建议将 ProxyManager 改为单例模式,这样在 AOPHelper 中创建代理实例的性能会高一些。非常感谢他的建议!现已做优化:
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
|
public
class
ProxyManager {
private
static
ProxyManager instance =
null
;
private
ProxyManager() {
}
public
static
ProxyManager getInstance() {
if
(instance ==
null
) {
instance =
new
ProxyManager();
}
return
instance;
}
@SuppressWarnings
(
"unchecked"
)
public
<T> T createProxy(
final
Class<?> targetClass,
final
List<Proxy> proxyList) {
return
(T) Enhancer.create(targetClass,
new
MethodInterceptor() {
@Override
public
Object intercept(Object target, Method method, Object[] args, MethodProxy proxy)
throws
Throwable {
ProxyChain proxyChain =
new
ProxyChain(targetClass, target, method, args, proxy, proxyList);
return
proxyChain.doProxyChain();
}
});
}
}
|
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
|
public
class
AOPHelper {
...
private
AOPHelper() {
try
{
|