Spring系列之AOP分析之为目标类挑选合适的Advisor(五)

简介:

我们在之前的文章中分析了Advisor的生成过程以及在Advisor中生成Advise的过程。在这一篇文章中我们说一下为目标类挑选合适的Advisor的过程。通过之前的分析我们知道,一个切面类可以生成多个Advisor(多个切面类的话那就更多多的Advisor了),这些Advisor是否都能适用于我们的目标类呢?这就需要通过Advisor中所拥有的Pointcut来进行判断了。先回到我们最开始的例子:

//手工创建一个实例
AspectJService aspectJService = new AspectJServiceImpl();
//使用AspectJ语法 自动创建代理对象
AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(aspectJService);

我们在我们的AspectJProxyFactory中传入了我们的目标对象。我们再回到AspectJProxyFactory的addAdvisorsFromAspectInstanceFactory方法中。

    private void addAdvisorsFromAspectInstanceFactory(MetadataAwareAspectInstanceFactory instanceFactory) {
        //获取Advisor的过程我们在之前分析了
        List<Advisor> advisors = this.aspectFactory.getAdvisors(instanceFactory);
        //这句代码的意思是为我们的目标类挑选合适的Advisor也是我们这一次要分析的内容
        advisors = AopUtils.findAdvisorsThatCanApply(advisors, getTargetClass());
        AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(advisors);
        //为Advisor进行排序
        AnnotationAwareOrderComparator.sort(advisors);
        addAdvisors(advisors);
    }

在AspectJProxyFactory中是通过调用AopUtils中的findAdvisorsThatCanApply方法来为目标类挑选合适的Advisor的或者是进判断哪些Advisor可以作用于目标类。在这个方法中传入了两个参数,一个参数是Advisor的集合,一个参数是目标类Class。我们看一下getTargetClass()这个方法的内容:
AdvisedSupport#getTargetClass

    @Override
    public Class<?> getTargetClass() {
        //直接调用targetSource的getTargetClass方法
        //其实也是相当于调用target.getClass()
        return this.targetSource.getTargetClass();
    }

OK,下面我们进入到AopUtils#findAdvisorsThatCanApply中看一下这个方法的内容

    public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
        //如果传入的Advisor集合为空的话,直接返回这个空集合
        //这里没有判断candidateAdvisors不为null的情况 因为在获取Advisor的地方是先创建一个空的集合,再进行添加Advisor的动作
        //不过还是加一下不为null的判断更好一点
        if (candidateAdvisors.isEmpty()) {
            return candidateAdvisors;
        }
        //创建一个合适的Advisor的集合 eligible
        List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
        //循环所有的Advisor
        for (Advisor candidate : candidateAdvisors) {
            //如果Advisor是IntroductionAdvisor  引介增强 可以为目标类 通过AOP的方式添加一些接口实现
            //引介增强是一种比较我们接触的比较少的增强 我们可以在以后的文章单独做个分析
            if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
                eligibleAdvisors.add(candidate);
            }
        }
        //是否有引介增强
        boolean hasIntroductions = !eligibleAdvisors.isEmpty();
        for (Advisor candidate : candidateAdvisors) {
            //如果是IntroductionAdvisor类型的话 则直接跳过
            if (candidate instanceof IntroductionAdvisor) {
                // already processed
                continue;
            }
            //判断此Advisor是否适用于target
            if (canApply(candidate, clazz, hasIntroductions)) {
                eligibleAdvisors.add(candidate);
            }
        }
        return eligibleAdvisors;
    }

canApply方法的内容

    public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
        //如果是IntroductionAdvisor的话,则调用IntroductionAdvisor类型的实例进行类的过滤
        //这里是直接调用的ClassFilter的matches方法
        if (advisor instanceof IntroductionAdvisor) {
            return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
        }
        //通常我们的Advisor都是PointcutAdvisor类型
        else if (advisor instanceof PointcutAdvisor) {
            PointcutAdvisor pca = (PointcutAdvisor) advisor;
            //这里从Advisor中获取Pointcut的实现类 这里是AspectJExpressionPointcut
            return canApply(pca.getPointcut(), targetClass, hasIntroductions);
        }
        else {
            // It doesn't have a pointcut so we assume it applies.
            return true;
        }
    }

