Spring AOP详解之面向切面编程

spring Aop切面的环绕通知时,如何防止切面异常影响主业务流程执行 一、前言 我们在做切面编程时,可能会使用到的环绕通知@Around。示例代码如下: @Aspect @Component public class MyAspect { @Around("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 阅读详情

本篇文章记录 Spring 中两大特性之AOP的相关知识:

🚨一、AOP—另一种编程思想

AOP(Aspect-Oriented Program,即面向切面编程)和OOP(Object-Oriented Programming, 面向对象编程)是Java中两种不同的抽象设计架构。AOP是通过预编译方式,在运行期通过代理的方式向目标类织入增强代码实现程序功能的统一维护的一种技术。Spring 中 AOP 代理由 Spring 的 IoC 容器负责生成和管理,其依赖关系也由 IoC 容器负责管理,因此两者相辅相成。

1. AOP与OOP

AOP与OOP是面向不同领域的两种设计思想。

  • OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。OOP的基本单位是类(class) 。
  • AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。AOP的基本单位是Aspect(切面)。

OOP实际上是对对象的属性和行为的封装,而是处理某个步骤和阶段的,从中进行切面的提取。也就是说,如果几个或更多个逻辑过程中,有重复的操作行为,AOP就可以提取出来,运用动态代理,达到程序功能统一维护的效果。

这么说来可能太含蓄,如果说到权限判断,日志记录等,可能就明白了。如下图所示:

2. 为什么需要AOP

上面我们提到了AOP与OOP,但是如果我们单纯使用OOP,那么权限判断怎么办?在每个操作前都加入权限判断?日志记录怎么办?在每个方法里的开始、结束、异常的地方手动添加日志?所以,如果使用AOP就可以借助代理完成这些重复的操作,就能够在逻辑过程中,降低各部分之间的耦合了。二者扬长补短,互相结合最好。

首先,在面向切面编程的思想里面,把功能分为核心业务功能和周边功能。

  • 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
  • 所谓的周边功能,比如性能统计,日志,事务管理等等

周边功能(一般为重复的功能)在 Spring 的面向切面编程AOP思想里,即被定义为切面。

在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。

AOP主要应用于日志记录,性能统计,安全控制,事务处理以及缓存等方面,它是为程序员解耦而生,并且对核心业务代码零侵入。

因此我们知道了为什么要使用AOP以及AOP做的事情了:

  • 为什么使用AOP:切面将系统中重复的事情(包括日志记录,性能统计,安全控制,事务处理)解耦出来,将功能分为核心和周边功能,从而达到解耦系统的重复代码和工作的效果;
  • AOP做的事情:因为切面是为解耦重复的任务而生的,因此AOP只对与业务无关的周边功能进行切面。不用操作业务部分代码(也可以说不影响到正常业务代码的使用,但是如果切面有影响,那么影响也是全局的)。

3. AOP的相关术语

下面详细了解一些AOP的概念:

  • 切面(Aspect) :实现通用问题的类,例如日志,事务管理,定义了切入点和通知的类,通知和切入点共同组成了切面:时间、地点、做什么。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的Advisor或拦截器实现。
  • 连接点(Joinpoint) :程序能够使用通知(Advice)的一个时机,即程序执行过程中明确的点,一般是方法的调用,或异常被抛出。
  • 通知(Advice) :类似Spring拦截器或者Servlet过滤器,是方法,定义切面要做什么,何时使用,有Before、After、Around等。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链(递归拦截器)。
  • 切点(Pointcut) :定义切面发生在哪里,带了通知的连接点,例如某个类或方法的名称,在程序中主要体现为切入点表达式。AOP框架必须允许开发者指定切入点,例如,使用正则表达式。
  • 引入(Introduction) :添加方法或字段到被通知的类。Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现IsModified接口,来简化缓存。
  • 目标对象(Target Object) :包含连接点的对象,也被称作被通知或被代理对象。
  • AOP代理(AOP Proxy) :AOP框架创建的对象,包含通知。在Spring中,AOP代理可以是JDK或CGLIB动态代理。
  • 编织(Weaving) :把切面应用到目标对象时,创建新的代理对象的过程。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

AOP指示器参数

通知方法注解

总结

  • 切入点(Pointcut) 在哪些类,哪些方法上切入(where)
  • 通知(Advice) 在方法执行的什么实际(when: 方法前/方法后/方法前后)做什么(what: 增强的功能)
  • 切面(Aspect) 切面 = 切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!
  • 织入(Weaving) 把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)

4. AOP的实现方式

AOP 要达到的效果是,保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。

AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码,其实就是代理模式的典型应用。 按照 AOP 框架修改源代码的时机,可以将其分为四类:

  • 静态 AOP : 在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP : 在运行阶段对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。
  • 动态代码字节生成:在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。
  • 自定义类加载器:在运行前,目标加载前,将切面逻辑加到目标字节码中。

因此Spring AOP是基于JDK和CGLib动态代理实现的,AspectJ是基于ASM与自定义ClassLoader实现的。

5. JDK和CGLib动态代理的区别

Spring AOP的实现是JDK和CGLib动态代理,这两种代理方式的使用了策略模式。

  1. 既然Spring AOP的实现是动态代理,那么JDK和CGLIB动态代理有什么区别呢?
  • JDK动态代理是面向接口的。JDK动态代理只能对实现了接口的类生成代理,而不能针对类。底层原理是:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,再调用具体方法前调用InvokeHandler来处理。
  • CGLIB动态代理是面向类的。CGLIB对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。主要是通过继承父类然后重写父类的方法实现,所以该类或方法最好不要声明成final。底层原理是:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
  1. 何时使用JDK和CGLib?
  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;
  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP;
  • 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLib之间转换。

因此,Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理。

  1. 那么我们开发时,JDK代理和CGLib代理该用哪个呢?

在《精通Spring4.x 企业应用开发实战》一书中给出了建议:

  • 如果是单例的我们最好使用CGLib代理,如果是多例的我们最好使用JDK代理。因此ASM框架一般是构建诸多开源框架的底层实现,但是随着JDK版本的提升,这个也有所改善。

原因:JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。

总结:

8fd11a48e2cfd659aa4cfe7f104c6fd8.png

🌌二、技术选型

AOP的实现有Spring AOP、AspectJ、JBoss AOP等。

本篇文章只在一定维度上讨论目前主流的AOP框架:Spring AOP 和 AspectJ。

1.Spring AOP和AspectJ

AspectJ是语言级别的AOP实现,扩展了Java语言,定义了AOP语法,能够在编译期提供横切代码的织入,所以它有专门的编译器用来生成遵守Java字节码规范的Class文件。

而Spring借鉴了AspectJ很多非常有用的做法,融合了AspectJ实现AOP的功能,可以说Spring AOP是新世纪的AspectJ。但Spring AOP本质上底层还是动态代理,所以Spring AOP是不需要有专门的编辑器配合(为了编织代码的各个方面, 它引入了称为 AspectJ 编译器 (ajc) 的编译器, 通过它编译我们的程序, 然后通过提供一个小型 (100K) 运行时库来运行它。)。

Spring aop 旨在提供一个跨 Spring IoC 的简单的 aop 实现, 以解决程序员面临的最常见问题。它不打算作为一个完整的 AOP 解决方案 —— 它只能应用于由 Spring 容器管理的 bean。

另一方面, AspectJ 是原始的 aop 技术, 目的是提供完整的 aop 解决方案。它更健壮, 但也比 Spring AOP 复杂得多。还值得注意的是, AspectJ 可以在所有域对象中应用。

对比总结

Spring AOPAspectJ
在纯 Java 中实现使用 Java 编程语言的扩展实现
不需要单独的编译过程除非设置 LTW,否则需要 AspectJ 编译器 (ajc)
只能使用运行时织入运行时织入不可用。支持编译时、编译后和加载时织入
功能不强-仅支持方法级编织更强大 - 可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等
只能在由 Spring 容器管理的 bean 上实现可以在所有域对象上实现
仅支持方法执行切入点支持所有切入点
代理是由目标对象创建的, 并且切面应用在这些代理上在执行应用程序之前 (在运行时) 前, 各方面直接在代码中进行织入
比 AspectJ 慢多了更好的性能,基准测试AspectJ 速度比 Spring AOP 快8到35倍
易于学习和应用相对于 Spring AOP 来说更复杂

2. 应用场景

Spring AOP只是针对Spring IOC容器进行设计的aop实现,因此适用于Spring进行管理的项目;

AspectJ更复杂,因为它引入了 AspectJ Java 工具 (包括编译器 (ajc)、调试器 (ajdb)、文档生成器 (ajdoc)、程序结构浏览器 (ajbrowser)), 我们需要将它们与我们的 IDE 或生成工具。要使用 AspectJ,我们需要引入 AspectJ 编译器 (ajc) 并重新打包所有的库 (除非我们切换到编译后或加载时间的织入)。

Spring AOP更加简单、开箱即用,AspectJ性能更高但更复杂,但需要特定编译器配合。

具体应用场景有:日志记录,性能统计,安全控制,事务处理以及缓存等。

3. 结论

Spring AOP项目用于Spring项目,AspectJ应用于Spring生态外的项目。

三、Spring AOP实践

AOP 的思想:减少重复的工作,让重复代码与业务代码分离!

在上图中,用户购物的核心业务就是下单和付款。而浏览商品、咨询商品以及售后都是重复且边缘的工作,每一个人在进行交易之前以及交易之后都需要重复进行这些工作,因此可以交给边缘业务其他人代理完成。因此,AOP的实现就是:围绕核心业务,以核心业务的时间先后为切入点来做核心业务的切面。

1. Maven依赖

开发环境为SpringBoot FrameWork,故使用AOP只需添加下述依赖即可:

pom.xml

<!-- spring aop依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.6.4</version>
</dependency>

2. 创建目标接口和接口实现类

目标类就是我们的核心,以方法执行先后时间为切入点,下单前、下单和下单后等地方都是插入切面通知的地方。

这些地方的集合就是连接点,而某一个具体的位置就是切点,连接点就是切点的集合。

OrderService.java

package com.deepinsea.springbootaop.springaop.service;
​
/**
 * @author deepinsea
 * @date 2022/4/12
 */
public interface OrderService {
​
    /**
     * 目标方法(订单)
     */
    void order();
}

OrderServiceImpl.java

package com.deepinsea.springbootaop.springaop.service.impl;
​
import com.deepinsea.springbootaop.springaop.service.OrderService;
import org.springframework.stereotype.Service;
​
/**
 * @author deepinsea
 * @date 2022/4/12
 */
@Service
public class OrderServiceImpl implements OrderService {
​
    @Override
    public void order() {
        // 仅仅是实现了核心的业务功能
        System.out.println("下单");
        System.out.println("付款");
    }
}

3. 创建切面类

创建一个类,将这个类变为切面。

  • @Aspect 将该类变为一个切面
  • @Component -- 将该类注册到Spring的容器里面
  • 切入点表达式:execution(* 包名.类名.方法名(..))

spring aop中的五种通知方式:

  • @Before:前置通知,在目标方法被调用之前调用通知功能
  • @After:后置通知,在目标方法执行结束后,无论执行结果如何都执行通知定义的任务
  • @After-returning:后置通知,在目标方法执行结束后,如果执行成功,则执行通知定义的任务
  • @After-throwing:异常通知,如果目标方法执行过程中抛出异常,则执行通知定义的任务
  • @Around:环绕通知,在目标方法执行前和执行后,都需要执行通知定义的任务。

前置(@Before)与最终通知(@After)

OrderAspect.java

package com.deepinsea.springbootaop.springaop.common.aspect;
​
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
​
/**
 * @author deepinsea
 * @date 2022/4/11
 * 切面类
 */
@Component
@Aspect
public class OrderAspect {
​
    @Before("execution(* com.deepinsea.springbootaop.springaop.service.impl.OrderServiceImpl.order())")
    public void beforeOrder() {
        System.out.println("下单之前--浏览商品");
        System.out.println("下单之前--咨询商品");
    }
​
    @After("execution(* com.deepinsea.springbootaop.springaop.service.impl.OrderServiceImpl.order())")
    public void afterOrder(){
        System.out.println("下单之后--售后");
    }
}

在定义完切面类后,我们发现所有方法的execution表达式内容完全一样,因此可以提取为一个公共的。这个也叫做切点(pointcut),是Spring AOP的高级抽象。使用@Pointcut实现重复内容的提取。

切点(@Pointcut)

OrderAspectPointcut.java

package com.deepinsea.springbootaop.springaop.common.aspect;
​
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
​
/**
 * @author deepinsea
 * @date 2022/4/12
 * 切面类
 */
@Component
@Aspect
public class OrderAspectPointcut {
​
    /**
     * 通过注解@Pointcut定义切点,iOrder()只是一个切点标识,无所谓是什么,
     * 方法中内容本身也是空的,使用该切点的地方直接通过切点方法iOrder引用切点表达式。
     */
    @Pointcut("execution(* com.deepinsea.springbootaop.springaop.service.impl.OrderServiceImpl.order())")
    public void iOrder(){}
​
    @Before("iOrder()")
    public void beforeOrder() {
        System.out.println("下单之前--浏览商品");
        System.out.println("下单之前--咨询商品");
    }
​
    @After("iOrder()")
    public void afterOrder(){
        System.out.println("下单之后--售后");
    }
}

环绕通知(@Around)

环绕通知,顾名思义,其会在目标方法被调用前后均会执行切面代码的一种通知,其可将前置通知、后置通知的代码放在一个通知方法当中,使得代码逻辑更加清晰。

这是Spring AOP中最强大的通知类型,因为它集成了前面所有通知的功能,同时保留了连接点原有的方法的功能,因此一个可以干掉上面4个注解。

注意

  • 只有环绕通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint。
  • 环绕通知需要返回返回值(对于有返回值的切点方法,本身为void的切面核心方法不用),否则真正调用者将拿不到返回值,只能得到一个null。
  • 环绕通知有控制目标方法是否执行、控制是否返回值和改变返回值的能力。

OrderAspectAround.java

package com.deepinsea.springbootaop.springaop.common.aspect;
​
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
​
/**
 * @author deepinsea
 * @date 2022/4/12
 * 切面类
 */
@Component
@Aspect
public class OrderAspectAround {
​
    // 环绕通知同样可以使用切点来简化
    @Pointcut("execution(* com.deepinsea.springbootaop.springaop.service.impl.OrderServiceImpl.order())")
    public void pointcutOrder(){}
​
    /**
     * 使用 @Around 注解来同时完成前置和最终通知
     * Spring AOP提供的ProceedingJoinPoint接口的proceed方法可以执行切点方法(并划分切点前后执行界限)
     * 以joinPoint.proceed()为界限,划分出前置通知、后置通知和最终通知以及异常通知
     */
    @Around("pointcutOrder()")
    public void aroundOrder(ProceedingJoinPoint joinPoint){
        System.out.println("下单之前--浏览商品");
        System.out.println("下单之前--咨询商品");
​
        try {
            joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
​
        System.out.println("下单之后--售后");
    }
​
    /**
     * 在有返回值的核心业务(切点)方法中,使用环绕通知需要给返回值,否则核心方法返回值为null;
     * 上面切点方法本身返回值为void,则不需要给返回值
     */
//    @Around("pointcutOrder()")
//    public Object aroundOrder(ProceedingJoinPoint joinPoint){
//        Object returnValue = null;
//        // 获取切点方法入参(形参)
//        Object[] args = joinPoint.getArgs();
//        // 执行前置方法
//        System.out.println("前置通知方法");
//        try {
//            returnValue = joinPoint.proceed(args);
//            System.out.println("后置通知方法");
//        } catch (Throwable e) {
//            e.printStackTrace();
//            System.out.println("异常通知方法");
//        }finally {
//            System.out.println("最终通知方法");
//        }
//
//        return returnValue;
//    }
}

上面三种方法都可以(使用其中一个,另外两个注释即可),只是一个更符合常规逻辑,一个进行了常规逻辑上的公共提取,另一个进行两者功能上的融合而已。推荐使用环绕通知进行aop配置。

扩展:Spring框架的设计非常优美,对多种设计模式进行了实现。在多切面执行时,就采用了责任链设计模式。

4. 启动AOP代理功能

切面类,目标方法都创建完了,但是切面类在启动时还不能自动代理目标方法。因为现在切面类和目标方法只是注入到Spring IOC容器的Bean,没有开启 @EnableAspectJAutoProxy注解启动AspectJ自动代理,这个开关默认是关闭的。说到底,Spring AOP是一种除了Spring本身以外,另一种由用户代理注册到Spring的Bean的一种方式。

注意:SpringBoot中无需显式指定@EnableAspectJAutoProxy注解,默认为开启;但Spring FrameWork需要手动开启

(1)注解的方式

我们可以以配置类的方式,通过@EnableAspectJAutoProxy注解启动AspectJ自动代理:

AspectConfig.java

package com.deepinsea.springbootaop.springaop.common.config;
​
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
​
/**
 * @author deepinsea
 * @date 2022/4/12
 * 切面配置类
 *
 * Jdk代理:基于接口的代理,一定是基于接口,会生成目标对象的接口的子对象。
 * Cglib代理:基于类的代理,不需要基于接口,会生成目标对象的子对象。
 *
 * 1\. 注解@EnableAspectJAutoProxy开启代理;
 * 2\. 如果属性proxyTargetClass默认为false, 表示使用jdk动态代理织入增强;
 * 3\. 如果属性proxyTargetClass设置为true,表示使用Cglib动态代理技术织入增强;
 * 4\. 如果属性proxyTargetClass设置为false,但是目标类没有声明接口,spring aop还是会使用Cglib动态代理,也就是说非接口的类要生成代理都用Cglib。
 */
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
//@ComponentScan("com.deepinsea.springbootaop.springaop")
public class AspectConfig {
}

(2)配置文件的方式

application.yml

spring:
  # AOP 配置
  aop:
    auto: true #spring aop开关, 相当于标注了@EnableAspectJAutoProxy
    proxy-target-class: true #开启cglib代理

注意:默认EnableAspectJAutoProxy是关闭的,并且需要proxy-target-class选项一定需要开启,否则会报错。

如果proxy-target-class属值被设置为false或者这个属性被省略,那么标准的JDK 基于接口的代理将起作用。即使你未声明 proxy-target-class="true" ,但运行类没有继承接口,spring也会自动使用CGLIB代理(高版本spring自动根据运行类选择 JDK 或 CGLIB 代理)

默认情况下,我们会在接口实现类加上@service注解以及进行事务注解的声明(全局声明或方法上声明都可),这种做法提供接口类服务供rpc调用是没问题的,客户端只需引入相应的jar包,spring中配置bean,使用时@Resource或@Autowired注解注入都可以。

5. 测试类进行测试

SpringbootAopApplicationTests.java

package com.deepinsea.springbootaop;
​
import com.deepinsea.springbootaop.springaop.service.impl.OrderServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
​
@SpringBootTest
class SpringbootAopApplicationTests {
​
    @Autowired
    private OrderServiceImpl orderService;
​
    /**
     * 测试AOP切面功能
     */
    @Test
    void testAop() {
        orderService.order();
    }
​
}

启动后,测试结果:

  .   ____          _            __ _ _
 /\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )___ | '_ | '_| | '_ / _` | \ \ \ \
 \/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |___, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.4)
​
2022-04-12 03:25:04.600  INFO 57392 --- [           main] c.d.s.SpringbootAopApplicationTests      : Starting SpringbootAopApplicationTests using Java 1.8.0_311 on aries with PID 57392 (started by deepinsea in E:\IdeaProjects\MyProjects\springboot-in-action\springboot-aop)
2022-04-12 03:25:04.609  INFO 57392 --- [           main] c.d.s.SpringbootAopApplicationTests      : No active profile set, falling back to 1 default profile: "default"
2022-04-12 03:25:05.079  INFO 57392 --- [           main] c.d.s.SpringbootAopApplicationTests      : Started SpringbootAopApplicationTests in 0.661 seconds (JVM running for 1.44)
下单之前--浏览商品
下单之前--咨询商品
下单
付款
下单之后--售后
​
Process finished with exit code 0
复制代码

下面为进阶使用

6. 自定义注解

JDK 1.5定义的常用元注解有以下几种:

  • @Target:用于描述注解的范围,即注解在哪用。
  • @Retention:用于描述注解的生命周期,表示需要在什么级别保存该注解,即保留的时间长短。
  • @Documented:用于将注释信息包含在javadoc中。
  • @Inherited:它指明被注解的类会自动继承,子类可以继承父类中的该注解。更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类, 父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中。

下面使用自定义的注解来实现Spring AOP:

OrderAnnotation.java

package com.deepinsea.springbootaop.springaop.common.annotation;
​
import java.lang.annotation.*;
​
/**
 * @author deepinsea
 * @date 2022/4/13
 * 自定义spring aop注解类
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OrderAnnotation {
}

OrderServiceAnnotationImpl.java

package com.deepinsea.springbootaop.springaop.service.impl;
​
import com.deepinsea.springbootaop.springaop.config.DeepinseaAOP;
import org.springframework.stereotype.Service;
​
/**
 * @author deepinsea
 * @date 2022/4/13
 * Service实现类(注解使用)
 */
@Service
public class OrderServiceAnnotationImpl {
​
    @DeepinseaAOP
    public void order(){
        // 自定义注解使用
        System.out.println("下单--自定义注解");
    }
}

@annotation 匹配带指定注解的连接点,通常使用方式就是将这个自定义注解标注到某个方法之上。

OrderAspectAnnotation.java

package com.deepinsea.springbootaop.springaop.common.aspect;
​
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
​
/**
 * @author deepinsea
 * @date 2022/4/13
 * 切面类
 */
@Component
@Aspect
public class OrderAspectAnnotation {
​
    /**
     * execution:用于匹配方法执行的连接点;
     * within:用于匹配指定类型内的方法执行;
     * this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
     * target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
     * args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
     * @within:用于匹配所以持有指定注解类型内的方法;
     * @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
     * @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
     * @annotation:用于匹配当前执行方法持有指定注解的方法;
     *
     * 注意:使用@annotation注解后,切点应为自定义注解类,而不是核心业务功能类
     */
    @Pointcut("@annotation(com.deepinsea.springbootaop.springaop.common.annotation.DeepinseaAOP)")
    public void pointcutOrder(){}
​
    @Around("pointcutOrder()")
    public void aroundOrder(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("下单之前--浏览商品");
        System.out.println("下单之前--咨询商品");
        try {
            proceedingJoinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("下单之后--售后");
    }
}

SpringbootAopApplicationTests.java

package com.deepinsea.springbootaop;
​
import com.deepinsea.springbootaop.springaop.service.impl.OrderServiceAnnotationImpl;
import com.deepinsea.springbootaop.springaop.service.impl.OrderServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
​
@SpringBootTest
class SpringbootAopApplicationTests {
​
    @Autowired
    private OrderServiceImpl orderService;
​
    @Autowired
    private OrderServiceAnnotationImpl orderServiceAnnotation;
​
    /**
     * 测试AOP切面功能
     */
//    @Test
//    void testAop() {
//        orderService.order();
//    }
​
    /**
     * 测试自定义注解下的切面功能(将上面的测试方法注释即可)
     */
    @Test
    void TestAnnotationAop() {
        orderServiceAnnotation.order();
    }
}

测试结果:

  .   ____          _            __ _ _
 /\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )___ | '_ | '_| | '_ / _` | \ \ \ \
 \/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |___, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.4)
​
2022-04-13 10:57:08.588  INFO 53188 --- [           main] c.d.s.SpringbootAopApplicationTests      : Starting SpringbootAopApplicationTests using Java 1.8.0_311 on aries with PID 53188 (started by deepinsea in E:\IdeaProjects\MyProjects\springboot-in-action\springboot-aop)
2022-04-13 10:57:08.588  INFO 53188 --- [           main] c.d.s.SpringbootAopApplicationTests      : No active profile set, falling back to 1 default profile: "default"
2022-04-13 10:57:09.462  INFO 53188 --- [           main] c.d.s.SpringbootAopApplicationTests      : Started SpringbootAopApplicationTests in 1.253 seconds (JVM running for 2.528)
下单之前--浏览商品
下单之前--咨询商品
下单--自定义注解
下单之后--售后
​
Process finished with exit code 0
Spring Boot AOP面向切面编程实战详解 可通过定义标记接口来建立切面契约:然后在切点中使用或这样只要目标类实现Auditable接口,就会自动被增强。Spring AOP默认采用JDK动态代理处理实现了接口的类,而对于没有接口的类则使用CGLIB代理。但在某些场景下,可能希望统一使用CGLIB代理,例如:目标类未实现任何接口;需要代理final以外的私有方法(受限于CGLIB限制);希望避免因接口变化导致代理失败。 阅读详情

相关推荐

Aveva Marine C# 二次开发入门001

1# 引用 C:\AVEVA\Marine\OH12.1.SP4\Aveva.ApplicationFramework.dll C:\AVEVA\Marine\OH12.1.SP4\Aveva.ApplicationFramework.Presentation.dll 2# 引用命名空间, using Aveva.ApplicationFramework.Presentation;usi...

weixin_30245867的博客 1911

Spring框架 -- 面向切面编程(AOP)

一、Java中代理 1. 概念   代理(Proxy) 是一种设计模式,提供了对目标对象另外的访问方式:即通过代理对象访问目标对象。这样做的好处是 可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。 2. Java 代理(三种) 静态代理 JDK动态代理(接口代理) CGLib动态代理(子类代理) 1)静态代理   静态代理概念:代理类实现与目标对象相同的接口,通过构造器或...

