Spring 提供了功能强大IOC、AOP等功能,前面博主对于 Spring IoC 的内容进行了整理,给出了个人的理解,并模拟实现了 Spring IoC(完成至发现Bean注解的循环依赖),具体内容请见 HB个人博客:Spring之IOC控制反转、DI依赖注入介绍和使用(详解)、模拟实现 IoC
在软件开发过程中,我们通常需要在多个模块或功能中重复执行相同的一段代码。这些重复的代码片段通常是关注点,例如日志记录、安全检查、事务处理等。这种现象会导致代码的重复,影响项目的可维护性和可读性。为了解决这个问题,AOP(面向切面编程)应运而生。博主将详细介绍个人对 Spring AOP 相关概念、使用方法和实现原理的理解。
在软件业,AOP为(Aspect Oriented Programming)的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
——摘自百度百科AOP 面向切面编程:https://baike.baidu.com/item/AOP/1332219?fromModule=lemma_inlink
AOP(Aspect Oriented Programming,面向切面编程)是一种编程思想,它的主要目的是将通用功能从业务逻辑中抽离出来,以达到代码复用和模块化的目的。
日志记录:可以将日志记录代码从业务逻辑代码中分离出来,使得业务逻辑更加清晰简洁。
安全控制:实现安全控制策略。
性能监控:实现应用程序性能监控。
异常处理:实现异常处理策略。
事务管理:实现事务管理策略。
缓存管理:实现缓存管理策略。
JoinPoint:表示程序执行的连接点,可以获取被拦截方法的参数和返回值等信息。
ProceedingJoinPoint:是 JoinPoint 的一个子接口,它提供了proceed()
方法,用于调用被拦截的方法。
Aspect:是切面的抽象,它由切点和通知组成。
Pointcut:切点是用于定义拦截规则的表达式,它可以匹配到程序执行的连接点。
Advice:通知是在切点拦截到的连接点上要执行的逻辑,包括Before、After、Around等类型。
Spring 中支持五种类型Advice;
通知类型 | 连接点 | 实现接口 |
---|---|---|
前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
后置通知 | 方法后 | org.springframework.aop.MethodBeforeAdvice |
环绕通知 | 方法前后 | org.aopalliance.intercept.MethodInterceptor |
异常抛出通知 | 方法抛出异常 | org.springframework.aop.ThrowsAdvice |
引介通知 | 类中增加新的方法 | org.springframework.aop.IntroductionInterceptor |
即 AOP 在不改变原有代码的情况下,去增加新的功能。
AopProxy:是 AOP 代理的接口,定义了获取代理对象的方法。
ProxyFactory:是 Spring AOP 用于创建和管理代理对象的工厂。
TargetSource:是目标对象的接口,定义了获取目标对象的方法。
Interceptor:是拦截器的接口,用于拦截方法调用并执行通知逻辑。
Spring AOP主要有两种实现方式:基于XML的配置和基于注解(@AspectJ)的配置。
首先在父 module 中导入依赖:
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.3.20version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
dependencies>
在子 module:aop 的 pom.xml 导入依赖:
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.6version>
<scope>runtimescope>
dependency>
基于 XML 的配置需要在 Spring 的配置文件中定义切面、通知和切入点。下面是一个基于 XML 的 Spring AOP 配置示例:
<bean id="userService" class="com.example.UserService"/>
<bean id="logAspect" class="com.example.LogAspect"/>
<aop:config>
<aop:aspect id="aspect" ref="logAspect">
<aop:pointcut id="pointcut" expression="execution(* com.example.UserService.*(..))"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
aop:aspect>
aop:config>
基于注解的配置需要在切面类上使用@Aspect注解,并在Spring配置文件中开启自动代理。下面是一个基于注解的Spring AOP配置示例:
@Aspect
public class LogAspect {
@Before("execution(* com.example.UserService.*(..))")
public void before() {
System.out.println("前置通知:方法执行前");
}
@After("execution(* com.example.UserService.*(..))")
public void after() {
System.out.println("后置通知:方法执行后");
}
}
在Spring配置文件中开启自动代理:
<aop:aspectj-autoproxy/>
<bean id="userService" class="com.example.UserService"/>
<bean id="logAspect" class="com.example.LogAspect"/>
下面我们以一个简单的用户管理应用为例,演示如何使用Spring AOP实现日志记录功能。
核心:主要是使用 Spring 的 API 接口实现
创建接口 IUserService
public interface IUserService {
public void add();
public void delete();
public void update();
public void select();
}
创建目标类 UserService
public class UserService implements IUserService{
@Override
public void add() {
System.out.println("增加");
}
@Override
public void delete() {
System.out.println("删除");
}
@Override
public void update() {
System.out.println("修改");
}
@Override
public void select() {
System.out.println("查询");
}
}
创建日志类 log
public class Log implements MethodBeforeAdvice, AfterReturningAdvice {
/**
* method:要执行的目标对象的方法
* args:参数
* target:目标对象
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行" + target.getClass().getName() + "的" + method.getName());
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + "方法,返回值为" + returnValue);
}
}
创建 applicationContext.xml
配置文件
<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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean id="userService" class="com.hb.service.UserService"/>
<bean id="log" class="com.hb.log.Log"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.hb.service.UserService.*(..))"/>
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
aop:config>
beans>
测试以及测试结果
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService) context.getBean("userService");
userService.add();
userService.delete();
userService.update();
userService.select();
}
}
结果:
核心:主要是切面定义
创建一个自定义类:
public class Custom {
public void before() {
System.out.println("===执行方法前===");
}
public void after() {
System.out.println("===执行方法后===");
}
}
创建 applicationContext.xml
配置文件:
<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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean id="custom" class="com.hb.custom.Custom"/>
<aop:config>
<aop:aspect ref="custom">
<aop:pointcut id="point" expression="execution(* com.hb.service.UserService.*(..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
aop:aspect>
aop:config>
beans>
测试以及测试结果:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService) context.getBean("userService");
userService.add();
userService.delete();
}
}
结果:
核心:使用注解实现
创建目标类:
//使用注解方式实现AOP
@Aspect //标注这个类是一个切面
public class PointCut {
@Before("execution(* com.hb.service.UserService.*(..))")
public void before() {
System.out.println("===执行方法前===");
}
@After("execution(* com.hb.service.UserService.*(..))")
public void after() {
System.out.println("===执行方法后===");
}
//在环绕通知中,可以给一个参数,代表要获取处理切入的点
@Around("execution(* com.hb.service.UserService.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("===环绕前===");
Object proceed = joinPoint.proceed(); //执行方法
System.out.println("===环绕后===");
}
}
创建 applicationContext.xml
配置文件:
<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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
<bean id="userService" class="com.hb.service.UserService"/>
<bean id="log" class="com.hb.log.Log"/>
<bean id="pointCut" class="com.hb.byAnnotation.PointCut"/>
beans>
测试以及测试结果:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService) context.getBean("userService");
userService.delete();
}
}
运行测试类,结果如下:
具体实现过程请见 HB个人博客:模拟实现 Spring AOP