【小家Spring】Spring AOP核心类Pointcut解析,对PointcutExpression切点表达式解析原理分析(以AspectJExpressionPointcut为例)(中)

简介: 【小家Spring】Spring AOP核心类Pointcut解析,对PointcutExpression切点表达式解析原理分析(以AspectJExpressionPointcut为例)(中)

这个举例事基于XML的,之前我们都是这么来用的。那么现在用Java代码的方式也实现一遍(不需要Spring容器):


    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new Person());
        //声明一个aspectj切点,一张切面
        JdkRegexpMethodPointcut cut = new JdkRegexpMethodPointcut();
        //cut.setPattern("com.fsx.maintest.Person.run"); //它会拦截Person类下所有run的方法(无法精确到方法签名)
        //cut.setPattern(".*run.*");//.号匹配除"\r\n"之外的任何单个字符。*号代表零次或多次匹配前面的字符或子表达式  所以它拦截任意包下任意类的run方法
        cut.setPatterns(new String[]{".*run.*", ".*say.*"}); //可以配置多个正则表达  式...  sayHi方法也会被拦截 
        // 声明一个通知(此处使用环绕通知 MethodInterceptor )
        Advice advice = (MethodInterceptor) invocation -> {
            System.out.println("============>放行前拦截...");
            Object obj = invocation.proceed();
            System.out.println("============>放行后拦截...");
            return obj;
        };
        //切面=切点+通知
        // 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数
        // Pointcut.TRUE:表示啥都返回true,也就是说这个切面作用于所有的方法上/所有的方法
        // addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是Pointcut.TRUE切面,因此需要注意这些区别  相当于new DefaultPointcutAdvisor(Pointcut.TRUE,advice);
        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
        factory.addAdvisor(advisor);
        Person p = (Person) factory.getProxy();
        // 执行方法
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);
    }
}
class Person {
    public int run() {
        System.out.println("我在run...");
        return 0;
    }
    public void run(int i) {
        System.out.println("我在run...<" + i + ">");
    }
    public void say() {
        System.out.println("我在say...");
    }
    public void sayHi(String name) {
        System.out.println("Hi," + name + ",你好");
    }
    public int say(String name, int i) {
        System.out.println(name + "----" + i);
        return 0;
    }
}
输出:
============>放行前拦截...
我在run...
============>放行后拦截...
============>放行前拦截...
我在run...<10>
============>放行后拦截...
============>放行前拦截...
我在say...
============>放行后拦截...
============>放行前拦截...
Hi,Jack,你好
============>放行后拦截...
============>放行前拦截...
Tom----666
============>放行后拦截...


最后需要注意的是:RegexpMethodPointcutAdvisor没有提供不匹配的正则表达式注入方法,即没有excludedPatterns注入,如果需要该功能请还是使用JdkRegexpMethodPointcut。


AspectJExpressionPointcut详细使用和分析


AspectJExpressionPointcut如字面,它和Expression有关,而这个切点表达式,最终还是依赖于AspectJ的jar包去解析的~~~~ Spring在使用@Aspect注解时,会大量的用到它


用AspectJExpressionPointcut实现的切点比JdkRegexpMethodPointcut实现切点的好处就是,在设置切点的时候可以用切点语言来更加精确的表示拦截哪个方法。(可以精确到返回参数,参数类型,方法名,当然,也可以模糊匹配)


这里面就必须要先了解一下Pointcut的切点表达式的写法,以及Spring支持哪些呢?推荐博文:

【小家Spring】Spring AOP中@Pointcut切入点表达式最全面使用介绍


纯Java方式Demo:


