从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

栏目: 编程工具 · 发布时间: 6年前

内容简介:在前面在前面的文章中实现的AOP功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。这篇文章就要来实现多个代理的功能,也就是实现代理链。在com.zbw.aop包下创建一个类起名为

在前面 从零开始实现一个简易的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的功能设计里,多个切面的执行顺序是一种'先入后出'的顺序。比如说有两个切面 Aspect1Aspect2 ,那么他们的执行顺序应该是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(); ,这样就能形成一个递归调用。

现在来具体实现 AdviceChaindoAdviceChain() 方法。

...

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 是否匹配切点表达式,如果不匹配日则跳过这个 ProxyAdvisoradviceIndex 这个计数器加一,假如匹配的话,就执行 ProxyAdvisordoProxy() 方法,并且把自己当作参数传入过去。直到 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();
        }
    }
}
复制代码

代理链的功能又实现了,现在可以写测试用例了。

测试用例

先实现两个切面 DoodleAspectDoodleAspect2

// 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 测试类中调用 DoodleControllerhello() 方法。

@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();
    }
}
复制代码
从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

在结果的图中可以看出 DoodleAspectDoodleAspect2 两个代理方法都执行了,并且是按照预期的执行顺序执行的。

源码地址: doodle

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Semi-Supervised Learning

Introduction to Semi-Supervised Learning

Xiaojin Zhu、Andrew B. Goldberg / Morgan and Claypool Publishers / 2009-6-29 / USD 40.00

Semi-supervised learning is a learning paradigm concerned with the study of how computers and natural systems such as humans learn in the presence of both labeled and unlabeled data. Traditionally, le......一起来看看 《Introduction to Semi-Supervised Learning》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具