来日可期的博客 1370

STM32驱动HC-SR04超声波模块:两种精准测距方案详解

本文详解STM32驱动HC-SR04超声波模块的两种精准测距方案:定时器中断计数法和外部中断边沿检测法。涵盖硬件连接、代码实现与优化技巧,帮助开发者根据精度和复杂度需求选择合适方案,提升超声波测距在嵌入式项目中的稳定性和准确性。

g5h6i的博客 684

【java学习】面向切面编程(AOP,Aspect Orient Programming)

1,AOP使用场景 面向对象编程(OOP)是从静态角度考虑程序结构,面向切面编程(AOP)是从动态角度考虑程序执行过程。 经常通过 AOP 来处理一些具有横切性质的系统性服务,如事物管理、安全检查、缓存、对象池管理等,AOP 已经成为一种很经常使用的解决方式。 1)优点 不修改原代码,就可以在主干功能里面添加新功能。 2,实现 AspectJ不是Spring组成部分,是独立的AOP框架,一般把AspectJ和Spring框架一起使用进行AOP操作。 可以通过xml或者注解方式来实现,下面介绍常用注解。 1)

午后阳光 2130

Field _II 仿真 学习笔记

参考Field II的使用说明,进行了四个例子的练习。仿真程序和结果如下: PURPOSE1:Calculate the received response from all elements of a linear array and plot the responses and the summed response 【计算来自所有线性阵元的接收响应,并绘制该响应以及叠加响应。】 >>