下面我先用一个纯Java的方式的例子,先体验一把:

    public static void main(String[] args) {
        //String pointcutExpression = "execution( int com.fsx.maintest.Person.run() )"; // 会拦截Person.run()方法
        //String pointcutExpression = "args()"; // 所有没有入参的方法会被拦截。  比如:run()会拦截,但是run(int i)不会被拦截
        // ... AspectJExpressionPointcut支持的表达式 一共有11种(也就是Spring全部支持的切点表达式类型)
        String pointcutExpression = "@annotation(org.springframework.test.context.transaction.AfterTransaction)"; // 拦截上方法标有@AfterTransaction此注解的任意方法们
        // =============================================================
        ProxyFactory factory = new ProxyFactory(new Person());
        //声明一个aspectj切点,一张切面
        AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
        cut.setExpression(pointcutExpression); // 设置切点表达式
        // 声明一个通知(此处使用环绕通知 MethodInterceptor )
        Advice advice = (MethodInterceptor) invocation -> {
            System.out.println("============>放行前拦截...");
            Object obj = invocation.proceed();
            System.out.println("============>放行后拦截...");
            return obj;
        };
        //切面=切点+通知
        // 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数
        // Pointcut.TRUE:表示啥都返回true,也就是说这个切面作用于所有的方法上/所有的方法
        // addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是Pointcut.TRUE切面,因此需要注意这些区别  相当于new DefaultPointcutAdvisor(Pointcut.TRUE,advice);
        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
        factory.addAdvisor(advisor);
        Person p = (Person) factory.getProxy();
        // 执行方法
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);
    }
}
class Person {
    @AfterTransaction
    public int run() {
        System.out.println("我在run...");
        return 0;
    }
    public void run(int i) {
        System.out.println("我在run...<" + i + ">");
    }
    public void say() {
        System.out.println("我在say...");
    }
    public void sayHi(String name) {
        System.out.println("Hi," + name + ",你好");
    }
    public int say(String name, int i) {
        System.out.println(name + "----" + i);
        return 0;
    }
}



如上面的图,其实Spring也我们提供了AspectJExpressionPointcutAdvisor来专门处理基于AspectJ的通知+切点的


XML方式


public class Person {
    @AfterTransaction
    public int run() {
        System.out.println("我在run...");
        return 0;
    }
    public void run(int i) {
        System.out.println("我在run...<" + i + ">");
    }
    public void say() {
        System.out.println("我在say...");
    }
    public void sayHi(String name) {
        System.out.println("Hi," + name + ",你好");
    }
    public int say(String name, int i) {
        System.out.println(name + "----" + i);
        return 0;
    }
}
// 相当于准备了一个Advice
public class MyMethodInteceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("============>放行前拦截...");
        Object obj = invocation.proceed();
        System.out.println("============>放行后拦截...");
        return obj;
    }
}

书写Spring的xml配置文件:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 作为示例,这是需要被切入的目标类 -->
    <bean class="com.fsx.bean.Person"/>
    <!-- 切面=切点+通知 (采用面向切点语言进行配置切面)   此处为了便捷 直接使用 AspectJExpressionPointcutAdvisor -->
    <bean id="advisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
        <property name="expression"
                  value="@annotation(org.springframework.test.context.transaction.AfterTransaction)"></property>
        <!-- 一个Advisor里面对应一个advice~~~ -->
        <property name="advice">
            <bean class="com.fsx.aop.MyMethodInteceptor"/>
        </property>
    </bean>
</beans>


把该xml配置文件导入Config配置类,让它生效


...
@Configuration
@ImportResource(locations = "classpath:spring.xml")
public class RootConfig { ... }


启动Spring容器(采用JUnit测试):


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
    @Autowired
    private Person p;
    @Test
    public void test1() {
        System.out.println(p.getClass()); //class com.fsx.bean.Person$$EnhancerBySpringCGLIB$$cba1d735
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);
    }
}
输出:
class com.fsx.bean.Person$$EnhancerBySpringCGLIB$$cba1d735 // 说明它是CGLIB的代理
============>放行前拦截...
我在run...
============>放行后拦截...
我在run...<10>
我在say...
Hi,Jack,你好
Tom----666


备注:此xml的配置方式其实是最原始的Spring AOP的使用。并没有使用到<aop:config>、<aop:pointcut>这种标签注解,关于使用它们的xml配置方式,此处不做过多介绍了~~~因为也比较简单~


AspectJExpressionPointcut源码分析



