【小家Spring】面向切面编程之---Spring AOP的原理讲解以及源码分析(Cannot find current proxy: Set 'exposeProxy' property on )(上)

简介: 【小家Spring】面向切面编程之---Spring AOP的原理讲解以及源码分析(Cannot find current proxy: Set 'exposeProxy' property on )(上)

前言


一说Spring AOP大家肯定不陌生,它作为Spring Framwork的两大基石之一,在Spring的产品线中有着大量的应用。相信小伙伴们在平时工作的项目中,自己也写过类似的AOP代码。


那么本文主要从Spring AOP运行过程上,结合一定的源码整体上介绍Spring AOP的一个运行过程。

知其然,知其所以然,相信我们使用起来才更有底气。


什么是AOP


AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。


请注意,本文所指、所讲的AOP,只代表AOP在Spring中的应用


另外,需要说明的是,本文对AOP最基本的概念、和最基本的使用,将不再占用篇幅说明了,因为默认你已经掌握了这些概念和基础知识

AOP能做什么


Spring框架的AOP机制可以让开发者把业务流程中的通用功能抽取出来,单独编写功能代码。在业务流程执行过程中,Spring框架会根据业务流程要求,自动把独立编写的功能代码切入到流程的合适位置。


比如:登录校验、日志输出等等。。。


前面我们已经大篇幅得聊了Spring IoC技术的原理和源码分析,那么本篇文章将从实践到理论上分析一下Spring得另外一大强大基石技术:Spring AOP


环境准备


导入jar包,基于之前得基础spring-web基础环境,还需导入如下jar包


        <!-- AOP相关Jar包 此包其实一般都可以不显导入,但aspectjweaver这个jar是必须的-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.framework.version}</version>
        </dependency>
        <dependency>
            <!-- 大名鼎鼎得@Aspect/@Pointcut系列的注解 都在此处 -->
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>


对于jar包的导入,这里做一个说明:像spring-core、spring-context、spring-beans这几个基础的jar包,一般都是不需要显示的导入的。因为只要你使用到了它的功能包比如spring-web、spring-aop等都会自动帮你导入进来。


另外说明一点:导入spring-context就自动导入了aop包。而我们最常导入的spring-webmv包,它其实包含了非常多的jar,都不建议再重复导入了。放过pom.xml,让它更清爽点吧



创建一个切面:


@Aspect
public class HelloAspect {
    // 只拦截service层的所有方法
    @Pointcut("execution(* com.fsx.service.*.*(..)) ")
    public void point() {
    }
    @Before("point()")
    public void before() {
        System.out.println("this is from HelloAspect#before...");
    }
    @After("point()")
    public void after() {
        System.out.println("this is from HelloAspect#after...");
    }
    @AfterReturning("point()")
    public void afterReturning() {
        System.out.println("this is from HelloAspect#afterReturning...");
    }
    @AfterThrowing("point()")
    public void afterThrowing() {
        System.out.println("this is from HelloAspect#afterThrowing...");
    }
  // 此处需要注意:若写了@Around方法,那么最后只会执行@Around和@AfterReturning 其它的都不会执行
    //@Around("point()")
    //public void around() {
    //    System.out.println("this is from HelloAspect#around...");
    //}
}


这里需要说明几点


  • Spring自己没有定义关于切面相关的注解,而是使用来自org.aspectj这个Jar包里面的注解的(但是没有用它的技术解析,这点需要明白)
  • org.aspectj.lang.annotation还有很多其余的注解,但是这里列出Spring只支持的注解如下:
  • @Aspect、@Before、@After、@AfterReturning、@AfterThrowing、@Around 其余的注解Spring都是不予解析的(由AspectJ内部技术去解析)


把切面加入容器 和 开启AspectJ得支持(二者缺一不可)


@Component
@EnableAspectJAutoProxy
@Aspect
public class HelloAspect {
  ...
}