qq_34018578的博客 1万+

Dify + Oracle + MCP:轻松构建 RAG 应用与Oracle MCP Agent

通过一个实践案例,展示如何使用 Oracle 23ai 数据库、Dify、Oracle SQLcl MCP 从零开始构建一个功能完备的 RAG 应用,以及通过 Agent 与 SQLcl MCP Server 实现 Oracle 数据访问。

Rambo zheng的博客 2501

Spring @Aspect、@Before、@After 注解实现 AOP 切面功能

Spring AOP 注解概述 1、Spring 的 AOP 功能除了在配置文件中配置一大堆的配置,比如切入点、表达式、通知等等以外,使用注解的方式更为方便快捷,特别是 Spring boot 出现以后,基本不再使用原先的 beans.xml 等配置文件了,而都推荐注解编程。 @Aspect 切面声明,标注在类、接口(包括注解类型)或枚举上。 @Pointcut 切入点声明,即切入到哪些目标类的目标方法。 value 属性指定切入点表达式,默认为 "",用于被通知注解引用

蚩尤后裔-汪茂雄 7万+

【chatgpt代码系列】磁导航纠偏算法推导

例如,可以根据AGV偏离路径的方向和距离,计算出需要进行的修正角度和移动距离,然后通过控制算法实现对AGV的纠偏。具体来说,磁导航AGV的行驶是通过磁场传感器感知地面磁场信号,然后根据信号的强度和方向来确定AGV的位置和方向,进而控制AGV的行驶。但是,在实际应用中,由于地面磁场信号的变化和干扰等因素,磁导航AGV可能会出现偏离路径的情况,导致行驶不稳定甚至出现误差。磁导航AGV纠偏公式是通过对AGV行驶中偏离路径的原因进行分析,结合磁导航AGV的工作原理和控制算法,推导出来的。假设AGV的目标方向为。