// 很容易发现,自己即是ClassFilter,也是MethodMatcher   
// 它是子接口:ExpressionPointcut的实现类
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
    implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
    ...
    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
  // 从此处可以看出,Spring支持的AspectJ的切点语言表达式一共有10中(加上后面的自己的Bean方式一共11种)
  // AspectJ框架本省支持的非常非常多,详解枚举类:org.aspectj.weaver.tools.PointcutPrimitive
  static {
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
    SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
  }
  ...
  // 它持有BeanFactory 的引用,但是是可以为null的,也就是说它脱离容器也能够正常work
  @Nullable
  private BeanFactory beanFactory;
  ...
  // PointcutExpression是org.aspectj.weaver.tools.PointcutExpression是AspectJ的类
  // 它最终通过一系列操作,由org.aspectj.weaver.tools.PointcutParser#parsePointcutExpression从字符串表达式解析出来
  @Nullable
  private transient PointcutExpression pointcutExpression;
  ...
  // 由此可见,我们不仅仅可议写&& ||  !这种。也支持 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;
  }
  ...
  // 这是ClassFilter 匹配类。借助的PointcutExpression#couldMatchJoinPointsInType 去匹配
  public boolean matches(Class<?> targetClass) { ... }
  // MethodMatcher 匹配方法,借助的PointcutExpression和ShadowMatch去匹配的
  public boolean matches(Method method, @Nullable Class<?> targetClass, boolean hasIntroductions) { ... }
  @Override
  public boolean isRuntime() {
    //mayNeedDynamicTest 相当于由AspectJ框架去判断的(是否有动态内容)
    return obtainPointcutExpression().mayNeedDynamicTest();
  }
  ...
  // 初始化一个Pointcut的解析器。我们发现最后一行,新注册了一个BeanPointcutDesignatorHandler  它是准们处理Spring自己支持的bean() 的切点表达式的
  private PointcutParser initializePointcutParser(@Nullable ClassLoader classLoader) {
    PointcutParser parser = PointcutParser
        .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
            SUPPORTED_PRIMITIVES, classLoader);
    parser.registerPointcutDesignatorHandler(new BeanPointcutDesignatorHandler());
    return parser;
  }
  // 真正的解析,依赖于Spring自己实现的这个内部类(主要是ContextBasedMatcher 这个类,就会使用到BeanFactory了)
  private class BeanPointcutDesignatorHandler implements PointcutDesignatorHandler {
    private static final String BEAN_DESIGNATOR_NAME = "bean";
    @Override
    public String getDesignatorName() {
      return BEAN_DESIGNATOR_NAME;
    }
    // ContextBasedMatcher由Spring自己实现,对容器内Bean的匹配
    @Override
    public ContextBasedMatcher parse(String expression) {
      return new BeanContextMatcher(expression);
    }
  }
}


NameMatchMethodPointcut


如果创建切入点时候,我们往往只需要方法名字匹配,无需理会方法的签名和返回类型,这种情况下,我们可以使用 NameMatchMethodPointCut方法名字匹配切入点。(这种功能最弱,但显然效率是最高的)


public class Main {
    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new Person());
        // 声明一个通知(此处使用环绕通知 MethodInterceptor )
        Advice advice = (MethodInterceptor) invocation -> {
            System.out.println("============>放行前拦截...");
            Object obj = invocation.proceed();
            System.out.println("============>放行后拦截...");
            return obj;
        };
        声明一个aspectj切点,一张切面
        //NameMatchMethodPointcut cut = new NameMatchMethodPointcut();
        //cut.setMappedName("run"); //会匹配所有的方法名为run的方法
         切点+通知
        //Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
        advisor.setMappedName("run");
        advisor.setAdvice(advice);
        factory.addAdvisor(advisor);
        Person p = (Person) factory.getProxy();
        // 执行方法
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);
    }
}
输出:
============>放行前拦截...
我在run...
============>放行后拦截...
============>放行前拦截...
我在run...<10>
============>放行后拦截...
我在say...
Hi,Jack,你好
Tom----666


其它Pointcut


上面已经介绍了Spring中使用得比较多的Pointcut,接下来简单的讲述一下稍微偏门些的Pointcut。

从顶部的pointcut的继承图中可以看出,有很多实现类。


ControlFlowPointCut:流程切入点


如果有这样的特殊需求:我们对一个方法进行切入通知,但只有这个方法在一个特定方法中被调用的时候执行通知(即存在流程上行的依赖关系),我们可以使用ControlFlowPointCut流程切入点

