前言
Spring AOP是一种强大的面向切面编程工具,它能够帮助我们更好地处理与核心业务逻辑无关的横切关注点。通过使用注解和配置类的方式,我们可以更加简洁和灵活地实现AOP。
本文将以一个示例来介绍如何结合注解和配置类来使用Spring AOP。通过这个示例,你将了解如何使用注解来定义切点、通知和切面,并通过配置类来启用和配置AOP。
前面三章我们都是通过 xml 文件去配置,那么这章我们开始使用 Java 配置类加注解来完成案例。
注意:使用 xml 是为了让大家了解 xml 怎么去配置,现在常用的都是注解加 Java 配置类来实现。
在开始之前,请确保你已经具备了Spring框架的基础知识,并且已经正确配置了Spring环境。
接下来,我们将一步步地介绍这个示例,让我们开始吧!
一、开始学习
1、新建项目,结构如下
2、添加 spring 依赖
<!-- spring 的核心依赖 -->
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8</version>
</dependency>
</dependencies>
3、在 service 包下新建一个 UserService 类(被代理的目标类)
@Slf4j
@Service
public class UserService {
/**
* 目标方法,也就是需要被增强的方法
* 因此它就是一个连接点
* @param name
*/
public String add(String name){
log.info("添加用户..." + name);
return "success";
}
}
4、在 aspect 包下新建一个 UserAspect 切面类
@Slf4j
// 将切面纳入容器管理
@Component
/**
* 使用 @Aspect 注解标识当前类为一个切面
*/
@Aspect
public class UserAspect {
@Pointcut("execution(* edu.nf.ch21.service.UserService.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before(JoinPoint jp) {
log.info("前置通知");
}
/**
* 后置通知
*
* @param jp
* @param returnVal
*/
@AfterReturning(value = "pointcut()", returning = "returnVal")
public void afterReturning(JoinPoint jp, Object returnVal) {
log.info("后置通知,目标方法返回值:" + returnVal);
}
/**
* 环绕通知
*
* @param jp
* @return
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint jp) throws Throwable {
log.info("环绕通知前,方法参数:" + jp.getArgs()[0]);
Object returnVal = jp.proceed();
log.info("环绕通知后,方法返回值:" + returnVal);
return returnVal;
}
/**
* 异常通知
*
* @param jp
* @param e
*/
@AfterThrowing(value = "pointcut()", throwing = "e")
public void afterThrowing(JoinPoint jp, Exception e) {
log.info("异常通知:" + e.getMessage());
}
/**
* 最终通知
* @param jp
*/
@After("pointcut()")
public void after(JoinPoint jp){
log.info("最终通知");
}
}
这个切面类定义了一个pointcut()
切点方法,用于指定哪些方法需要被拦截。这里使用了execution()
表达式来匹配edu.nf.ch21.service.UserService
类中的所有方法。
接下来,针对这个切点方法,定义了五个通知方法:
before()
方法是一个前置通知,在目标方法执行之前执行,并且拦截到JoinPoint
参数,可以获取目标方法的信息。afterReturning()
方法是一个后置通知,在目标方法正常返回时执行,拦截到JoinPoint
和目标方法的返回值returnVal
参数。around()
方法是一个环绕通知,可以在目标方法执行前、执行后都做一些处理,并拦截到ProceedingJoinPoint
参数,可以手动调用目标方法并获取返回值。afterThrowing()
方法是一个异常通知,在目标方法抛出异常时执行,拦截到JoinPoint
和Exception
参数。after()
方法是一个最终通知,在目标方法执行结束后(包括正常结束和异常结束)执行,拦截到JoinPoint
参数。
其中还使用了很多的注解,它们分别是什么意思?
@Component
: 这是Spring框架的注解之一,用于将类标识为一个可被Spring容器扫描和管理的组件。在这个示例中,@Component
注解表示将UserAspect
类作为一个切面组件纳入到Spring容器中。
@Aspect
: 这是AspectJ框架的注解,用于标识一个类为切面。切面是一个包含了各种通知和切点的类,用于对其他类中的方法进行拦截和增强。
@Pointcut
: 这是一个自定义的方法级别注解,用于定义一个切点。切点决定了哪些方法会被拦截和应用切面逻辑。在这个示例中,pointcut()
方法使用了@Pointcut
注解来定义切点。
@Before
: 这是一个前置通知注解,用于在目标方法执行之前执行某段逻辑。在这个示例中,before()
方法使用了@Before
注解,表示在匹配到的方法执行之前会执行before()
方法中的逻辑。
@AfterReturning
: 这是一个后置返回通知注解,用于在目标方法正常返回时执行某段逻辑。在这个示例中,afterReturning()
方法使用了@AfterReturning
注解,并通过value
属性指定了切点,returning
属性指定了目标方法返回值的参数名,表示在匹配到的方法正常返回时会执行afterReturning()
方法中的逻辑。
@Around
: 这是一个环绕通知注解,用于在目标方法执行前和执行后执行某段逻辑。在这个示例中,around()
方法使用了@Around
注解,并通过value
属性指定了切点,表示在匹配到的方法执行前后会执行around()
方法中的逻辑。注意,around()
方法的参数类型为ProceedingJoinPoint
,可以手动调用目标方法。
@AfterThrowing
: 这是一个异常通知注解,用于在目标方法抛出异常时执行某段逻辑。在这个示例中,afterThrowing()
方法使用了@AfterThrowing
注解,并通过value
属性指定了切点,throwing
属性指定了异常的参数名,表示在匹配到的方法抛出异常时会执行afterThrowing()
方法中的逻辑。
@After
: 这是一个最终通知注解,用于在目标方法执行结束后执行某段逻辑,无论是否抛出异常。在这个示例中,after()
方法使用了@After
注解,并通过value
属性指定了切点,表示在匹配到的方法执行结束后会执行after()
方法中的逻辑。
总结起来,这些注解一起使用可以实现对目标方法的拦截和增强,例如在方法执行前后打印日志、记录返回值、处理异常等。
5、在 config 包下新建一个 AppConfig 配置类
@Configuration
@ComponentScan(basePackages = "edu.nf.ch21")
// 启用 AspectJ 注解处理器
// proxyTargetClass 指定为 true 表示强制使用 cglib 生成代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}
这段代码是一个Spring配置类,用于配置Spring容器和启用AspectJ注解处理器。下面是对每个注解的解释:
@Configuration
: 这是一个Spring框架的注解,用于表示该类是一个配置类。配置类通常用于定义Bean的创建和配置,以及其他的Spring配置。
@ComponentScan
: 这是一个Spring框架的注解,用于指定要扫描的包或类的路径。在这个示例中,basePackages
属性指定了要扫描的基础包路径为"edu.nf.ch21",意味着Spring容器会扫描并加载该包及其子包中的组件。
@EnableAspectJAutoProxy
: 这是一个Spring框架的注解,用于启用AspectJ注解处理器,并开启基于AspectJ的自动代理功能。proxyTargetClass = true
表示使用cglib生成代理对象而不是默认的基于接口的JDK动态代理方式。
这些注解的组合作用是将该配置类纳入Spring容器的管理,并启用AspectJ注解处理器和自动代理功能,从而实现AOP(面向切面编程)的特性。
6、测试
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService bean = context.getBean(UserService.class);
bean.add("qiu");
}
}
运行结果
使用注解开发,效率是不是很高,没错,在实际开发中注解和 Java 配置类确实是很开。也是开发中最常用的一种,当然 xml 也可以,只不过是使用 Java 配置类加注解开发会比较好看,阅读性好。大家根据自己的选择使用,推荐使用的是这种方法。
二、使用Spring AOP 注解结合配置类和使用 xml 有什么区别
使用Spring AOP注解和配置类与使用XML方式定义AOP有以下区别:
配置方式:使用注解的方式是通过在代码中添加注解来定义切面、切点和通知等,而使用XML方式是通过编写XML配置文件来定义AOP元素。
代码可读性:使用注解方式可以直接在Java代码中看到AOP相关的配置,使得代码更加紧凑和可读。而使用XML方式需要打开独立的XML配置文件,可能会增加代码的复杂性。
配置灵活性:使用注解方式可以更灵活地进行配置,可以直接将切面和通知应用于具体的类或方法,也可以使用通配符来匹配一组类或方法。而使用XML方式则需要在配置文件中明确指定切入点和通知的目标。
IDE支持:使用注解方式可以充分利用现代IDE的代码提示和自动补全功能,提升开发效率。而使用XML方式则需要手动编写XML配置文件,可能会增加书写错误的风险。
集成和维护:使用注解方式可以更加方便地进行代码集成和版本控制,同时也更容易进行维护和重构。而使用XML方式则可能导致配置文件随着系统变得越来越庞大和难以管理。
总的来说,注解方式相对于XML方式更加简洁、灵活和方便于代码维护,但对于复杂的AOP配置或需要与第三方库进行集成的情况,XML方式可能更为适用。选择使用哪种方式还取决于个人或团队的偏好和具体的项目需求。
三、gitee 案例
案例完整地址:https://gitee.com/qiu-feng1/spring-framework.git