少磊的博客 2807

切面是什么?切面类是什么?面向切面编程是什么?

面向切面编程(AOP)是一种编程范式,它允许开发者将应用程序的横切关注点(cross-cutting concerns)与业务逻辑分离开来。横切关注点指的是那些影响多个模块的功能,例如日志记录、事务管理、安全性检查等。AOP 的核心思想是将这些通用功能模块化,使得业务逻辑更加清晰,并且横切关注点能够得到复用。

weixin_45791946的博客 987

第1章 环境准备

本章是 Kubernetes 高可用集群部署的前置基础环境配置,所有节点必须严格按顺序执行。主要内容包括:节点规划(IP、角色、配置要求)、VMware 克隆节点环境清理(解决 MAC 地址和主机名冲突)、统一工作目录创建、主机名与 hosts 解析配置、系统基础升级、防火墙与 swap 关闭、内核模块加载(overlay、br_netfilter、ip_vs)、网络参数优化(IP 转发、桥接过滤)、时间同步配置等。Ubuntu 24.04 内核 6.8+ 原生支持 eBPF 和 IPVS,无需手动升级内核

幸福街--linux技术分享 210

Windows 11 安装 Miniforge,配置国内源

本文详细介绍了在 Windows 11 系统中安装 Miniforge 的完整流程,包括下载、安装选项设置、配置国内镜像源(如清华大学源)以加速包管理,并启用 mamba 提升依赖解析与安装速度。同时涵盖 conda 与 mamba 的基本使用验证、PowerShell 初始化及环境提示符优化,帮助用户快速搭建高效、开源、免费的 Python 科学计算环境。

