自研Spring AOP2.0(三)

声明

本篇文章除部分引用外,均为原创内容,如有雷同纯属巧合,引用转载请附上原文链接与声明。

阅读条件

读本篇文章需掌握java基础知识,熟悉代理模式,了解ClassLoader加载类的流程,掌握注解的使用方式,灵活运用反射,阅读自研Spring IOC(一),阅读自研Spring AOP(二),Spring AOP切点定义

注意

本文若包含部分下载内容,本着一站式阅读的想法,本站提供其对应软件的直接下载方式,但是由于带宽原因下载缓慢是必然的,建立读者去相关官网进行下载,若某些软件禁止三方传播,请在主页上通过联系作者的方式将相关项目进行取消。

相关文章
文章大纲
  • 问题分析
  • 改进方案
  • 使用举例
简述

项目的github源码地址传送门点此,该篇文章对应的分支为aop-2.0
该篇文章将在自研Spring AOP(二)基础上,在切点定义上新增对于Aspect语法树的支持,以达到灵活的切点定义能力,事实上Spring AOP也是采用的该语法树来对切点进行的描述,所以从理论上讲,本框架所支持的AOP切点定义与Spring AOP所支持的相同。

问题分析

自研Spring AOP(二)中,完成了对于AOP功能的实现,主要是通过动态代理来进行的实现,具体有以下流程

  • 定义切点
  • 识别切点位置
  • 对切点生效处创建对应的动态代理对象
  • IOC过程中优先扫描代理对象缓存池

上述流程确实是体现了AOP的横向织入概念,但是通过@Aspect定义切面时,所声明的切点只能是某注解标记的类,该切点粒度较大,也就意味着如果一个类在某切点的效果范围内时,那么该类的所有方法均会被代理。但Spring AOP中通过AspectJ语法树做到了对方法级别上的切点定义,即切点可以声明只对哪些方法生效,而在自研Spring AOP(二)可以理解为只能对目标代理类的全部方法生效。所以为了解决该问题,在本文中将引入AspectJ语法树以支持同样粒度的切点定义,需要注意的是,本文只是对AOP切点的增加,并未在AOP的概念上做任何延展,仅是提供一个升级版的AOP实现。

改进方案
引入AspectJ语法树

在spring-aop模块中的pom.xml中添加以下依赖,该包有AspectJ语法树支持

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
    <scope>runtime</scope>
</dependency>
修改@Aspect注解

在aop-1.0分支的@Aspect注解,如下,可以看到value()只能声明为对某一类注解标记的类生效

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {

    /**
     * 对哪一类注解进行切面织入
     *
     * @return
     */
    Class<? extends Annotation> value();

    /**
     * 该切面类的执行顺序,按照从小到大执行
     *
     * @return
     */
    int order() default Integer.MAX_VALUE;
}

修改后的@Aspect注解,如下,可以看到value改为了pointCut,为String类型,保持与Spring AOP中的定义相同

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {

    /**
     * 该切面的切点定义,这里引用了AspectJ语法树,使用官方Spring aop相同
     *
     * @return
     */
    String pointcut();

    /**
     * 该切面类的执行顺序,按照从小到大执行
     *
     * @return
     */
    int order() default Integer.MAX_VALUE;
}
修改 DefaultAspect

在aop-1.0分支中的DefaultAspect类,如下,仅声明了被代理方法执行前的方法和被代理方法执行后的方法

public abstract class DefaultAspect {

    public void before() throws Exception {

    }

    public void afterReturn() throws Exception {

    }
}

而现在由于切点的定义发生了改变,所以需要新增对于匹配类和匹配方法的判断方法,当切点匹配到某个类时,则需要创建该类的代理类。若切点匹配到指定方法时,则说明该代理类的此方法被执行时需要被代理,并不是所有代理类的方法都会被代理执行,只有与切点相互匹配时(在切点的管理范围内),才需要执行代理逻辑。
在新的切面中,新增了类变量PARSER,这是AspectJ提供的语法支持,可以在初始化时指定需要支持的原语种类,这里选择的是支持所有原语,即execution表达式,within表达式...等等。新增了expession成员变量,改变量是用来判断切点定义是否匹配指定类(isMathcClass方法),是否匹配指定方法(isMatchMethod),需要注意的是expression在对指定类判断是否匹配时只对within语法起效,其他类型表达式均返回true,而对指定方法判断是否匹配时则可以做到精准匹配,所以在执行时需要在相关代理类中对切面进行进一步筛选。
修改后的DefaultAspect类,在aop-2.0分支中,如下

