Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的一个重要特性。它提供了一种通过横向切割应用程序的方式来解耦和增强代码的能力。AOP 可以在不修改原有业务逻辑的情况下,通过将通用功能模块化并横向应用于多个不同的对象上,实现横切关注点的统一处理。
比如系统中有很多业务逻辑类,在每个类中都需要记录日志,那么使用 Spring AOP 可以让你只编写一次日志记录的代码,然后将这段代码应用到所有的业务逻辑类上,从而避免了大量的重复代码和修改代码的麻烦。
在Spring AOP中,核心概念有:
通过代理模式实现 AOP,即创建目标对象的代理对象,在代理对象中织入切面逻辑。Spring 默认采用基于代理的 AOP 实现。
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
spring-aop
:支持面向切面编程AOP,提供了 AOP的基本功能,包含定义切点,通知和切面等,在使用SpringAOP时,需要引入该依赖来使用相关的类和注解aspectjweaver
:AspectJ的运行时库,用于实现AOP的底层功能,AspectJ是一个功能强大的Java AOP框架,它扩展了Java语言,提供了更丰富的AOP功能,Spring AOP在内部使用了 AspectJ 的技术来实现 AOP。这两个依赖是 Spring AOP 的核心组成部分,通过结合使用它们,可以方便地在 Spring 应用程序中实现基于代理的 AOP。
spring-aop
提供了高级的 AOP 抽象和注解,而aspectjweaver
则提供了底层的 AOP 运行时支持。
UserService.java
package com.sin.service;
import org.springframework.stereotype.Service;
/**
* @createTime 2024/1/3 14:37
* @createAuthor SIN
* @use
*/
public interface UserService {
String getUser(String username);
void saveUser(String username, String password);
}
UserServiceImpl.java
package com.sin.service.impl;
import com.sin.service.UserService;
import org.springframework.stereotype.Service;
/**
* @createTime 2024/1/3 15:14
* @createAuthor SIN
* @use
*/
@Service
public class UserServiceImpl implements UserService {
public String getUser(String username) {
System.out.println("获取用户: " + username);
return "用户: " + username;
}
public void saveUser(String username, String password) {
System.out.println("保存用户: " + username + ", 密码: " + password);
}
}
LoggingAspect.java
package com.sin.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Bean;
/**
* @createTime 2024/1/3 14:39
* @createAuthor SIN
* @use
*/
public class LoggingAspect {
public void beforeAdvice() {
System.out.println("在方法之前执行");
}
public void afterAdvice() {
System.out.println("在方法之后执行");
}
}
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?><!-- XML声明,xml版本和编码格式 -->
<!-- spring配置文件起点 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置目标对象和切面对象 -->
<bean id="userService" class="com.sin.service.impl.UserServiceImpl"/>
<bean id="loggingAspect" class="com.sin.aspect.LoggingAspect"/>
<!--
<aop:config> 用于配置整个 AOP 代理的参数和行为
<aop:aspect> : 表示一个切面,ref属性指定了引用的切面对象
<aop:before> :前置通知,method属性指定了切面对象中的通知方法,pointcut属性指定了切点表达式
<aop:after> : 后置通知,
-->
<aop:config>
<aop:aspect ref="loggingAspect">
<!--
execution(* com.sin.service.UserService.*(..))
execution() 表示执行的方法
* 表示匹配任意返回类型
com.sin.service.UserService 表示UserService接口的完整路径
* 表示匹配任意方法名
(..) 表示任意参数列表
-->
<aop:before method="beforeAdvice" pointcut="execution(* com.sin.service.UserService.*(..))"/>
<aop:after method="afterAdvice" pointcut="execution(* com.sin.service.UserService.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
AppTest.java
package com.sin.test;
import com.sin.service.UserService;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @createTime 2024/1/3 8:41
* @createAuthor SIN
* @use
*/
public class AppTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.getUser("张三");
userService.saveUser("李四","123456");
}
}
LoggingAspect是一个切面类,它包含了beforeAdvice()和afterAdvice()两个通知方法,这些方法将在 UserService 接口的所有方法执行前后被调用。在这些方法中,我们可以实现额外的逻辑,比如记录日志、检查权限等。
UserService.java
package com.sin.service;
import org.springframework.stereotype.Service;
/**
* @createTime 2024/1/3 14:37
* @createAuthor SIN
* @use
*/
public interface UserService {
String getUser(String username);
void saveUser(String username, String password);
}
UserServiceImpl.java
package com.sin.service.impl;
import com.sin.service.UserService;
import org.springframework.stereotype.Service;
/**
* @createTime 2024/1/3 15:14
* @createAuthor SIN
* @use
*/
@Service
public class UserServiceImpl implements UserService {
public String getUser(String username) {
System.out.println("获取用户: " + username);
return "用户: " + username;
}
public void saveUser(String username, String password) {
System.out.println("保存用户: " + username + ", 密码: " + password);
}
}
LoggingAspect.java
package com.sin.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @createTime 2024/1/3 14:39
* @createAuthor SIN
* @use 切面类,用于定义要插入的日志逻辑
*/
@Aspect // 声明该类为切面类
@Component // 该类作为Spring的组件进行扫描和管理
public class LoggingAspect {
/**
* 切面方法
*/
@Around("execution(* com.sin.service.UserService.*(..))") // 定义切入点表达式,表示对com.sin.service.UserService中的所有方法进行环绕通知
public Object logMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis(); // 开始时间
Object result = joinPoint.proceed(); // 执行目标方法
long endTime = System.currentTimeMillis(); // 结束方法
long executedTime = endTime - startTime; // 执行方法时间
// 输出方法执行信息和时间
System.out.println(joinPoint.getSignature() + "执行时间"+ executedTime + "ms");
return result;
}
}
AppConfig.java
package com.sin.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
/**
* @createTime 2024/1/2 10:56
* @createAuthor SIN
* @use
*/
@Configuration
@EnableAspectJAutoProxy // 启用 AspectJ 自动代理,运行时自动创建代理对象来实现切面的功能
@ComponentScan(basePackages = "com.sin") // 扫描组件
public class AppConfig {
}
AppTest.java
package com.sin.test;
import com.sin.config.AppConfig;
import com.sin.service.UserService;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @createTime 2024/1/4 9:17
* @createAuthor SIN
* @use
*/
public class AppTest {
@Test
public void test(){
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = annotationConfigApplicationContext.getBean(UserService.class);
userService.getUser("张三");
userService.saveUser("李四","121212");
}
}