寒水馨 · 轻量级技术文档 · hanshuixin.org 4519

【Spring】面向切面编程详解(AOP)

①AOP(Aspect Oriented Program)即面向切面编程。我们先回顾以下三层架构,三层架构式垂直架构,即一层对另一层提供服务,不能越级访问,分级进行运作。上述架构是垂直分布的,而面向切面则是一个切面:②到底是什么切面呢?就是把各种类中冗余的代码提取出来,在需要用的时候就横向切入,就像一个切面一样。到底是什么冗余代码呢?事务处理、异常处理等等!突然来一大堆听不懂。③举个栗子:比如说事务控制,在我们的业务中,虽然查询是最多的,但是添加、修改、删除也不少。

未来可以改变 6720

【全网首发】2026华为OD双机位C卷 机考真题题库含考点说明以及在线OJ (Java)

如果你收到的是双机位C卷考试通知,需要针对性地调整备考策略。自2025年11月19日起,华为OD机试已正式从双机位A卷切换至。复杂度明显提升,需要组合多个算法点。题面相对简单,直接考查编程基本功。考查问题分析、算法选择和优化能力。通常为1-2个核心算法点的应用。

算法大师! 168

Spring AOP面向切面编程详解

定义:spring AOP是spring框架提供的AOP实现,基于动态代理机制.特点依赖spring容器:通过Bean容器管理切面和目标对象动态代理:使用JDK动态代理或CGLIB动态代理来实现切面的织入简单易用:与spring生态无缝集成,适合大多数spring的应用功能有限: 只能代理spring 管理的Bean ,无法代理非spring管理的对象• Spring AOP的优势(与Spring生态无缝集成、动态代理机制等)AspectJ是一个独立的AOP框架,基于字节码。