重载canApply方法的内容。

    public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
        Assert.notNull(pc, "Pointcut must not be null");
        //进行切点表达式的匹配最重要的就是 ClassFilter 和 MethodMatcher这两个方法的实现。
        //MethodMatcher中有两个matches方法。一个参数是只有Method对象和targetclass,另一个参数有
        //Method对象和targetClass对象还有一个Method的方法参数 他们两个的区别是:
        //两个参数的matches是用于静态的方法匹配 三个参数的matches是在运行期动态的进行方法匹配的
        //先进行ClassFilter的matches方法校验
        //首先这个类要在所匹配的规则下
        if (!pc.getClassFilter().matches(targetClass)) {
            return false;
        }
        //再进行 MethodMatcher 方法级别的校验
        MethodMatcher methodMatcher = pc.getMethodMatcher();
        if (methodMatcher == MethodMatcher.TRUE) {
            // No need to iterate the methods if we're matching any method anyway...
            return true;
        }

        IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
        if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
            introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
        }

        Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
        classes.add(targetClass);
        for (Class<?> clazz : classes) {
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            //只要有一个方法能匹配到就返回true
            //这里就会有一个问题:因为在一个目标中可能会有多个方法存在,有的方法是满足这个切点的匹配规则的
            //但是也可能有一些方法是不匹配切点规则的,这里检测的是只有一个Method满足切点规则就返回true了
            //所以在运行时进行方法拦截的时候还会有一次运行时的方法切点规则匹配
            for (Method method : methods) {
                if ((introductionAwareMethodMatcher != null &&
                        introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
                        methodMatcher.matches(method, targetClass)) {
                    return true;
                }
            }
        }

        return false;
    }

从上面的代码来看,这次我们要分析的重点就在AspectJExpressionPointcut这个类中了。在AspectJExpressPointcut中预先初始化了这些内容:你能看出来这是什么内容吗?

    static {
        //execution
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        //args
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
        //this
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
        //target
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
        //within
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
        //@annotation
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
    }

我们来看一下这段代码,这是要从AspectJExpressPointcut中获取ClassFilter

        if (!pc.getClassFilter().matches(targetClass)) {
            return false;
        }
    public ClassFilter getClassFilter() {
        checkReadyToMatch();
        return this;
    }
    private void checkReadyToMatch() {
        //如果没有expression的值的话 直接抛出异常
        if (getExpression() == null) {
            throw new IllegalStateException("Must set property 'expression' before attempting to match");
        }
        if (this.pointcutExpression == null) {
            //选择类加载器
            this.pointcutClassLoader = determinePointcutClassLoader();
            //构建PointcutExpression的实例
            this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
        }
    }
    //下面的这一段逻辑完全就是aspectj这个jar中的内容了 很复杂不多说了。。。。
    private PointcutExpression buildPointcutExpression(ClassLoader classLoader) {
        //初始化一个PointcutParser的实例 PointcutParser   aspectj中提供的类
        PointcutParser parser = initializePointcutParser(classLoader);
        PointcutParameter[] pointcutParameters = new PointcutParameter[this.pointcutParameterNames.length];
        for (int i = 0; i < pointcutParameters.length; i++) {
            pointcutParameters[i] = parser.createPointcutParameter(
                    this.pointcutParameterNames[i], this.pointcutParameterTypes[i]);
        }
        //解析切点表达式 我们的切点表示有可能会这样写:在切面中定义一个专门的切面表达式方法
        //在不同的通知类型中引入这个切点表达式的方法名 
        return parser.parsePointcutExpression(replaceBooleanOperators(getExpression()),
                this.pointcutDeclarationScope, pointcutParameters);
    }
    //这个方法 将表达式中的 and 替换为 && or 替换为 ||   not 替换为 !
    private String replaceBooleanOperators(String pcExpr) {
        String result = StringUtils.replace(pcExpr, " and ", " && ");
        result = StringUtils.replace(result, " or ", " || ");
        result = StringUtils.replace(result, " not ", " ! ");
        return result;
    }

