Spring IOC
IOC是什么?
在传统的编程模式里,对象之间的依赖关系管理及对象的创建都是由开发者在代码中手动完成的。比如有一个 Car 类依赖于 Engine 类来实现启动等功能,按照常规写法,在 Car 类的代码里可能会像这样来创建 Engine 对象:
public class Car {private Engine engine = new Engine(); // 直接在Car类中创建Engine对象public void start() {engine.run();}
}
这样的代码会使 Car 类和 Engine 类紧密耦合在一起,一旦 Engine 类的构造方法、初始化逻辑等发生变化,Car 类的代码就得跟着修改,不利于代码的维护和扩展。
而 IOC(Inversion of Control)即控制反转,把原来的代码里需要实现的对象创建和对象间依赖关系转交给外部的容器(在 Spring 框架中就是 Spring IOC 容器)来负责。开发者只需要告诉容器哪些对象之间存在依赖关系,容器就会在合适的时候创建对象,并将依赖的对象注入到需要的地方。
IOC的实现方式有依赖注入和依赖查找,由于依赖查找使用的很少,因此 IOC 也叫做依赖注入。
依赖注入(DL):强调对象是被动地接收它所依赖的其他对象而不用自己主动去找,对象不是从容器中查找它依赖的类,而是在容器实例化对象时主动将它依赖的类注入给它。
public class Car {private Engine engine; // 只是定义Engine类型的成员变量,不进行实例化创建// 通过构造函数接收Engine对象,由容器注入public Car(Engine engine) {this.engine = engine;}public void start() {engine.run();}
}
此时,Engine 对象的创建和注入由 Spring IOC 容器来完成,Car 类不再关心 Engine 具体怎么创建出来的,只关注如何使用它,这就大大降低了类与类之间的耦合度,使得代码更加灵活,易于维护和测试。
IOC 容器的初始化过程?
构造方法阶段:当使用 ClassPathXmlApplicationContext 来创建 IOC 容器时(这是一种常见的从类路径下加载 XML 配置文件来初始化容器的方式),它的构造方法会执行两个重要操作:
首先,调用父容器的构造方法为当前容器设置好 Bean 资源加载器。这个资源加载器就像是一个 “搬运工”,负责从各种可能的位置(比如类路径下的配置文件、文件系统中的配置文件等)获取 Bean 相关的配置资源信息,为后续加载 Bean 定义做准备。
然后,调用父类的 setConfigLocations 方法设置 Bean 配置信息的定位路径。例如,如果你的 Bean 配置文件放在类路径下的config 目录下,名为 applicationContext.xml,那配置路径就可能是 classpath:config/applicationContext.xml,通过这个设置,容器就知道该去哪里找到描述 Bean 的配置文件了。
启动加载过程:ClassPathXmlApplicationContext 接着会调用父类 AbstractApplicationContext 的 refresh 方法,这个 refresh 方法是整个 IOC 容器启动流程的核心,它是一个模板方法,定义了一系列标准的步骤来完成容器的初始化,包括加载 Bean 定义、创建 Bean 实例、进行依赖注入等操作。而且,如果在创建新的 IOC 容器之前已经存在一个旧的容器,那么在这个阶段会先把旧容器销毁掉,确保后续基于 refresh 方法所做的初始化操作都是基于一个全新的、干净的环境,避免出现配置冲突或者旧实例干扰新配置的情况。
加载 Bean 配置资源:在容器创建好之后,会通过 loadBeanDefinitions 方法来加载 Bean 配置资源。这个方法主要做两件事:
一是调用之前设置好的资源加载器的方法去获取要加载的资源,也就是根据配置路径找到对应的配置文件。
二是真正执行加载功能,这部分是由子类 XmlBeanDefinitionReader 来具体实现的。在加载过程中,首先要解析配置文件的路径,确保能准确找到文件,然后读取文件里的内容,接着使用 XML 解析器把文件里的 Bean 配置信息转换成计算机能够理解的文档对象,最后再按照 Spring Bean 的定义规则(比如 Bean 的类名、属性、构造函数参数等如何定义)对这个文档对象进行解析,提取一个个 Bean 定义信息。
注册 Bean 信息:解析好的Bean信息会被存放在一个 hashMap集合中,在这个集合里,key 是一种字符集相关的标识(可以简单理解为由于区分不同 Bean 或者配置分组等情况),value 则是 BeanDefinition 对象,这个对象包含了 Bean 的详细定义信息,像类名、属性值、依赖关系等。在往这个 HashMap 里注册 Bean 信息的过程中,为了保证在多线程环境下数据的一致性和准确性(因为可能多个线程同时访问或者修改这个容器里的 Bean 信息),会使用 synichronized 关键字来保证线程安全。只有当配置信息里的Bean 被成功解析并且注册到 IOC 容器之后,整个 IOC 容器的初始化才算真正完成,这时容器里的 Bean 定义信息就可以被随时使用和检索了,它们是后续实现控制反转和依赖注入的基础上,就好比有了这些信息,容器才知道该怎么去创建和管理各个 Bean 实例以及它们之间的关系。
实例:假设有如下简单的 Spring XML 配置文件(applicationContext.xml),用于配置 Car 和 Engine 相关的 Bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="engine" class="com.example.Engine"><property name="type" value="V8"/></bean><bean id="car" class="com.example.Car"><constructor-arg ref="engine"/></bean>
</beans>
以下是使用 ClassPathXmlApplicationContext 加载整个配置文件并初始化 IOC 容器的代码示例:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {// 创建ClassPathXmlApplicationContext实例,传入配置文件路径ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");// 从容器中获取Car的Bean实例Car car = (Car) context.getBean("car");car.start();}
}
在这个示例中,容器初始化时,先通过构造方法设置好资源加载器和配置文件路径,然后调用 refresh 方法启动整个初始化流程,在 loadBeanDefinitions 阶段加载并解析 applicationContext.xml 文件里的 Bean 配置信息,把 Engine 和 Car 的 BeanDefinition 注册到容器中,最后可以从容器里获取到 Car 实例并调用其方法,整个过程展示了 IOC 容器的初始化以及基于此的 Bean 管理和使用。
基于注解的容器初始化
直接注册注解 Bean:Spring 允许直接将带有注解的 Bean 注册到容器中,有两种常见的时机来进行这个操作。
一种是在初始化容器的时候直接注册,比如使用 AnnotationConfigApplicationContext 创建容器时,可以将配置类(用 @Configuration 注解标记的类)作为参数传入,在这个配置类里通过 @Bean 注解来定义 Bean 。例如:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class AppConfig {@Beanpublic Engine engine() {return new Engine("V6");}@Beanpublic Car car(Engine engine) {return new Car(engine);}
}
然后就可以这样创建容器并获取 Bean:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Main {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);Car car = context.getBean(Car.class);car.start();}
}
另一种情况是在容器已经创建之后,通过BeanDefinitionRegistry 接口的实现类手动去注册 Bean 定义,不过这种方式相对复杂一些,手动注册完成后同样需要刷新容器,这样容器才能对新注册的 Bean 进行后续的实例化、依赖注入等处理操作。
扫描指定包及其子包:这是一种更便捷、常用的基于注解初始化容器的方式。通过在配置类或者启动类上使用 @ComponentScan 注解,并指定要自动扫描的路径,容器就会去这个路径下查找带有特定注解(如 @ Component、@Controller、@Sevice、@Repository 等)的类,并将它们注册为 Bean 。例如,有如下项目结构:
com.example
- controller
- UserController.java
- service
- UserService.java
- repository
- UserRepository.java
在 UserController.java 类中:
import org.springframework.stereotype.Controller;@Controller
public class UserController {// 控制器类的相关业务方法
}
在UserService.java 类中:
import org.springframework.stereotype.Service;@Service
public class UserService {// 业务服务类的相关业务方法
}
在 UserRepository 类中:
import org.springframework.stereotype.Repository;@Repository
public class UserRepository {// 数据访问类的相关业务方法
}
启动类可以这样写:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;@SpringBootApplication
@ComponentScan("com.example")
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args);}
}
在这个例子中,Spring 会自动扫描 com.example 包及其子包下的这几个类,因为它们分别带有 @Controller、@Service、@Repository 注解,所以都会被注册为 Bean,方便在整个应用中进行依赖注入和使用。
依赖注入的相关注解?
基于注解配置 bean ,也可以实现自动装配,使用的注解是:@AutoWired 或者 @Resource
@AutoWired 的规则说明:
1. 在IOC容器中查找待装配的组件的类型,如果有唯一的bean匹配 ,则使用该bean装配 和定义的属性名称无关;
2. 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找,找到就装配,找不到就抛异常;
3. @Autowired + @Qualifier 可以实现根据 name/id 来根据对应名称查找对应的 bean;
@Resource 的规则说明:
1. @Resource 有两个属性是比较重要的,分别是 name 和 type,Spring 将@Resource 注解的 name属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型;所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略;
2. 如果 @Resource 没有指定 name 和 type ,则先使用 byName 注入策略, 如果匹配不上, 再使用 byType 策略, 如果都不成功 ,就会报错;
3. Resource 中的 type 使用来限制注入 bean 的类型 默认是 Object 表示可以注入该类及其子类的 bean;
推荐使用@Resource:
1. 该注解属于jdk 不属于框架 没有强耦合性 兼容性更好;
2. Autowired 是spring框架的 只能在spring框架中使用 主要也是用于配合spring特性使用;
如何通过注解创建 Bean ?
@Conponent:这是一个基础的注解,用于将一个普通的 Java 类标记为 Spring 中的组件,让 Spring 容器能够识别并管理它。当你在一个类上添加了 @Conponent 注解后,Spring 容器在启动扫描组件的过程中,就会将这个类实例化并纳入到容器的管理范围之内,相当于在 XML 配置文件里手动写了一个 <bean> 标签来定义这个类对应的 Bean 。例如:
import org.springframework.stereotype.Component;@Component
public class MathUtils {public int add(int num1, int num2) {return num1 + num2;}
}
在上述代码中,MathUtils 类被标记为 @Component,Spring 容器会自动将它注册为一个 Bean,默认情况下,这个 Bean 的 id 就是类名首字母小写,也就是 mathUtils 。如果想自定义 id,可以使用 @Component("customId") 这样的形式来指定。
@Controller、@Service、@Repository 三个注解都是 @Component 的衍生注解,作用及属性都是一模一样,只是提供了更加明确的语义:@Controller用于表现层,@Service用于业务层,@Repository用于持久层。如果注解的中只有一个value属性要赋值时可以省略value。
如果想将第三方的类变成组件有没有源代码,也就没办法使用 @Component 进行自动配置,这种时候就要使用 @Bean 注解,被 @Bean 注解的方法返回值是一个对象,将会实例化,配置和初始化一个新对象并返回,这个对象由 Spring 的 IOC 容器管理。name属性用于给当前 @Bean 注解方法创建的对象指定一个名称,即 Bean 的 id,当使用注解配置方法时,如果方法有参数,Spring 会去容器查找是否有可用的 bean 对象,查找方式和 @Autowired 一样。
Spring AOP
AOP是什么?
AOP(Aspect-Oriented Programming)即面向切面编程,它是一种编程范式,旨在解决在软件开发过程中那些横切多个业务模块的功能实现和代码重复问题。传统的编程方式往往是将业务逻辑、日志记录、权限验证、事务管理等不同类型的代码混合在一个个业务方法中,导致代码的耦合度很高,不利于维护和扩展。
例如,在一个电商系统中,多个业务方法(如用户下单、商品查询、订单修改等)可能都需要进行日志记录,按照常规做法,就需要在每个业务方法中都添加日志记录的代码,如下所示:
public class OrderService {public void placeOrder(Order order) {// 记录下单操作开始的日志System.out.println("开始下单,订单信息:" + order);// 实际的下单业务逻辑,比如验证库存、生成订单记录等//...// 记录下单操作完成的日志System.out.println("下单操作完成");}public Order getOrderById(int id) {// 记录查询订单操作开始的日志System.out.println("开始查询订单,订单 ID:" + id);// 实际的查询订单业务逻辑,比如从数据库中查询订单信息Order order = queryOrderFromDB(id);// 记录查询订单操作完成的日志System.out.println("查询订单操作完成,查询到的订单信息:" + order);return order;}
}
可以看到,日志记录的代码分散在各个业务方法中,显得很杂乱,而且如果后期想要修改日志记录的格式或者逻辑,就需要在每个业务方法中去逐一修改,非常麻烦。
而 AOP 通过预编译和运行期动态代理的方式,将这些横切关注点(如日志记录、权限验证等)从业务代码中分离出来,统一进行维护和管理,使得业务代码更加专注于核心业务逻辑,而横切功能则可以通过配置或者注解的方式灵活地 “织入” 到业务方法的执行过程中。比如,使用 AOP 后,上述的日志记录功能可以单独提取出来进行统一配置,业务方法就只需要关注自身的核心业务逻辑,代码变得更加清晰和易于维护。
AOP 的常用场景包括但不限于权限认证(在执行某个业务方法前验证用户是否有相应权限)、自动缓存(对一些查询结果进行缓存,下次调用相同方法时直接返回缓存结果,提高性能)、错误处理(统一处理业务方法中抛出的异常)、日志(记录业务方法的执行情况)、调试(添加一些调试相关的辅助信息)以及事务(保证一组业务操作要么全部成功,要么全部失败)等。
实现 AOP 的方式
动态代理方式:这种方式是在运行期间通过拦截方法的调用,对原有的方法进行装饰,从而实现功能的增强。它主要基于两种具体的技术实现:
1. JDK 动态代理(基于接口代理):要求被代理的目标对象必须实现接口。JDK 动态代理会在运行时动态生成一个代理类,这个代理类实现了和目标对象相同的接口,并且在代理类的各个方法中可以添加额外的逻辑来实现对目标方法的增强。例如,假设有一个接口 Calculator 和它的一个实现类 CalculatorImpl:
// 定义接口
public interface Calculator {int add(int num1, int num2);int subtract(int num1, int num2);
}// 接口实现类
public class CalculatorImpl implements Calculator {@Overridepublic int add(int num1, int num2) {return num1 + num2;}@Overridepublic int subtract(int num1, int num2) {return num1 - num2;}
}
现在想要对 CalculatorImpl
类中的方法进行日志记录增强,可以使用 JDK 动态代理来实现,代码如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class CalculatorProxy implements InvocationHandler {private Object target;public CalculatorProxy(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 在方法调用前记录日志System.out.println("调用方法:" + method.getName() + ",参数:" + args);// 调用目标对象的方法Object result = method.invoke(target, args);// 在方法调用后记录日志System.out.println("方法:" + method.getName() + " 执行完毕,结果:" + result);return result;}public static Object getProxy(Object target) {return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new CalculatorProxy(target));}
}
然后可以这样使用代理:
public class Main {public static void main(String[] args) {Calculator calculator = (Calculator) CalculatorProxy.getProxy(new CalculatorImpl());int result = calculator.add(3, 5);System.out.println("最终结果:" + result);}
}
在上述代码中,CalculatorProxy 类实现了 InvocationHandler 接口,在 invoke 方法中添加了日志记录的逻辑,在调用目标方法前后分别输出相关信息。通过 Proxy.newProxyInstance 方法动态生成了 Calculator 接口的代理对象,当调用代理对象的方法时,实际上就会进入到 invoke 方法中,先执行前置的日志记录,再调用目标方法,最后执行后置的日志记录,从而实现了对目标方法的增强。
2. CGLib 基于类代理的字节码提升:与 JDK 动态代理不同,CGLib 不需要目标对象实现接口,它是通过字节码提升技术,在运行时动态生成目标类的子类,然后在子类的方法中添加增强逻辑来实现 AOP。例如,还是对上面的 CalculatorImpl 类(假设它没有实现接口)进行增强,使用 CGLib 的代码如下:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;public class CalculatorCglibProxy implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 在方法调用前记录日志System.out.println("调用方法(CGLib):" + method.getName() + ",参数:" + args);// 调用目标对象的方法(通过MethodProxy调用可以提高性能)Object result = proxy.invokeSuper(obj, args);// 在方法调用后记录日志System.out.println("方法(CGLib):" + method.getName() + " 执行完毕,结果:" + result);return result;}public static Object getProxy(Class<?> clazz) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(clazz);enhancer.setCallback(new CalculatorCglibProxy());return enhancer.create();}
}
使用方式如下:
public class Main {public static void main(String[] args) {CalculatorImpl calculator = (CalculatorImpl) CalculatorCglibProxy.getProxy(CalculatorImpl.class);int result = calculator.add(3, 5);System.out.println("最终结果:" + result);}
}
在这个例子中,CalculatorCglibProxy 类实现了 MethodInterceptor 接口,在 intercept 方法中添加了日志记录的逻辑,通过 Enhancer 类动态生成目标类的子类作为代理对象,在代理对象的方法执行时,会先执行前置日志记录,再调用目标方法(通过 MethodProxy 调用原方法),最后执行后置日志记录,实现了对目标方法的增强,而且这种方式不需要目标类实现接口,适用范围更广。
静态织入方式:引入特定的语法创建“切面”,从而使得编译器可以在编译期间织入相关“切面”的代码。不过这种方式相对比较复杂,而且灵活性不如动态代理方式,因为一旦编译完成,织入的逻辑就固定了,不像动态代理可以在运行时根据配置或者其他条件灵活改变增强逻辑。
AOP 原理
1. AOP 代理的生成与作用:
AOP 代理是 AOP 框架在运行时动态创建的对象。它的存在是为了在不修改原始目标对象代码的基础上,对目标对象的方法进行增强。例如,在一个简单的用户服务系统中,有一个UserService类,其中包含了addUser、updateUser等方法。当启用 AOP 时,AOP 框架会为UserService生成一个代理对象。这个代理对象就像是一个 “包装器”,它包裹着原始的UserService对象,并且可以代替UserService对象被其他代码使用。
AOP 代理包含了目标对象的所有方法。这意味着从外部调用者的角度来看,它和原始目标对象具有相同的方法签名和行为模式。继续以UserService为例,代理对象同样有addUser和updateUser方法,调用者可以像使用原始UserService一样调用这些方法。
然而,AOP 代理中的方法与目标对象的方法存在关键差异。在 AOP 代理方法内部,会在特定的切入点添加增强处理。比如,对于addUser方法,在执行真正的addUser业务逻辑之前,可能会先进行权限验证(这就是一种增强处理)。在完成权限验证后,代理方法会回调目标对象的addUser方法来执行原本的业务逻辑,就好像在原始业务逻辑的前后插入了额外的处理步骤。
2. 与 Spring IOC 容器的关系:
在 Spring 框架中,AOP 代理的生成和管理工作是由 Spring 的 IOC 容器负责的。IOC 容器就像是一个 “大管家”,它知道哪些对象需要进行 AOP 代理,并且会在适当的时候创建和配置这些代理。例如,当一个带有@Service注解的UserService类被扫描并注册到 IOC 容器中,并且存在针对UserService的切面配置时,IOC 容器会自动创建UserService的 AOP 代理。
AOP 代理的依赖关系也由 IOC 容器来管理。这使得 AOP 代理可以方便地利用 IOC 容器的依赖注入功能。例如,假设切面类需要记录日志,而日志记录器(Logger)是一个在 IOC 容器中管理的 Bean。AOP 代理可以通过 IOC 容器的依赖注入获取Logger对象,用于在切面方法中记录日志信息。而且,AOP 代理可以直接使用容器中的其他 Bean 实例作为目标对象进行增强处理。比如,在一个电商系统中,ProductService(产品服务)是一个在 IOC 容器中的 Bean,而我们有一个切面用于统计ProductService方法的执行时间。IOC 容器会将ProductService注入到 AOP 代理中作为目标对象,AOP 代理在方法执行前后记录时间差,从而实现对ProductService方法执行时间的统计。
AOP 相关注解
@Aspect:@Aspect 是用于声明一个类是切面 Bean 的注解。一个切面是一个包含了多个切点和通知的模块,用于实现横切关注点的功能。当 Spring 扫描到带有 @Aspect 注解的类时,会将其识别为切面类,并会处理其中定义的切点和通知逻辑。
示例:假设我们要创建一个日志记录切面,用于记录服务层方法的调用信息。首先创建一个切面类:
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {// 这里可以定义切点和通知方法
}
在这个例子中,@Aspect 注解告诉 Spring 这个 LoggingAspect 类是一个切面类,@Component注解则将这个切面类注册到 Spring 容器中,使得 Spring 能够对其进行管理和处理。
@Before:@Before 是前置通知注解。它定义的方法会在目标方法执行之前被调用。这个通知通常用于在执行目标业务逻辑之前进行一些准备工作,比如权限验证、参数检查、资源初始化等。
示例:在上述 LoggingAspect 类中添加一个前置通知方法,用于记录方法开始执行的日志:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;@Aspect
@Component
public class LoggingAspect {private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);@Before("execution(* com.example.service.*.*(..))")public void beforeMethodExecution() {logger.info("方法即将执行");}
}
在这个例子中,@Before 注解的切点表达式 execution(* com.example.service.*.*(..)) 表示匹配 com.example.service 包及其子包下的所有类的所有方法。当这些方法被调用时, beforeMethodExecution 方法会先被执行,记录一条日志信息,表示方法即将执行。
@After:@After 是后置通知注解。它定义的方法会在目标方法退出时执行,无论目标方法是正常返回还是因为抛出异常而退出。这个通知常用于释放资源、清理临时数据等操作。
示例:在 LoggingAspect 类中添加一个后置通知方法,用于记录方法执行完成的日志:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;@Aspect
@Component
public class LoggingAspect {private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);@After("execution(* com.example.service.*.*(..))")public void afterMethodExecution() {logger.info("方法执行完成");}
}
当 com.example.service 包及其子包下的方法执行完成(无论是正常返回还是抛出异常)后, afterMethodExecution 方法都会被执行,记录方法执行完成的日志。
@AfterReturning:@AfterReturning 是返回后通知注解。它定义的方法会在目标方法正常完成(没有抛出异常)之后执行,并且可以通过 returning 属性接收目标方法的返回值。这个通知常用于对返回结果进行处理、记录返回值等操作。
示例:在 LoggingAspect 类中添加一个返回后通知方法,用于记录方法的返回值:
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;@Aspect
@Component
public class LoggingAspect {private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {}@AfterReturning(pointcut = "serviceMethods()", returning = "result")public void afterReturningMethodExecution(Object result) {logger.info("方法正常返回,返回值为: {}", result);}
}
这里通过 @Pointcut 定义了一个切点,然后在 @AfterReturning 注解中引用了这个切点。当目标方法正常返回后,afterReturningMethodExecution 方法会被执行,并且可以通过 result 参数获取目标方法的返回值,记录返回值的日志信息。
@AfterThrowing:@AfterThrowing 是异常通知注解。它定义的方法会在目标方法抛出异常导致退出时执行,并且可以通过 throwing 属性接收抛出的异常对象。这个通知常用于对异常进行处理、记录异常信息等操作。它和 @AfterReturning 注解在逻辑上是互斥的,对于一个目标方法的执行,只会执行 @AfterReturning 或者 @AfterThrowing 中的一个。
示例:在 LoggingAspect 类中添加一个异常通知方法,用于记录方法抛出异常的信息:
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;@Aspect
@Component
public class LoggingAspect {private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {}@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")public void afterThrowingMethodExecution(Exception ex) {logger.error("方法抛出异常: {}", ex.getMessage());}
}
当 com.example.service 包及其子包下的方法抛出异常时,afterThrowingMethodExecution 方法会被执行,并且可以通过 ex 参数获取抛出的异常对象,记录异常信息的日志。