前言
在前面從零開始實現一個簡易的Java MVC框架(四)--實現AOP和從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點這兩節文章中已經實現了AOP功能並且引用aspectj表示式實現切點的功能,這篇文章繼續完善doodle框架的AOP功能。
在前面的文章中實現的AOP功能時,目標類都只能被一個切面代理,如果想要生成第二個代理類,就會把之前的代理類覆蓋。這篇文章就要來實現多個代理的功能,也就是實現代理鏈。
實現代理鏈
在com.zbw.aop包下建立一個類起名為AdviceChain
package com.zbw.aop;
import ...
/**
* 通知鏈
*/
public class AdviceChain {
/**
* 目標類
*/
@Getter
private final Class<?> targetClass;
/**
* 目標例項
*/
@Getter
private final Object target;
/**
* 目標方法
*/
@Getter
private final Method method;
/**
* 目標方法引數
*/
@Getter
private final Object[] args;
/**
* 代理方法
*/
private final MethodProxy methodProxy;
/**
* 代理通知列
*/
private List<ProxyAdvisor> proxyList;
/**
* 代理通知列index
*/
private int adviceIndex = 0;
public AdviceChain(Class<?> targetClass, Object target, Method method, Object[] args, MethodProxy methodProxy, List<ProxyAdvisor> proxyList) {
this.targetClass = targetClass;
this.target = target;
this.method = method;
this.args = args;
this.methodProxy = methodProxy;
this.proxyList = proxyList;
}
/**
* 遞迴執行 執行代理通知列
*/
public Object doAdviceChain() throws Throwable {
...
}
}
複製程式碼
由於要實現多個通知類鏈式執行的功能,這個類就是代替之前的ProxyAdvisor
來生產代理類,並且通過doAdviceChain()
方法執行具體的切面方法以及目標代理類的方法。
在最初設計這個方法的時候,我想的是直接for迴圈proxyList
這個屬性裡的ProxyAdvisor
,然後一個個執行對應的Advice方法不就行了,後來發現這是不行的。因為在AOP的功能設計裡,多個切面的執行順序是一種'先入後出'的順序。比如說有兩個切面Aspect1
和Aspect2
,那麼他們的執行順序應該是Aspect1@before()->Aspect2@before()->targetClass@method()->Aspect2@after()->Aspect1@after(),先執行的Aspect1@before()方法要在最後執行Aspect1@after()。
要實現'先入後出'的功能通常有兩種實現方式,一是藉助棧這個資料結構,二是用遞迴的方式,這裡我們用遞迴的方式實現。
在實現doAdviceChain()
的功能之前,先修改之前的ProxyAdvisor
類。
...
public class ProxyAdvisor {
...
/**
* 執行順序
*/
private int order;
/**
* 執行代理方法
*/
public Object doProxy(AdviceChain adviceChain) throws Throwable {
Object result = null;
Class<?> targetClass = adviceChain.getTargetClass();
Method method = adviceChain.getMethod();
Object[] args = adviceChain.getArgs();
if (advice instanceof MethodBeforeAdvice) {
((MethodBeforeAdvice) advice).before(targetClass, method, args);
}
try {
result = adviceChain.doAdviceChain(); //執行代理鏈方法
if (advice instanceof AfterReturningAdvice) {
((AfterReturningAdvice) advice).afterReturning(targetClass, result, method, args);
}
} catch (Exception e) {
if (advice instanceof ThrowsAdvice) {
((ThrowsAdvice) advice).afterThrowing(targetClass, method, args, e);
} else {
throw new Throwable(e);
}
}
return result;
}
}
複製程式碼
在ProxyAdvisor
類中新增一個屬性order
,這是用於儲存這個切面類的執行順序的。然後再修改doProxy()
方法,把傳入引數由原來的很多類相關的資訊改為傳入AdviceChain
,因為我們把類資訊都放在了AdviceChain
中了。然後把原來在doProxy()
方法開頭的if (!pointcut.matches(method))
這個切點判斷移除,這個判斷將會改在AdviceChain
中。然後在原來要呼叫proxy.invokeSuper(target, args);
的地方改為呼叫adviceChain.doAdviceChain();
,這樣就能形成一個遞迴呼叫。
現在來具體實現AdviceChain
的doAdviceChain()
方法。
...
public Object doAdviceChain() throws Throwable {
Object result;
while (adviceIndex < proxyList.size()
&& !proxyList.get(adviceIndex).getPointcut().matches(method)) {
//如果當前方法不匹配切點,則略過該代理通知類
adviceIndex++;
}
if (adviceIndex < proxyList.size()) {
result = proxyList.get(adviceIndex++).doProxy(this);
} else {
result = methodProxy.invokeSuper(target, args);
}
return result;
}
複製程式碼
在這個方法中,先是通過一個while迴圈判定proxyList
的當前ProxyAdvisor
是否匹配切點表示式,如果不匹配日則跳過這個ProxyAdvisor
且adviceIndex
這個計數器加一,假如匹配的話,就執行ProxyAdvisor
的doProxy()
方法,並且把自己當作引數傳入過去。直到adviceIndex
計數器的大小大於等於proxyList
的大小,則呼叫目標類的方法。
這樣就形成一個遞迴的形式來實現代理鏈。
改裝原有AOP功能
現在要改裝原來的AOP的實現程式碼,讓AdviceChain
的功能加入到框架中
為了讓切面能夠排序,先新增一個Order
註解,用於標記排序。在zbw.aop包下建立Order
註解類
package com.zbw.aop.annotation;
import ...
/**
* aop順序
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Order {
/**
* aop順序,值越大越先執行
*/
int value() default 0;
}
複製程式碼
然後再改裝AOP執行器,先修改createProxyAdvisor()
方法,把Order
註解的值存入到ProxyAdvisor
中。
// Aop.java
...
/**
* 通過Aspect切面類建立代理通知類
*/
private ProxyAdvisor createProxyAdvisor(Class<?> aspectClass) {
int order = 0;
if (aspectClass.isAnnotationPresent(Order.class)) {
order = aspectClass.getAnnotation(Order.class).value();
}
String expression = aspectClass.getAnnotation(Aspect.class).pointcut();
ProxyPointcut proxyPointcut = new ProxyPointcut();
proxyPointcut.setExpression(expression);
Advice advice = (Advice) beanContainer.getBean(aspectClass);
return new ProxyAdvisor(advice, proxyPointcut, order);
}
複製程式碼
然後再增加一個createMatchProxies()
方法,由於之前生成代理類都是用一個ProxyAdvisor
就可以了,而現在是一個List,所以現在要用該方法用於生成一個List,其中存放的是匹配目標類的切面集合。傳入的引數proxyList
為所有的ProxyAdvisor
集合,返回的引數為目標類匹配的代理通知集合,並且這個集合是根據order排序的。
// Aop.java
...
/**
* 獲取目標類匹配的代理通知列表
*/
private List<ProxyAdvisor> createMatchProxies(List<ProxyAdvisor> proxyList, Class<?> targetClass) {
Object targetBean = beanContainer.getBean(targetClass);
return proxyList
.stream()
.filter(advisor -> advisor.getPointcut().matches(targetBean.getClass()))
.sorted(Comparator.comparingInt(ProxyAdvisor::getOrder))
.collect(Collectors.toList());
}
複製程式碼
最後再修改doAop()
方法。
// Aop.java
...
/**
* 執行Aop
*/
public void doAop() {
//建立所有的代理通知列表
List<ProxyAdvisor> proxyList = beanContainer.getClassesBySuper(Advice.class)
.stream()
.filter(clz -> clz.isAnnotationPresent(Aspect.class))
.map(this::createProxyAdvisor)
.collect(Collectors.toList());
//建立代理類並注入到Bean容器中
beanContainer.getClasses()
.stream()
.filter(clz -> !Advice.class.isAssignableFrom(clz))
.filter(clz -> !clz.isAnnotationPresent(Aspect.class))
.forEach(clz -> {
List<ProxyAdvisor> matchProxies = createMatchProxies(proxyList, clz);
if (matchProxies.size() > 0) {
Object proxyBean = ProxyCreator.createProxy(clz, matchProxies);
beanContainer.addBean(clz, proxyBean);
}
});
}
複製程式碼
同樣的,由於代理類從ProxyAdvisor
改成AdviceChain
,對應的代理類創造器也要做對應的修改。
package com.zbw.aop;
import ...
/**
* 代理類建立器
*/
public final class ProxyCreator {
/**
* 建立代理類
*/
public static Object createProxy(Class<?> targetClass, List<ProxyAdvisor> proxyList) {
return Enhancer.create(targetClass, new AdviceMethodInterceptor(targetClass, proxyList));
}
/**
* cglib MethodInterceptor實現類
*/
private static class AdviceMethodInterceptor implements MethodInterceptor {
/**
* 目標類
*/
private final Class<?> targetClass;
/**
* 代理通知列表
*/
private List<ProxyAdvisor> proxyList;
public AdviceMethodInterceptor(Class<?> targetClass, List<ProxyAdvisor> proxyList) {
this.targetClass = targetClass;
this.proxyList = proxyList;
}
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return new AdviceChain(targetClass, target, method, args, proxy, proxyList).doAdviceChain();
}
}
}
複製程式碼
代理鏈的功能又實現了,現在可以寫測試用例了。
測試用例
先實現兩個切面DoodleAspect
和DoodleAspect2
:
// DoodleAspect
@Slf4j
@Order(1)
@Aspect(pointcut = "@within(com.zbw.core.annotation.Controller)")
public class DoodleAspect implements AroundAdvice {
@Override
public void before(Class<?> clz, Method method, Object[] args) throws Throwable {
log.info("-----------before DoodleAspect-----------");
log.info("class: {}, method: {}", clz.getName(), method.getName());
}
@Override
public void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable {
log.info("-----------after DoodleAspect-----------");
log.info("class: {}, method: {}", clz, method.getName());
}
@Override
public void afterThrowing(Class<?> clz, Method method, Object[] args, Throwable e) {
log.error("-----------error DoodleAspect-----------");
log.error("class: {}, method: {}, exception: {}", clz, method.getName(), e.getMessage());
}
}
複製程式碼
// DoodleAspect2
@Slf4j
@Order(2)
@Aspect(pointcut = "@within(com.zbw.core.annotation.Controller)")
public class DoodleAspect2 implements AroundAdvice {
@Override
public void before(Class<?> clz, Method method, Object[] args) throws Throwable {
log.info("-----------before DoodleAspect2-----------");
log.info("class: {}, method: {}", clz.getName(), method.getName());
}
@Override
public void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable {
log.info("-----------after DoodleAspect2-----------");
log.info("class: {}, method: {}", clz, method.getName());
}
@Override
public void afterThrowing(Class<?> clz, Method method, Object[] args, Throwable e) {
log.error("-----------error DoodleAspect2-----------");
log.error("class: {}, method: {}, exception: {}", clz, method.getName(), e.getMessage());
}
}
複製程式碼
然後在AopTest
測試類中呼叫DoodleController
的hello()
方法。
@Slf4j
public class AopTest {
@Test
public void doAop() {
BeanContainer beanContainer = BeanContainer.getInstance();
beanContainer.loadBeans("com.zbw");
new Aop().doAop();
new Ioc().doIoc();
DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class);
controller.hello();
}
}
複製程式碼
在結果的圖中可以看出DoodleAspect
和DoodleAspect2
兩個代理方法都執行了,並且是按照預期的執行順序執行的。
- 從零開始實現一個簡易的Java MVC框架(一)--前言
- 從零開始實現一個簡易的Java MVC框架(二)--實現Bean容器
- 從零開始實現一個簡易的Java MVC框架(三)--實現IOC
- 從零開始實現一個簡易的Java MVC框架(四)--實現AOP
- 從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點
- 從零開始實現一個簡易的Java MVC框架(六)--加強AOP功能
- 從零開始實現一個簡易的Java MVC框架(七)--實現MVC
- 從零開始實現一個簡易的Java MVC框架(八)--製作Starter
- 從零開始實現一個簡易的Java MVC框架(九)--優化MVC程式碼
原始碼地址:doodle