public abstract class DefaultAspect {

    private static final PointcutParser PARSER = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
    private PointcutExpression expression;

    public DefaultAspect() {
        this.expression = PARSER.parsePointcutExpression(this.getClass().getAnnotation(Aspect.class).pointcut());
    }

    /**
     * 被代理对象方法执行前的逻辑
     *
     * @throws Exception
     */
    public abstract void before() throws Exception;

    /**
     * 被代理对象方法执行后的逻辑
     *
     * @throws Exception
     */
    public abstract void afterReturn() throws Exception;

    /**
     * 该切面是否匹配指定class对象
     *
     * @param targetClass
     * @return
     */
    public boolean isMatchClass(Class<?> targetClass) {
        return expression.couldMatchJoinPointsInType(targetClass);
    }

    /**
     * 该切面是否匹配指定的方法
     *
     * @param method
     * @return
     */
    public boolean isMatchMethod(Method method) {
        return expression.matchesMethodExecution(method).alwaysMatches();
    }
}
修改AspectChainMethodInterceptor

根据上文,在执行时,对切面进行过滤,修改后如下

public class AspectChainMethodInterceptor implements MethodInterceptor {

    private Object target;
    private List<? extends DefaultAspect> sortedAspectList;

    public AspectChainMethodInterceptor(Object target, List<? extends DefaultAspect> aspectList) {
        this.target = target;
        this.sortedAspectList = aspectList.stream()
                .sorted(Comparator.comparingInt(c -> c.getClass().getAnnotation(Aspect.class).order()))
                .collect(Collectors.toList());
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        List<? extends DefaultAspect> filterAspectList = filterAspectList(method, sortedAspectList);
        for (DefaultAspect defaultAspect : filterAspectList) {
            defaultAspect.before();
        }
        Object returnValue = method.invoke(target, args);
        List<? extends DefaultAspect> reverseOrderAspectList = filterAspectList.stream()
                .sorted(Comparator.comparingInt(c -> -1 * c.getClass().getAnnotation(Aspect.class).order()))
                .collect(Collectors.toList());
        for (DefaultAspect defaultAspect : reverseOrderAspectList) {
            defaultAspect.afterReturn();
        }
        return returnValue;
    }

    private List<? extends DefaultAspect> filterAspectList(Method method, List<? extends DefaultAspect> sortedAspectList) {
        return sortedAspectList.stream()
                .filter(v -> v.isMatchMethod(method))
                .collect(Collectors.toList());
    }
}
修改AspectChainInvocationHandler

同理修改AspectChainMethodInterceptor,修改后如下

public class AspectChainInvocationHandler implements InvocationHandler {

    private Object target;
    private List<? extends DefaultAspect> sortedAspectList;

    public AspectChainInvocationHandler(Object target, List<? extends DefaultAspect> aspectList) {
        this.target = target;
        this.sortedAspectList = aspectList.stream()
                .sorted(Comparator.comparingInt(c -> c.getClass().getAnnotation(Aspect.class).order()))
                .collect(Collectors.toList());
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        List<? extends DefaultAspect> filterAspectList = filterAspectList(method, sortedAspectList);
        for (DefaultAspect defaultAspect : filterAspectList) {
            defaultAspect.before();
        }
        Object returnValue = method.invoke(target, args);
        List<? extends DefaultAspect> reverseOrderAspectList = filterAspectList.stream()
                .sorted(Comparator.comparingInt(c -> -1 * c.getClass().getAnnotation(Aspect.class).order()))
                .collect(Collectors.toList());
        for (DefaultAspect defaultAspect : reverseOrderAspectList) {
            defaultAspect.afterReturn();
        }
        return returnValue;
    }

    private List<? extends DefaultAspect> filterAspectList(Method method, List<? extends DefaultAspect> sortedAspectList) {
        return sortedAspectList.stream()
                .filter(v -> v.isMatchMethod(method))
                .collect(Collectors.toList());
    }
}
修改容器的动态代理创建流程