public class Main {
    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new Person());
        // 声明一个通知(此处使用环绕通知 MethodInterceptor )
        Advice advice = (MethodInterceptor) invocation -> {
            System.out.println("============>放行前拦截...");
            Object obj = invocation.proceed();
            System.out.println("============>放行后拦截...");
            return obj;
        };
        声明一个aspectj切点,一张切面
        // 含义:Main类里面,方法名为funabc执行时,内部调用的任何代理的方法都会被拦截~~~ 它控制的是整个流程
        ControlFlowPointcut cut = new ControlFlowPointcut(Main.class, "funabc");
        // 切点+通知
        Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
        factory.addAdvisor(advisor);
        Person p = (Person) factory.getProxy();
        // 执行方法
        p.run();
        p.run(10);
        p.say();
        p.sayHi("Jack");
        p.say("Tom", 666);
        // 此处调用Main类,方法名为funabc的方法。内部代理对象的方法就都会被拦截上了
        funabc(p);
    }
    private static void funabc(Person person) {
        person.run();
        person.say();
    }
}
输出:
我在run...
我在run...<10>
我在say...
Hi,Jack,你好
Tom----666
============>放行前拦截...
我在run...
============>放行后拦截...
============>放行前拦截...
我在say...
============>放行后拦截...


使用流程切入点有时候可以解决不少问题,但值得注意的是:

使用流程切入点在jdk1.4中比其他切入点要慢5倍,在1.3上则要慢10倍,追求高性能的要慎重使用

相关文章
|
1月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。
|
8月前
|
数据采集 前端开发 JavaScript
金融数据分析:解析JavaScript渲染的隐藏表格
本文详解了如何使用Python与Selenium结合代理IP技术,从金融网站(如东方财富网)抓取由JavaScript渲染的隐藏表格数据。内容涵盖环境搭建、代理配置、模拟用户行为、数据解析与分析等关键步骤。通过设置Cookie和User-Agent,突破反爬机制;借助Selenium等待页面渲染,精准定位动态数据。同时,提供了常见错误解决方案及延伸练习,帮助读者掌握金融数据采集的核心技能,为投资决策提供支持。注意规避动态加载、代理验证及元素定位等潜在陷阱,确保数据抓取高效稳定。
256 17
|
9月前
|
XML Java 测试技术
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
592 25
|
12月前
|
自然语言处理 算法 Python
再谈递归下降解析器:构建一个简单的算术表达式解析器
本文介绍了递归下降解析器的原理与实现,重点讲解了如何使用Python构建一个简单的算术表达式解析器。通过定义文法、实现词法分析器和解析器类,最终实现了对基本算术表达式的解析与计算功能。
307 52
|
12月前
|
数据采集 自然语言处理 搜索推荐
基于qwen2.5的长文本解析、数据预测与趋势分析、代码生成能力赋能esg报告分析
Qwen2.5是一款强大的生成式预训练语言模型,擅长自然语言理解和生成,支持长文本解析、数据预测、代码生成等复杂任务。Qwen-Long作为其变体,专为长上下文场景优化,适用于大型文档处理、知识图谱构建等。Qwen2.5在ESG报告解析、多Agent协作、数学模型生成等方面表现出色,提供灵活且高效的解决方案。
1196 49
|
12月前
|
测试技术 开发者 Python
使用Python解析和分析源代码
本文介绍了如何使用Python的`ast`模块解析和分析Python源代码,包括安装准备、解析源代码、分析抽象语法树(AST)等步骤,展示了通过自定义`NodeVisitor`类遍历AST并提取信息的方法,为代码质量提升和自动化工具开发提供基础。
920 9
|
12月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
458 8
|
11月前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
431 4
|
10月前
|
人工智能 供应链 搜索推荐
中国CRM市场深度分析:主流供应商排名与特点解析
随着中国企业数字化转型的深入,CRM(客户关系管理)软件市场迅速发展,形成了多个优秀解决方案提供商。销售易、纷享销客、明源云客、金蝶云之家、简道云、红圈营销和爱客CRM等供应商各具特色。销售易在大型企业市场表现突出,提供全链路营销销售一体化及强大的AI能力;纷享销客以易用性和高性价比著称,适合中小企业;明源云客专注房地产行业,提供全流程解决方案;金蝶云之家与ERP系统深度整合,适合传统制造业;简道云是低代码平台,灵活性高;红圈营销专注零售业,支持全渠道营销;爱客CRM则主打智能营销功能。企业在选择CRM时需综合考虑实施难度、价格定位、技术支持等因素,并结合自身需求进行试用和调研,确保选择最适合
|
12月前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
244 4

推荐镜像

更多
  • DNS