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

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

实现代理链在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
10#
谢谢大神指导,希望以后经常看到这么优秀的帖子
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马