这里只需要修改容器判断一个类是否需要创建代理的逻辑,即判断bean是否在某些切面的切点定义范围内,修改后的beanContainer如下,可以看到getAspectByClass发生了改变,通过isMatchClass方法来进行过滤,如果存在切面,则说明需要创建代理类来融合切面和被代理类的逻辑。

public class BeanContainer {

    private static Map<Class<?>, Object> container = new ConcurrentHashMap<>();
    private static Map<Class<?>, Object> proxyBeanContainer = new ConcurrentHashMap<>();
    private static List<Class<? extends Annotation>> beanAnnotations = Arrays.asList(Controller.class, Service.class, Repository.class, Component.class, Aspect.class);

    private static Set<Class<?>> getClasses() {
        //  未作改变
    }

    public static Map<Class<?>, Object> getContainer() {
        //  未作改变
    }

    public static <T> T getBean(Class<T> clazz) {
        //  未作改变
    }

    public static void addBean(Class<?> clazz, Object bean) {
        //  未作改变
    }

    public static void addProxyBean(Class<?> clazz, Object bean) {
        //  未作改变
    }

    public static Object remove(Class<?> clazz) {
        //  未作改变
    }

    public static void doIoc() {
        //  未作改变
    }

    public static void doAop() {
        //  未作改变
    }

    public static void doBeanScan(String basePackage) {
        //  未作改变
    }

    private static Object createProxy(Map.Entry<Class<?>, Object> entry) {
        //  未作改变
    }

    /**
     * 判断该类是否存在其相应的切面配置
     * @param clazz
     * @return 该类是否存在其相关的切面配置
     */
    private static boolean needProxy(Class<?> clazz) {
        return !clazz.isAnnotationPresent(Aspect.class) && !CollectionUtil.isNullOrEmpty(getAspectByClass(clazz));
    }

    public static Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation) {
        //  未作改变
    }

    /**
     * 获取class需要织入的切面类
     * @param clazz
     * @return
     */
    public static List<? extends DefaultAspect> getAspectByClass(Class<?> clazz) {
        return getBeansByAnnotation(Aspect.class).stream()
                .map(v -> (DefaultAspect) v)
                .filter(v -> v.isMatchClass(clazz))
                .collect(Collectors.toList());
    }

    private static void extractClassPath(String basePackage, String path, Set<Class<?>> classes) {
        //  未作改变
    }

    private static boolean needLoad(Class<?> clazz) {
        //  未作改变
    }
}
使用举例

相关代码请见aop-2.0分支的demo模块

创建切面
@Aspect(pointcut = "within(tech.chenx.service.*)", order = 2)
public class CommAspect extends DefaultAspect {

    @Override
    public void before() throws Exception {
        log.info("CommAspect.before() was invoke at [{}]", System.currentTimeMillis());
    }

    @Override
    public void afterReturn() throws Exception {
        log.info("CommAspect.afterReturn() was invoke at [{}]", System.currentTimeMillis());
    }
}

@Aspect(pointcut = "execution(* tech.chenx..*.*(..))", order = 1)
public class LogAspect extends DefaultAspect {

    @Override
    public void before() throws Exception {
        log.info("LogAspect.before() was invoke at [{}]", System.currentTimeMillis());
    }

    @Override
    public void afterReturn() throws Exception {
        log.info("LogAspect.afterReturn() was invoke at [{}]", System.currentTimeMillis());
    }
}
创建Controller
@Controller
public class ExampleController{

    @Autowired
    private IExampleService service;

    @Override
    public String show() {
        log.info("ExampleController.show()");
        service.show();
        return "controller show method";
    }
}
创建Service
public interface IExampleService {
    void show();
}
@Service
public class ExampleServiceImpl implements IExampleService {

    @Autowired
    private IExampleRepository repository;

    @Override
    public void show() {
        log.info("ExampleServiceImpl.show()");
        repository.show();
    }
}
创建Repository
@Repository
public class ExampleRepository implements IExampleRepository {

    @Override
    public void show() {
        log.info("ExampleRepository.show()");
    }
}
执行NobitaApplication

执行结果看出,LogAspect切面生效,对每一个类的方法均进行了拦截,而CommAspect仅仅拦截了Service的请求,且在LogAspect之后执行。结果正确。