qq_62374596的博客 1063

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Spring之AOP面向切面编程详解

AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架的核心特性之一,它通过“横切”思想,将日志、事务、权限等通用功能从业务逻辑中分离,实现代码解耦与复用,掌握AOP是编写优雅Spring代码的关键。

aa_hdkf_vg的博客 1368

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

spring面向切面编程AOP详解

spring面向切面编程AOP详解 文章目录spring面向切面编程AOP详解一、什么是 AOP?1.AOP 相关概念2.AOP 的作用及其优势3.AOP 的底层实现二、基于 XML 的 AOP 开发1.快速入门三、基于注解的 AOP 开发总结 一、什么是 AOP? AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。 AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的

weixin_51405802的博客 968

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Spring 6面向切面编程aop详解

Spring 6面向切面编程aop详解

CXYCMY的博客 695

Spring AOP 全面详解:面向切面编程

架构清晰化业务代码专注于核心逻辑横切关注点集中管理功能可插拔通过配置动态启用/禁用切面灵活组合不同功能模块系统可观测性统一收集日志、指标、追踪。

no_rt_h_se_v_en的博客 1321

Spring AOP(面向切面编程)详解

面向切面编程(AOP)是一种编程技术,它允许开发者将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。横切关注点是指那些在多个模块中都会用到的功能,如日志记录、事务管理、安全检查等。通过 AOP,这些横切关注点可以被集中管理和复用,从而减少代码重复,提高代码的可维护性和可扩展性。答案。