本文为了简便,就直接采用@Component以及直接把@EnableAspectJAutoProxy写在切面类上面了,但是大多数情况我们都用@Bean的方式注册切面,且@EnablXXX写在对应的Config类上,方便管理(我是推荐这么做的,但是本文仅仅是为了方便而已)


至于为什么我可以这么乱写也都能生效,这个在前面博文中都有重点分析过,不明白的可以出门左拐~~~


执行结果如下:

this is from HelloAspect#before...
this is my method~~
this is from HelloAspect#after...
this is from HelloAspect#afterReturning...



从执行结果来看,我们的切面已经生效了。并且我们也能看得到各通知的执行时机


附:


    @Override
    public Object hello() {
        System.out.println("this is my method~~");
        // 此处特意把this输出来对比,请务必注意差别
        System.out.println(this.getClass()); //class com.fsx.service.HelloServiceImpl
        System.out.println(beanFactory.getBean(HelloService.class).getClass()); //class com.sun.proxy.$Proxy32   是JDK的动态代理了
        return "service hello";
    }


此处做一步输出,我们发现this代表还是本对象。而Spring容器内的HelloServiceImpl已经是一个JDK得动态代理对象了(HelloServiceImpl实现了接口)

@After和@AfterReturning的区别如下:


try{
    try{
        //@Before
        method.invoke(..);
    }finally{
        //@After
    }
    //@AfterReturning
}catch(){
    //@AfterThrowing
}



  1. 它俩得执行时机不同,@After先执行,@AfterReturning后执行
  2. @AfterReturning它能拿到目标方法执行完的返回值,但是@After不行
  3. @After它在finnaly里面,所以它不管怎么样都会执行(哪怕目标方法抛出异常),但是@AfterReturning如国目标方法没有正常return(比如抛出异常了),它是不会执行的


AspectJ 框架它定义的通知类型有 6 种:

  • 前置通知 @Before 相当于 BeforeAdvice
  • 后置通知 @AfterReturning 相当于 AfterReturningAdvice
  • 环绕通知 @Around 相当于 MethodInterceptor
  • 抛出通知 @AfterThrowing 相当于 ThrowAdvice
  • 最终通知 @After 不管是否异常,该通知都会执行
  • 引介增强:org.springframework.aop.IntroductionInterceptor,表示在目标类中添加一些新的方法和属性。


引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,可以为目标类创建实现某接口的代理。

引介增强的配置与一般的配置有较大的区别:首先,需要指定引介增强所实现的接口;其次,由于只能通过为目标类创建子类的方式生成引介增强的代理,所以必须将proxyTargetClass设置为true。


从上面可以看出,@AspectJ的方式相比较于传统的方式, 多了一个最终通知



切点类型:Spring提供了六种类型切点


  • 静态方法切点:org.springframework.aop.support.StaticMethodMatcherPointcut是静态方法切点的抽象基类,默认情况下它匹配所有的类。(NameMatchMethodPointcut提供简单字符串匹配方法签名,AbstractRegexpMethodPointcut使用正则表达式匹配方法签名。) 它不考虑方法入参个数、类型匹配
  • 动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut是动态方法的抽象基类,默认情况下它匹配所有的类 它会考虑方法入参个数、类型匹配
  • 注解切点:org.springframework.aop.support.annotation.AnnotationMatchingPointcut实现类表示注解切点。使用AnnotationMatchingPointcut支持在Bean中直接通过JDK 5.0注解标签定义切点
  • 表达式切点:org.springframework.aop.support.ExpressionPointcut接口主要是为了支持AspectJ切点表达式语法而定义的接口。 这个是最强大的,Spring支持11种切点表达式
  • 流程切点:org.springframework.aop.support.ControlFlowPointcut实现类表示控制流程切点。ControlFlowPointcut是一种特殊的切点,它根据程序执行堆栈的信息查看目标方法是否由某一个方法直接或间接调用,以此判断是否为匹配的连接点。
  • 复合切点:org.springframework.aop.support.ComposablePointcut实现类是为创建多个切点而提供的方便操作类。它所有的方法都返回ComposablePointcut类。


