第一个Spring AOP 应用
用一个案列讲清楚Spring AOP 如何对已有功能进行拓展,做到即插即用.
Maven 加载依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--aspectjweaver是Spring AOP的底层依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
定义目标类与目标方法
定义一个 UserService 类 和 UserDao 类。
public class UserService {
private UserDao userDao;
public void createUser(){
System.out.println("执行创建用户业务逻辑");
userDao.insert();
}
public String generateRandomPassword(String type , Integer length){
System.out.println("按" + type + "方式生成"+ length + "位随机密码");
return "Zxcquei1";
}
// getter 和 setter 方法 省略
}
public class UserDao {
public void insert(){
System.out.println("新增用户数据");
}
}
新的需求
假设有这么一个需求,需要获取 UserService 运行过程中每一个方法执行开始时的时间戳,和方法传入的参数个数。
我们可以在UserService 所有方法内部多加几行代码,用于获取进入方法时的时间戳,和参数个数。但是这样做会非常麻烦,需要在每一个方法内部进行修改,以后不再需要这个功能时, 又需要将这些代码删掉。
Spring AOP 实现新需求
如果可以做这么一个插件,用来"卡"在目标方法上面,可以获取目标方法的关键信息那就好了。当我们不再需要这个功能时,直接将这个插件拆掉即可。
定义切面类
我们可以理解 切面类 就是插件,用来拓展功能的。
定义一个 MethodAspect 切面类,里面有一个 printExecutionTime 方法,其作用时获取 目标方法(也就是需要被拓展的方法) 执行的起始时间和参数信息(个数和名称)。
特别的是, MethodAspect 传入的是 JointPoint 对象, JoinPoint 类型 也就是切点, 可以理解为这个MethodAspect 切面类 切到哪个方法,就可以获取到哪个方法的信息。
public class MethodAspect {
//切面方法, 用于快速扩展额外功能
//JointPoint, 连接点, 通过连接点可以获取目标方法的信息
public void printExecutionTime(JoinPoint joinPoint){
SimpleDateFormat sdt= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSSS");
String now = sdt.format(new Date());
String className = joinPoint.getTarget().getClass().getName();//获取目标类的名称
String methodName = joinPoint.getSignature().getName();//获取目标方法的名称
System.out.println(now +" : "+className+"."+methodName);
Object[] args = joinPoint.getArgs();//获取目标方法参数
System.out.println("----> 参数个数" + args.length );
for (Object arg :args){
System.out.println("--->参数 : "+ arg);
}
}
}
配置 applicationContext.xml 文件
配置文件的抬头为 标准写法照抄即可。我们首先定义了两个 bean (userDao 和 userService)。我们还定义了一个切面类的bean。重点是下面的 <aop:config> 标签。 <aop:pointcut> 标签 作用是定义好 切面类的 pointcut 位置,其核心为 expression 属性, 也就是说清除在哪些类,哪些方法里面起作用。 <aop:aspect> 标签作用是指明Spring AOP 中用到哪个切面类,需要在目标方法运行 什么时候执行。比如下面的<aop:before> 标签说明 切面类 会在目标方法执行前去执行 切面方法。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userDao" class="indi.chester.spring.aop.dao.UserDao"/>
<bean id="userService" class="indi.chester.spring.aop.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<!-- 切面类 -->
<bean id="methodAspect" class="indi.chester.spring.aop.aspect.MethodAspect"/>
<!-- 配置 AOP -->
<aop:config>
<!-- pointcut 切点, 使用 execution 表达式描述切面的作用范围 -->
<!-- pointcut 切点表达式: pubilc 访问修饰符, * 任意返回类型, .. 包通配符, *.* 任意类的任意方法, (..) 参数通配符 -->
<!-- 作用于 indi.chester 包下所有public类的运行 -->
<aop:pointcut id="pointcut" expression="execution(public * indi.chester..*.*(..))"/>
<!-- 定义切面类 -->
<aop:aspect ref="methodAspect">
<!-- before 通知, 在目标方法运行前, 先去执行 切面类 的 切面方法-->
<aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
测试一下
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.createUser();
userService.generateRandomPassword("MD5", 16);
}
}
运行结果:
022-02-18 21:11:38 0335 : indi.chester.spring.aop.service.UserService.createUser
----> 参数个数0
执行创建用户业务逻辑
2022-02-18 21:11:38 0357 : indi.chester.spring.aop.dao.UserDao.insert
----> 参数个数0
新增用户数据
2022-02-18 21:11:38 0366 : indi.chester.spring.aop.service.UserService.generateRandomPassword
----> 参数个数2
--->参数 : MD5
--->参数 : 16
按MD5方式生成16位随机密码
可以看出定义好的切面类在 creaeUser 和 generateRandomPassword 方法调用之前执行,打印输出了参数个数和参数.
Last updated
Was this helpful?