A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

实现代理链在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()方法。
@Slf4jpublic 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两个代理方法都执行了,并且是按照预期的执行顺序执行的。

源码地址:doodle
原文地址:从零开始实现一个简易的Java MVC框架(六)--加强AOP功能


作者:zzzzbw
链接:https://juejin.im/post/5b4beedc5188251abb46a843



9 个回复

倒序浏览
优秀,奈斯
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
优秀,奈斯
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
回复 使用道具 举报
gzsba 中级黑马 2018-7-28 00:48:31
9#
谢谢大神指导,希望以后经常看到这么优秀的帖子
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马