切面类型


切面的分类:


  • Advisor:代表一般切面,它仅包含一个Advice。这个切面太宽泛,一般不会直接使用。
  • PointcutAdvisor:代表具有切点的切面,它包含Advice和Pointcut两个类。
  • IntroductionAdvisor:代表引介切面。引介切面是对应引介增强的特殊的切面,它应用于类层面上。


PointcutAdvisor主要有6个具体的实现类:


  • DefaultPointcutAdvisor:最常用的切面类型
  • NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面
  • RegexpMethodPointcutAdvisor:对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作。
  • StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下,匹配所有的目标类。
  • AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面
  • AspectJPointcutAdvisor:用于AspectJ语法定义的切面。

相关文章
|
2月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
2月前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
2月前
|
Java Spring XML
掌握面向切面编程的秘密武器:Spring AOP 让你的代码优雅转身,横切关注点再也不是难题!
【8月更文挑战第31天】面向切面编程(AOP)通过切面封装横切关注点,如日志记录、事务管理等,使业务逻辑更清晰。Spring AOP提供强大工具,无需在业务代码中硬编码这些功能。本文将深入探讨Spring AOP的概念、工作原理及实际应用,展示如何通过基于注解的配置创建切面,优化代码结构并提高可维护性。通过示例说明如何定义切面类、通知方法及其应用时机,实现方法调用前后的日志记录,展示AOP在分离关注点和添加新功能方面的优势。
38 0
|
2月前
|
Java Spring 容器
彻底改变你的编程人生!揭秘 Spring 框架依赖注入的神奇魔力,让你的代码瞬间焕然一新!
【8月更文挑战第31天】本文介绍 Spring 框架中的依赖注入(DI),一种降低代码耦合度的设计模式。通过 Spring 的 DI 容器,开发者可专注业务逻辑而非依赖管理。文中详细解释了 DI 的基本概念及其实现方式,如构造器注入、字段注入与 setter 方法注入,并提供示例说明如何在实际项目中应用这些技术。通过 Spring 的 @Configuration 和 @Bean 注解,可轻松定义与管理应用中的组件及其依赖关系,实现更简洁、易维护的代码结构。
35 0
|
2月前
|
Java Spring 供应链
Spring 框架事件发布与监听机制,如魔法风暴席卷软件世界,开启奇幻编程之旅!
【8月更文挑战第31天】《Spring框架中的事件发布与监听机制》介绍了Spring中如何利用事件发布与监听机制实现组件间的高效协作。这一机制像城市中的广播系统,事件发布者发送消息,监听器接收并响应。通过简单的示例代码,文章详细讲解了如何定义事件类、创建事件发布者与监听器,并确保组件间松散耦合,提升系统的可维护性和扩展性。掌握这一机制,如同拥有一把开启高效软件开发大门的钥匙。
39 0
|
3月前
|
Java Spring 容器
Spring问题之Spring AOP是如何实现面向切面编程的
Spring问题之Spring AOP是如何实现面向切面编程的
|
2月前
|
XML Java 数据库
Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
这篇文章是Spring5框架的实战教程,详细解释了AOP的关键术语,包括连接点、切入点、通知、切面,并展示了如何使用AspectJ注解来开发AOP实例,包括切入点表达式的编写、增强方法的配置、代理对象的创建和优先级设置,以及如何通过注解方式实现完全的AOP配置。
|
3月前
|
Java Spring
Spring AOP(面向切面编程)详解
Spring AOP(面向切面编程)详解
|
3月前
|
开发框架 监控 Java
Spring Boot中的反应式编程最佳实践
Spring Boot中的反应式编程最佳实践
|
3月前
|
XML Java 数据格式
Spring6(三):面向切面AOP(3)
Spring6(三):面向切面AOP(3)
23 0
下一篇
无影云桌面