pjx987的博客 1560

spring4(二) AOP详解-面向切面编程

spring AOP-面向切面编程 文章目录spring AOP-面向切面编程AOP简介优点AOP常用概念两种AOP实现方式一、Schema-based代码实现二、AspectJ AOP简介 AOP:中文名称面向切面编程 英文名称:( Aspect Oriented Programming ) 正常程序执行流程都是纵向执行流程 在原有纵向执行流程中添加横切面,叫面向切面编程 面向切面编程(...

孔明的博客 458

Spring面向切面编程-AOP详解

文章目录前言介绍AOP一、实现AOP1.1、全注解形式实现AOP前提准备(引入jar包)实现AOP(五种通知)二、认识JoinPont与ProceedingJoinPoint2.1、初识两个接口2.2、JoinPoint使用2.3、ProceedingJoinPoint使用(配合@Around)三、对注解进行切面进行权限校验实际业务1、对一条请求的请求接收到响应做日志处理总结参考文章参考文章 前言 本篇博客主要是介绍Spring中AOP的实际应用,若文章中出现相关问题,请指出! 所有博客文件目录索引:博客目

每个人都是独一无二的,把握好自己的节奏,跟着自己的心走。 1413

Spring详解篇之 AOP面向切面编程

一、概述 Aop(aspect oriented programming面向切面编程),是spring框架的另一个特征。AOP包括切面、连接点、通知(advice)、切入点(pointCut) 。 1.aop几个概念: 横切关注点: 对哪些方面进行拦截,拦截后怎么处理...

chuifuhuo6864的博客 147

Spring-AOP面向切面编程详解

一、AOP 定义 AOP:Aspect Oriented Programming面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时加入相关逻辑。利用AOP可以对业务逻辑 的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高 了开发的效率。 用一句话概括:在不影响原来业务类的前提下实现动态增强。 1 AOP 是基于动态代理模式。 2 AOP 是方法级别的。 3 AOP 可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是

QT2016 452

Spring——面向切面编程(AOP)详解

声明:本博客仅仅是一个初学者的学习记录、心得总结,其中肯定有许多错误,不具有参考价值,欢迎大佬指正,谢谢!想和我交流、一起学习、一起进步的朋友可以加我微信Liu__66666666 这是简单学习一遍之后的记录,后期还会修改。 一、问题引入搜小说 shupu.org ​ 在日常写项目的时候,肯定少不了要打印日志。例如,要向数据库中insert一个用户,我想在插入前输出一下相关信息,怎么...

代码小牛的博客 227
上一篇: ReentrantReadWriteLock读写锁和票据锁StempedLock
下一篇: SpringClouldAlibaba 之 Sentinel流控规则同步到nacos(并重新生成镜像)
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值