由于我们在项目开发中使用SpringAOP基本上都是用的AspectJ的注解的形式。AspectJ对于切点的匹配规则解析是一个比较复杂的过程,所以我们在使用的AspectJ中的切点表达式的时候要按照它的一个规则来进行书写。

相关文章
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
641 73
|
9月前
|
负载均衡 Java API
基于 Spring Cloud 的微服务架构分析
Spring Cloud 是一个基于 Spring Boot 的微服务框架,提供全套分布式系统解决方案。它整合了 Netflix、Zookeeper 等成熟技术,通过简化配置和开发流程,支持服务发现(Eureka)、负载均衡(Ribbon)、断路器(Hystrix)、API网关(Zuul)、配置管理(Config)等功能。此外,Spring Cloud 还兼容 Nacos、Consul、Etcd 等注册中心,满足不同场景需求。其核心组件如 Feign 和 Stream,进一步增强了服务调用与消息处理能力,为开发者提供了一站式微服务开发工具包。
773 0
|
11月前
|
前端开发 IDE Java
Spring MVC 中因导入错误的 Model 类报错问题解析
在 Spring MVC 或 Spring Boot 开发中,若导入错误的 `Model` 类(如 `ch.qos.logback.core.model.Model`),会导致无法解析 `addAttribute` 方法的错误。正确类应为 `org.springframework.ui.Model`。此问题通常因 IDE 自动导入错误类引起。解决方法包括:删除错误导入、添加正确包路径、验证依赖及清理缓存。确保代码中正确使用 Spring 提供的 `Model` 接口以实现前后端数据传递。
403 0
|
11月前
|
SQL 前端开发 Java
深入分析 Spring Boot 项目开发中的常见问题与解决方案
本文深入分析了Spring Boot项目开发中的常见问题与解决方案,涵盖视图路径冲突(Circular View Path)、ECharts图表数据异常及SQL唯一约束冲突等典型场景。通过实际案例剖析问题成因,并提供具体解决方法,如优化视图解析器配置、改进数据查询逻辑以及合理使用外键约束。同时复习了Spring MVC视图解析原理与数据库完整性知识,强调细节处理和数据验证的重要性,为开发者提供实用参考。
450 0
|
Java Spring
【Spring配置相关】启动类为Current File,如何更改
问题场景:当我们切换类的界面的时候,重新启动的按钮是灰色的,不能使用,并且只有一个Current File 项目,下面介绍两种方法来解决这个问题。
306 10
|
XML Java 数据格式
Spring Core核心类库的功能与应用实践分析
【12月更文挑战第1天】大家好,今天我们来聊聊Spring Core这个强大的核心类库。Spring Core作为Spring框架的基础,提供了控制反转(IOC)和依赖注入(DI)等核心功能,以及企业级功能,如JNDI和定时任务等。通过本文,我们将从概述、功能点、背景、业务点、底层原理等多个方面深入剖析Spring Core,并通过多个Java示例展示其应用实践,同时指出对应实践的优缺点。
206 14
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
4077 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
1726 2
|
6月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
676 0
|
5月前
|
监控 Java Spring
AOP 切面编程
AOP(面向切面编程)通过动态代理在不修改源码的前提下,对方法进行增强。核心概念包括连接点、通知、切入点、切面和目标对象。常用于日志记录、权限校验、性能监控等场景,结合Spring AOP与@Aspect、@Pointcut等注解,实现灵活的横切逻辑管理。
1465 6
AOP 切面编程