前言
在前面从零开始实现一个简易的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<ProxyAdvisor>,所以现在要用该方法用于生成一个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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。