【面试题】Spring篇

内容来源于网络,如有侵权请联系删除

Spring Bean 一共有几种作用域?

一共有六种作用域:

  • singleton:默认是单例,含义不用解释了吧,一个 IOC 容器内部仅此一个
  • prototype:原型,多实例
  • request:每个请求都会新建一个属于自己的 Bean 实例,这种作用域仅存在 Spring Web 应用中
  • session:一个 http session 中有一个 bean 的实例,这种作用域仅存在 Spring Web 应用中
  • application:整个 ServletContext 生命周期里,只有一个 bean,这种作用域仅存在 Spring Web 应用中
  • websocket:一个 WebSocket 生命周期内一个 bean 实例,这种作用域仅存在 Spring Web 应用中

别背网上那些多年前的答案了,官网最新截图:

img

@Qualifier 注解有什么作用 ?

@Qualifier 注解在 Spring 中的主要作用是用于在依赖注入时消除歧义。当一个类型有多个实现时,@Qualifier 注解可以指定需要注入哪一个具体的 Bean。

例如以下代码,当 Service 有多个实现类的时候,可以通过 @Qualifier 指定名称选择对应的实现 Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class Client {

private final Service service;

@Autowired
public Client(@Qualifier("serviceImpl1") Service service) {
this.service = service;
}

public void doSomething() {
service.serve();
}
}

扩展 @Primary

@Primary 注解用于指定当有多个候选 Bean 时默认注入哪个 Bean,也就是指定了第一顺位。

当结合 @Qualifier 使用时,可以覆盖 @Primary 的默认行为,例如以下这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Component
@Primary
public class DefaultService implements Service {
public void serve() {
System.out.println("Default Service");
}
}

@Component
@Qualifier("specificService")
public class SpecificService implements Service {
public void serve() {
System.out.println("Specific Service");
}
}

@Component
public class Client {

private final Service service;

@Autowired
public Client(@Qualifier("") Service service) {
this.service = service;
}

public void doSomething() {
service.serve();
}
}

即使 DefaultService 被标记为 @Primary,但由于 @Qualifier("specificService"),所以最终注入的仍然是 SpecificService!

@Bean和@Component有什么区别?

@Bean@Component 都是用于定义 Spring 容器中的 Bean 的注解,但它们的使用场景和方式有所不同:

  • @Bean 注解通常用于 Java 配置类的方法上,以声明一个 Bean 并将其添加到 Spring 容器中,用于显示声明
  • @Component 注解用于类级别,将该类标记为 Spring 容器中的一个组件,自动检测并注册为 Bean(需要扫对应的包),用于自动扫描和注入
特性 @Bean @Component
使用位置 方法级别(在 @Configuration 类中) 类级别
扫描机制 不支持自动扫描,需手动注册 支持自动扫描,通过 @ComponentScan 自动发现
主要用途 用于配置第三方库或复杂对象 用于自动发现并注册自定义类
常见场景 手动配置复杂对象、第三方库类 自定义服务、DAO 层、控制器等类的自动注册
灵活性 更灵活,适合复杂初始化 自动化更强,适合类的简单注册

@Bean 的使用场景

@Bean 注解用于显式声明 Spring 容器管理的 Bean,通常用于以下场景:

  • 手动创建复杂的对象:需要进行复杂的初始化过程,或者需要传递参数给构造函数的对象。
  • 第三方库或无法修改的类:如果某个类不是由自己开发,且无法添加 Spring 注解时,可以通过 @Bean 来手动注册。
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class AppConfig {

@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("user");
dataSource.setPassword("password");
return dataSource;
}
}

在上述例子中,@Bean 注解用于创建和配置第三方类 DataSource,将其注册为 Spring 容器中的 Bean。

@Component 的使用场景

@Component 注解用于类级别的自动扫描与注入,Spring 会自动发现和管理这些类。它是 Spring 中实现自动扫描 Bean 的基础。

@Component 是一个通用的注解,还有一些特定用途的衍生注解:

  • **@Service**:用于标识服务层的类。
  • **@Repository**:用于标识数据访问层的类(DAO 层)。
  • **@Controller**:用于标识控制器类,通常用于 Spring MVC 中处理 HTTP 请求。
1
2
3
4
5
6
@Component
public class UserService {
public void createUser(String name) {
System.out.println("Creating user: " + name);
}
}

在这个例子中,UserService 类通过 @Component 注解被 Spring 扫描并注册为容器中的 Bean。

@Component、@Controller、@Repository和@Service 的区别?

它们本质上没区别,并且其它三个都是 @Component 的衍生注解,之所以做了这些划分主要是为了更好地组织和管理应用的各个层次,提高代码的可读性和可维护性。

1)**@Component**:这是一个通用的注解,用于将任意类注册为 Spring 容器中的 Bean。它没有特定的语义,适用于任何需要 Spring 管理的类。

2)**@Controller**:这是一个专门用于 Spring MVC 中的控制器(Controller)层的注解。用于处理 HTTP 请求,并将结果返回给客户端。

3)**@Service**:用于标识业务逻辑层的类。它具有明确的语义,表明该类承担业务操作,主要用于编写服务类。

4)**@Repository**:用于数据访问层(DAO)的类,与数据库交互。

区别总结:

  • **@Component**:通用注解,用于将任何类标记为 Spring Bean。
  • **@Controller**:特定于 Spring MVC,处理 Web 层请求。
  • **@Service**:特定于业务逻辑层,用于处理服务逻辑。
  • **@Repository**:特定于持久层,通常用于数据访问对象(DAO)。

@Component 的使用场景

@Component 是一个通用的 Spring 注解,表示该类是一个 Spring Bean,它适用于任何需要注入到 Spring 容器中的类。@Component 是其他注解的基础,@Controller@Service@Repository 都是它的特定用法。

示例

1
2
3
4
5
6
@Component
public class GeneralComponent {
public void doSomething() {
System.out.println("Doing something...");
}
}

使用场景

  • 用于没有明确职责的类或通用组件,比如工具类、任务调度器等。

@Controller 的使用场景

@Controller 是 Spring MVC 专用的注解,主要用于标识控制器层的类。它将 HTTP 请求映射到对应的处理方法,返回视图名称或响应数据。

示例

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/user")
public class UserController {

@GetMapping("/profile")
public String getUserProfile(Model model) {
// 添加数据到模型
model.addAttribute("user", new User("John", "Doe"));
return "userProfile"; // 返回视图名称
}
}

使用场景

  • 处理 HTTP 请求、返回视图或 JSON 响应。通常与前端交互,属于 MVC 模式中的控制器层。

@Service 的使用场景

@Service 表示业务逻辑层的类,通常包含业务操作和服务逻辑。虽然它在功能上与 @Component 类似,但 @Service 表明该类具有特定的业务功能,适合放置业务处理代码。

示例

1
2
3
4
5
6
7
8
@Service
public class UserService {

public void createUser(String name) {
// 执行业务逻辑
System.out.println("Creating user: " + name);
}
}

使用场景

  • 主要用于封装业务逻辑或服务操作,处理复杂的业务功能,通常与 DAO 层和控制器层交互。

@Repository 的使用场景

@Repository 用于标识数据访问层的类,通常与数据库或其他持久化存储系统交互。Spring 还提供了自动将数据库异常转换为 Spring 数据访问异常的功能。

示例

1
2
3
4
5
6
7
8
@Repository
public class UserRepository {

public User findUserById(Long id) {
// 从数据库中查询用户
return new User("John", "Doe");
}
}

使用场景

  • 主要用于数据访问层,与数据库交互,通常封装 CRUD 操作。@Repository 注解还具有异常转换功能,能够将数据库异常转换为 Spring 的 DataAccessException

@Repository 的异常处理

@Repository 提供了一项特殊功能,即异常转换。当与数据库交互时,@Repository 注解会自动将低层次的数据库异常(如 SQLException)转换为 Spring 统一的 DataAccessException,从而简化了异常处理流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Repository
public class UserRepository {

public User findById(Long id) {
try {
// 执行数据库查询
} catch (SQLException e) {
// Spring 会将其转换为 DataAccessException
throw new DataAccessException("Database error");
}
return new User();
}
}

Spring中的@Primary注解的作用是什么?

在 Spring 框架中,@Primary 注解用于标记当存在多个候选 Bean 时,应该优先注入哪一个。

它的主要作用是解决 Bean 注入时的歧义问题,当一个接口或父类有多个实现时,Spring 无法确定该注入哪个具体的 Bean,此时可以使用 @Primary 来指明首选的 Bean。

使用场景

1)多实现类:当一个接口有多个实现类,并且在注入时未明确指定要注入哪个实现类时,Spring 会报错。这时可以使用 @Primary 注解来指定一个首选的实现类。

2)注入优先级:在某些情况下,虽然存在多个候选 Bean,但我们希望某个特定的 Bean 在没有明确指定时被优先注入,这时可以使用 @Primary 注解。

代码举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

public interface MyService {
void performAction();
}

@Component
@Primary
public class MyServiceImpl1 implements MyService {
@Override
public void performAction() {
System.out.println("面试鸭表演1");
}
}

@Component
public class MyServiceImpl2 implements MyService {
@Override
public void performAction() {
System.out.println("面试鸭表演2");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyComponent {
private final MyService myService;

@Autowired
public MyComponent(MyService myService) {
this.myService = myService;
}

public void doSomething() {
myService.performAction();
}
}

使用 @Primary 注解后,此时会优先注入 MyServiceImpl1。

Spring中的@Value注解的作用是什么?

在 Spring 框架中,@Value 注解用于注入外部化的配置值到 Spring 管理的 Bean 中。通过 @Value 注解,可以将属性文件、环境变量、系统属性等外部资源中的值注入到 Spring Bean 的字段、方法参数或构造函数参数中。

使用场景

1)配置文件注入:将属性文件中的值注入到 Bean 中。

2)系统属性和环境变量:将系统属性或环境变量的值注入到 Bean 中。

3)默认值设置:在属性不可用时,提供默认值。

举例说明

Properties 文件

1
2
app.name=MyApp
app.version=1.0.0

@Value 注解注入属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppConfig {

@Value("${app.name}")
private String appName;

@Value("${app.version}")
private String appVersion;

public String getAppName() {
return appName;
}

public String getAppVersion() {
return appVersion;
}
}

Spring中的 @ModelAttribute 注解的作用是什么?

@ModelAttribute 是 Spring MVC 中的一个注解,用于将请求的参数或者表单数据绑定到控制器处理方法的参数上,或者将其添加到模型(Model)中,以便在视图(View)中展示。

作用:

1)用于方法参数@ModelAttribute 注解可以用于控制器方法的参数上,表示将请求中的参数绑定到某个对象上,并将其添加到模型(Model)中,便于在视图层访问。

2)用于方法级别@ModelAttribute 注解也可以用在控制器方法上,表示在执行任何控制器方法之前,先运行带有该注解的方法,用来为视图模型添加预处理的数据。

odelAttribute 用于参数绑定

@ModelAttribute 用在控制器方法的参数上时,Spring 会自动从 HTTP 请求中提取参数,并将其绑定到指定的 Java 对象中。该注解主要用于将请求参数映射到复杂的对象(例如实体类)。

使用场景:

  • 处理表单提交时,将多个请求参数绑定到一个对象。
  • 在 RESTful 风格的控制器中,将请求参数转换为业务对象。

示例

1
2
3
4
5
6
@PostMapping("/register")
public String registerUser(@ModelAttribute User user) {
// Spring 会自动将请求参数绑定到 User 对象上
System.out.println("Mianshiya User Name: " + user.getName());
return "registrationSuccess";
}

请求示例

1
2
3
4
5
<form action="/register" method="POST">
<input type="text" name="name" />
<input type="text" name="email" />
<button type="submit">Register</button>
</form>

在这个例子中,Spring 会自动将请求中的 nameemail 参数绑定到 User 对象的相应字段上,并传递给 registerUser() 方法。

工作原理:

  • Spring 首先创建 User 对象的实例,然后根据请求参数的名称,将对应的值填充到 User 对象的属性中。
  • 如果请求参数与对象属性不匹配,Spring 将忽略未匹配的参数,或者抛出异常(如果字段是强制必需的)。

@ModelAttribute 用于模型预处理

@ModelAttribute 注解用在方法级别时,它会在每个控制器方法执行之前先执行该方法。这种方式常用于准备通用的模型数据(如下拉列表、表单数据等),以便在视图中共享这些数据。

使用场景:

  • 在视图中共享某些通用数据(如用户信息、选项列表等)。
  • 为表单准备预处理的数据,例如表单的初始值或默认选项。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
public class UserController {

@ModelAttribute("roles")
public List<String> populateRoles() {
// 模拟从数据库中获取角色数据
return Arrays.asList("Admin", "User", "Guest");
}

@GetMapping("/showForm")
public String showForm(Model model) {
model.addAttribute("user", new User());
return "userForm";
}
}

在这个例子中,populateRoles() 方法会在每个控制器方法之前执行,且返回的角色列表会自动添加到模型中,供视图使用。通过 @ModelAttribute("roles"),视图层可以访问该数据。

工作原理:

  • @ModelAttribute 方法在执行控制器的任何其他方法之前运行,Spring 将该方法返回的数据添加到 Model 中,使得这些数据可以在视图中使用。
  • 返回的数据可以通过视图模板引擎(如 Thymeleaf、JSP 等)直接访问。

@ModelAttribute@RequestParam 的区别

虽然 @ModelAttribute@RequestParam 都用于从请求中提取数据,但它们的用途有所不同:

  • **@ModelAttribute**:用于绑定复杂对象,将请求参数映射到 Java 对象中。适用于处理表单提交和对象映射。
  • **@RequestParam**:用于绑定简单类型的请求参数(如 Stringint 等),适合处理单个请求参数。

示例

1
2
3
4
5
6
7
8
9
10
11
// 使用 @ModelAttribute 绑定复杂对象
@PostMapping("/register")
public String registerUser(@ModelAttribute User user) {
return "registrationSuccess";
}

// 使用 @RequestParam 绑定简单类型
@GetMapping("/user")
public String getUserById(@RequestParam("id") Long id) {
return "userDetails";
}

ModelAttribute@RequestBody 的区别

  • @ModelAttribute 是基于传统的表单参数和 URL 请求参数进行数据绑定。
  • @RequestBody 是用于处理 HTTP 请求体中的 JSON 或 XML 数据。

示例

1
2
3
4
5
6
7
8
9
10
11
// 使用 @ModelAttribute 处理表单数据
@PostMapping("/register")
public String registerUser(@ModelAttribute User user) {
return "registrationSuccess";
}

// 使用 @RequestBody 处理 JSON 请求
@PostMapping("/api/register")
public String registerUserViaApi(@RequestBody User user) {
return "apiSuccess";
}

什么是 Spring IOC?

Spring IOC(Inversion of Control,控制反转)是 Spring 框架的核心概念之一。它是通过依赖注入(Dependency Injection) 实现的。IOC 让对象的创建与管理职责由容器负责,而不是由对象自身控制。

  • 核心思想:控制反转意味着将对象的创建和依赖关系交由 Spring 容器管理,而不是由程序代码直接控制。这种机制使得程序更加灵活和解耦,提升了代码的可维护性和扩展性。
  • 依赖注入:通过构造器注入、setter 注入或接口注入,将对象所需的依赖传递给它,而不是让对象自行创建依赖。

理解控制和反转

IOC,即 Inversion of Control,控制反转。

首先要明确 IOC 是一种思想,而不是一个具体的技术,其次 IOC 这个思想也不是 Spring 创造的。

然后我们要理解到底控制的是什么,其实就是控制对象的创建,IOC 容器根据配置文件来创建对象,在对象的生命周期内,在不同时期根据不同配置进行对象的创建和改造。

那什么被反转了?其实就是关于创建对象且注入依赖对象的这个动作,本来这个动作是由我们程序员在代码里面指定的,例如对象 A 依赖对象 B,在创建对象 A 代码里,我们需要写好如何创建对象 B,这样才能构造出一个完整的 A。

而反转之后,这个动作就由 IOC 容器触发,IOC 容器在创建对象 A 的时候,发现依赖对象 B ,根据配置文件,它会创建 B,并将对象 B 注入 A 中。

这里要注意,注入的不一定非得是一个对象,也可以注入配置文件里面的一个值给对象 A 等等。

至此,你应该明确了,控制和反转。

Spring IOC 有什么好处?

对象的创建都由 IOC 容器来控制之后,对象之间就不会有很明确的依赖关系,使得非常容易设计出松耦合的程序。

例如,对象 A 需要依赖一个实现 B,但是对象都由 IOC 控制之后,我们不需要明确地在对象 A 的代码里写死依赖的实现 B,只需要写明依赖一个接口,这样我们的代码就能顺序的编写下去。

然后,我们可以在配置文件里定义 A 依赖的具体的实现 B,根据配置文件,在创建 A 的时候,IOC 容器就知晓 A 依赖的 B,这时候注入这个依赖即可。

如果之后你有新的实现需要替换,那 A 的代码不需要任何改动,你只需要将配置文件 A 依赖 B 改成 B1,这样重启之后,IOC 容器会为 A 注入 B1。

这样就使得类 A 和类 B 解耦了, very nice!

并且也因为创建对象由 IOC 全权把控,那么我们就能很方便的让 IOC 基于扩展点来“加工”对象,例如我们要代理一个对象,IOC 在对象创建完毕,直接判断下这个对象是否需要代理,如果要代理,则直接包装返回代理对象。

这等于我们只要告诉 IOC 我们要什么,IOC 就能基于我们提供的配置文件,创建符合我们需求的对象。

正是这个控制反转的思想,解放了我们的双手

Spring 中的 DI 是什么?

DI(Dependency Injection,依赖注入)普遍认为是 Spring 框架中用于实现控制反转(IOC) 的一种机制。DI 的核心思想是由容器负责对象的依赖注入,而不是由对象自行创建或查找依赖对象。

通过 DI,Spring 容器在创建一个对象时,会自动将这个对象的依赖注入进去,这样可以让对象与其依赖的对象解耦,提升系统的灵活性和可维护性。

依赖注入的优势

  • 解耦合:通过将对象的依赖从代码中抽离出来,由容器负责管理,降低了类之间的耦合度。
  • 提高测试性:通过注入 mock 对象或替代实现,DI 使得单元测试变得更容易。
  • 提高可维护性和灵活性:通过配置文件或注解,开发者可以在不修改代码的情况下更改依赖,增加了应用程序的可扩展性和灵活性。

什么是 Spring Bean?

任何通过 Spring 容器实例化、组装和管理的 Java 对象都可以被称为 Spring Bean。Bean 可以在 Spring 容器中被定义并且通过依赖注入来与其他 Bean 进行互相依赖。

即 Bean 可以看作是 Spring 应用中的一个对象,它的生命周期(创建、初始化、使用、销毁等过程)完全由 Spring 容器管理。

Spring Bean 的生命周期

  • 实例化:当 Spring 容器启动时,根据配置文件或注解,Spring 会首先实例化 Bean。
  • 依赖注入:在实例化之后,Spring 容器会通过构造器、setter 方法或注解将其他 Bean 的依赖注入进来。
  • 初始化:如果 Bean 实现了 InitializingBean 接口或者使用了 @PostConstruct 注解,Spring 会在依赖注入完成后调用相应的初始化方法。
  • 销毁:如果 Bean 实现了 DisposableBean 接口或使用了 @PreDestroy 注解,Spring 会在容器关闭时调用销毁方法。

2. 定义 Spring Bean 的方式

  • XML 配置:早期的 Spring 应用通常通过 XML 文件定义 Bean,使用 <bean> 标签来指定类、构造器参数和依赖关系。
  • 基于注解的配置:使用 @Component、@Service、@Repository、@Controller 等注解可以将类标记为 Spring Bean,Spring 会自动扫描这些类并将其注册为 Bean。
  • Java 配置类:通过 @Configuration 和 @Bean 注解,可以在 Java 类中手动定义 Bean。相比 XML 配置,这种方式更加简洁和类型安全。

Spring 中的 BeanFactory 是什么?

BeanFactory 其实就是 IOC 的底层容器。负责管理和配置应用中的 Bean。它是 Spring IOC(控制反转)容器的基础实现,提供了创建和管理 Bean 的基本功能。通过 BeanFactory,Spring 容器可以按照需求加载和实例化 Bean。

  • 核心概念:BeanFactory 负责从配置源(XML、Java 配置类、注解等)中读取 Bean 的定义,并负责创建、管理这些 Bean 的生命周期。
  • 延迟加载:BeanFactory 的一个重要特性是延迟初始化,即它只会在 Bean 首次请求时才会实例化该 Bean,而不是在容器启动时就立即创建所有的 Bean。

BeanFactory 的实现

我们都说 Spring 是 IOC 容器,说的再直白点,其实就是 Bean 的工厂,它帮我们生产 Bean,如果我们需要 Bean 就从工厂拿到 bean,所以再来理解下 BeanFactory 这个名字,就知晓它就是 Spring 的核心。

例如我们调用 getBean ,这就是 BeanFactory 定义的方法,通过它得到 Bean。

不过 BeanFactory 本身只是一个接口,一般我们所述的 BeanFactory 指的是它实现类:

  • DefaultListableBeanFactory:BeanFactory 的默认实现,通常用于内部处理 Bean 的实例化和管理工作。它支持所有基本的依赖注入特性,如构造器注入、setter 注入等。
  • XmlBeanFactory(已废弃):基于 XML 文件配置的 BeanFactory 实现,已经在 Spring 3.x 中被淘汰,现推荐使用 ApplicationContext。

Spring 中的 FactoryBean 是什么?

FactoryBean 是 Spring 提供的一个特殊接口,允许开发者通过自定义的逻辑创建复杂的 Bean 实例。与普通的 Bean 不同,通过 FactoryBean 创建的 Bean 不一定是 FactoryBean 本身,而是它生产的对象

它提供了一种灵活的方式来控制 Bean 的创建过程,尤其适用于生成动态代理或需要复杂配置的 Bean。

  • 核心概念:FactoryBean 是一个实现了 FactoryBean<T> 接口的 Bean,通过它可以自定义复杂对象的创建逻辑。Spring 容器会调用 getObject() 方法来获取实际的 Bean 实例。
  • 使用场景:FactoryBean 通常用于需要创建复杂对象或需要使用代理模式生成 Bean 的场景。

FactoryBean 的主要方法

  • **getObject()**:这是 FactoryBean 最重要的方法,返回实际的 Bean 实例。Spring 容器会调用这个方法来获取 Bean。
  • **getObjectType()**:返回由 getObject() 方法所返回的对象类型,Spring 可以根据这个类型进行 Bean 的类型检查。
  • **isSingleton()**:用于确定 getObject() 返回的 Bean 是否为单例。如果返回 true,那么 Spring 容器只会创建一个实例;如果返回 false,每次调用都会创建一个新的实例。

FactoryBean 的典型使用场景

  • 复杂对象创建:如果某个 Bean 的创建过程比较复杂,比如需要动态加载配置文件或执行其他逻辑才能实例化对象,FactoryBean 是非常合适的选择。
  • 代理对象生成:FactoryBean 常用于生成动态代理对象。例如,Spring AOP 使用 FactoryBean 来生成代理对象,使得 AOP 切面能够透明地应用于目标对象。
  • 条件性 Bean:在某些条件下返回不同的 Bean 实例,例如根据应用的环境配置不同的数据库连接池或者日志框架实现。

使用 FactoryBean 示例

1)实现 FactoryBean 接口:首先需要定义一个类,实现 FactoryBean<T> 接口,并实现 getObject()getObjectType()isSingleton() 等方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyFactoryBean implements FactoryBean<MyBean> {
@Override
public MyBean getObject() throws Exception {
// 复杂逻辑创建 MyBean 对象
return new MyBean();
}

@Override
public Class<?> getObjectType() {
return MyBean.class;
}

@Override
public boolean isSingleton() {
return true;
}
}

2)使用 FactoryBean:在 Spring 容器中定义 FactoryBean,Spring 会通过 FactoryBean 创建实际的 Bean 实例。

1
<bean id="myBean" class="com.example.MyFactoryBean"/>

这样,我们 getBean("myBean") 会得到 MyFactoryBean#getObject 的结果,如果单纯只想要 MyFactoryBean, 那么加个 “&” 即可,即 getBean("&MyFactoryBean")

4. FactoryBean 与普通 Bean 的区别

  • 创建逻辑不同:普通的 Bean 直接由 Spring 容器管理,而 FactoryBean 通过自定义的 getObject() 方法创建实际的对象。
  • 动态代理和复杂对象:FactoryBean 适用于创建动态代理或复杂的 Bean,而普通 Bean 通常只处理简单的对象创建。

Spring 中的 ObjectFactory 是什么?

ObjectFactory是 Spring 框架中的一个接口,主要用于延迟获取 Bean 实例

ObjectFactory 提供了一种延迟加载的机制,它通过 getObject() 方法返回一个 Bean 的实例。使用 ObjectFactory 可以避免在容器启动时立即创建所有 Bean,即只有在真正需要使用 Bean 时才会从 Spring 容器中获取该 Bean 实例,有助于优化性能。

ObjectFactory 的使用场景

  • 懒加载 Bean:当某个 Bean 的创建过程可能耗时较长或依赖的资源较重时,可以通过 ObjectFactory 进行懒加载,避免容器启动时不必要的 Bean 创建。这能有效提升系统的启动速度。
  • 避免循环依赖:在某些情况下,两个 Bean 可能相互依赖,导致循环依赖问题。通过使用 ObjectFactory,可以延迟其中一个 Bean 的创建,避免循环依赖。

在 Spring 的循环依赖里就用到它了,三级缓存的 map 里面存储的就是 ObjectFactory,用于延迟代理对象的创建。

并且在某些需要在运行时决定动态 Bean 使用的场景,可以使用 ObjectFactory 动态获取 Bean 实例。

Spring 如何解决循环依赖?

ObjectFactory 与 Provider 的对比

Provider 是 JSR-330(依赖注入标准)的接口,与 Spring 的 ObjectFactory 类似。它们都用于延迟获取 Bean 或实例。Provider 更加广泛地被应用于标准 Java 环境,而 ObjectFactory 是 Spring 特有的接口。

Provider 接口的定义如下:

1
2
3
public interface Provider<T> {
T get();
}

主要区别ObjectFactory 是 Spring 内部的接口,具有与 Spring 容器集成的能力,而 Provider 则是 Java 标准中的一部分,适用于更通用的场景。二者在功能上非常相似,都提供了惰性获取 Bean 实例的机制。

ObjectFactory 使用示例

ObjectFactory 通常通过注入的方式使用,Spring 框架会为你提供这个接口的实现,你只需要定义和使用它。例如:

1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class MyService {

@Autowired
private ObjectFactory<MyBean> myBeanFactory;

public void doSomething() {
// 当需要时,获取 Bean 实例
MyBean myBean = myBeanFactory.getObject();
// 使用 myBean 执行逻辑
}
}

Spring 如何解决循环依赖?

关键就是提前暴露未完全创建完毕的 Bean

在 Spring 中主要是使用三级缓存来解决了循环依赖:

  • 一级缓存(Singleton Objects Map): 用于存储完全初始化完成的单例Bean。
  • 二级缓存(Early Singleton Objects Map): 用于存储尚未完全初始化,但已实例化的Bean,用于提前暴露对象,避免循环依赖问题。
  • 三级缓存(Singleton Factories Map): 用于存储对象工厂,当需要时,可以通过工厂创建早期Bean(特别是为了支持AOP代理对象的创建)。

解决步骤

  • Spring 首先创建 Bean 实例,并将其加入三级缓存中(Factory)。
  • 当一个 Bean 依赖另一个未初始化的 Bean 时,Spring 会从三级缓存中获取 Bean 的工厂,并生成该 Bean 的代理对象。
  • 代理对象存入二级缓存,解决循环依赖。
  • 一旦所有依赖 Bean 被完全初始化,Bean 将转移到一级缓存中。

在 Spring 中,只有同时满足以下两点才能解决循环依赖的问题:

  1. 依赖的 Bean 必须都是单例
  2. 依赖注入的方式,必须不全是构造器注入,且 beanName 字母序在前的不能是构造器注入

为什么必须都是单例

如果从源码来看的话,循环依赖的 Bean 是原型模式,会直接抛错:

image.png

所以 Spring 只支持单例的循环依赖,但是为什么呢

按照理解,如果两个 Bean 都是原型模式的话,那么创建 A1 需要创建一个 B1,创建 B1 的时候要创建一个 A2,创建 A2 又要创建一个 B2,创建 B2 又要创建一个 A3,创建 A3 又要创建一个 B3…..

就又卡 BUG 了,是吧,因为原型模式都需要创建新的对象,不能用以前的对象。

如果是单例的话,创建 A 需要创建 B,而创建的 B 需要的是之前的那个 A, 不然就不叫单例了,对吧?

也是基于这点, Spring 就能操作操作了。

具体做法就是:先创建 A,此时的 A 是不完整的(没有注入 B),用个 map 保存这个不完整的 A,再创建 B ,B 需要 A,所以从那个 map 得到“不完整”的 A,此时的 B 就完整了,然后 A 就可以注入 B,然后 A 就完整了,B 也完整了,且它们是相互依赖的。

image.png

读起来好像有点绕,但是逻辑其实很清晰。

为什么不能全是构造器注入

在 Spring 中创建 Bean 分三步:

  1. 实例化,createBeanInstance,就是 new 了个对象
  2. 属性注入,populateBean, 就是 set 一些属性值
  3. 初始化,initializeBean,执行一些 aware 接口中的方法,initMethod,AOP代理等

明确了上面这三点,再结合我上面说的“不完整的”,我们来理一下。

如果全是构造器注入,比如A(B b),那表明在 new 的时候,就需要得到 B,此时需要 new B ,但是 B 也是要在构造的时候注入 A ,即B(A a),这时候 B 需要在一个 map 中找到不完整的 A ,发现找不到。

为什么找不到?因为 A 还没 new 完呢,所以找不到完整的 A,因此如果全是构造器注入的话,那么 Spring 无法处理循环依赖

一个set注入,一个构造器注入一定能成功?

假设我们 A 是通过 set 注入 B,B 通过构造函数注入 A,此时是成功的

我们来分析下:实例化 A 之后,此时可以在 map 中存入 A,开始为 A 进行属性注入,发现需要 B,此时 new B,发现构造器需要 A,此时从 map 中得到 A ,B 构造完毕,B 进行属性注入,初始化,然后 A 注入 B 完成属性注入,然后初始化 A。

整个过程很顺利,没毛病。

image.png

假设 A 是通过构造器注入 B,B 通过 set 注入 A,此时是失败的

我们来分析下:实例化 A,发现构造函数需要 B, 此时去实例化 B,然后进行 B 的属性注入,从 map 里面找不到 A,因为 A 还没 new 成功,所以 B 也卡住了,然后就 gg。

image.png

看到这里,仔细思考的小伙伴可能会说,可以先实例化 B 啊,往 map 里面塞入不完整的 B,这样就能成功实例化 A 了啊。

确实,思路没错但是 Spring 容器是按照字母序创建 Bean 的,A 的创建永远排在 B 前面

现在我们总结一下:

  • 如果循环依赖都是构造器注入,则失败
  • 如果循环依赖不完全是构造器注入,则可能成功,可能失败,具体跟BeanName的字母序有关系。

Spring 解决循环依赖全流程

经过上面的铺垫,我想你对 Spring 如何解决循环依赖应该已经有点感觉了,接下来我们就来看看它到底是如何实现的。

明确了 Spring 创建 Bean 的三步骤之后,我们再来看看它为单例搞的三个 map:

  1. 一级缓存,singletonObjects,存储所有已创建完毕的单例 Bean (完整的 Bean)
  2. 二级缓存,earlySingletonObjects,存储所有仅完成实例化,但还未进行属性注入和初始化的 Bean
  3. 三级缓存,singletonFactories,存储能建立这个 Bean 的一个工厂,通过工厂能获取这个 Bean,延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存

这三个 map 是如何配合的呢?

  1. 首先,获取单例 Bean 的时候会通过 BeanName 先去 singletonObjects(一级缓存) 查找完整的 Bean,如果找到则直接返回,否则进行步骤 2。
  2. 看对应的 Bean 是否在创建中,如果不在直接返回找不到(返回null),如果是,则会去 earlySingletonObjects (二级缓存)查找 Bean,如果找到则返回,否则进行步骤 3
  3. 去 singletonFactories (三级缓存)通过 BeanName 查找到对应的工厂,如果存着工厂则通过工厂创建 Bean ,并且放置到 earlySingletonObjects 中。
  4. 如果三个缓存都没找到,则返回 null。

从上面的步骤我们可以得知,如果查询发现 Bean 还未创建,到第二步就直接返回 null,不会继续查二级和三级缓存。

返回 null 之后,说明这个 Bean 还未创建,这个时候会标记这个 Bean 正在创建中,然后再调用 createBean 来创建 Bean,而实际创建是调用方法 doCreateBean。

doCreateBean 这个方法就会执行上面我们说的三步骤:

  1. 实例化
  2. 属性注入
  3. 初始化

在实例化 Bean 之后,会往 singletonFactories 塞入一个工厂,而调用这个工厂的 getObject 方法,就能得到这个 Bean

1
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

要注意,此时 Spring 是不知道会不会有循环依赖发生的,但是它不管,反正往 singletonFactories 塞这个工厂,这里就是提前暴露

然后就开始执行属性注入,这个时候 A 发现需要注入 B,所以去 getBean(B),此时又会走一遍上面描述的逻辑,到了 B 的属性注入这一步。

此时 B 调用 getBean(A),这时候一级缓存里面找不到,但是发现 A 正在创建中的,于是去二级缓存找,发现没找到,于是去三级缓存找,然后找到了。

并且通过上面提前在三级缓存里暴露的工厂得到 A,然后将这个工厂从三级缓存里删除,并将 A 加入到二级缓存中。

然后结果就是 B 属性注入成功。

紧接着 B 调用 initializeBean 初始化,最终返回,此时 B 已经被加到了一级缓存里 。

这时候就回到了 A 的属性注入,此时注入了 B,接着执行初始化,最后 A 也会被加到一级缓存里,且从二级缓存中删除 A。

Spring 解决依赖循环就是按照上面所述的逻辑来实现的。

重点就是在对象实例化之后,都会在三级缓存里加入一个工厂,提前对外暴露还未完整的 Bean,这样如果被循环依赖了,对方就可以利用这个工厂得到一个不完整的 Bean,破坏了循环的条件。

为什么 Spring 循环依赖需要三级缓存,二级不够吗?

Spring 之所以需要三级缓存而不是简单的二级缓存,主要原因在于AOP代理Bean的早期引用问题

二级缓存虽然可以解决循环依赖的问题,但在涉及到动态代理(AOP) 时,直接使用二级缓存不做任何处理会导致我们拿到的 Bean 是未代理的原始对象。如果二级缓存内存放的都是代理对象,则违反了 Bean 的生命周期。

进一步理解分析为什么需要三级缓存

很明显,如果仅仅只是为了破解循环依赖,二级缓存够了,压根就不必要三级。

你思考一下,在实例化 Bean A 之后,我在二级 map 里面塞入这个 A,然后继续属性注入,发现 A 依赖 B 所以要创建 Bean B,这时候 B 就能从二级 map 得到 A ,完成 B 的建立之后, A 自然而然能完成。

所以为什么要搞个三级缓存,且里面存的是创建 Bean 的工厂呢

我们来看下调用工厂的 getObject 到底会做什么,实际会调用下面这个方法:

1
2
3
4
5
6
7
8
9
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}Copy to clipboardErrorCopied

重点就在中间的判断,如果 false,返回就是参数传进来的 bean,没任何变化。

如果是 true 说明有 InstantiationAwareBeanPostProcessors ,且循环的 smartInstantiationAware 类型,如有这个 BeanPostProcessor 说明 Bean 需要被 aop 代理

我们都知道如果有代理的话,那么我们想要直接拿到的是代理对象,也就是说如果 A 需要被代理,那么 B 依赖的 A 是已经被代理的 A,所以我们不能返回 A 给 B,而是返回代理的 A 给 B。

这个工厂的作用就是判断这个对象是否需要代理,如果否则直接返回,如果是则返回代理对象。

看到这明白的小伙伴肯定会问,那跟三级缓存有什么关系,我可以在要放到二级缓存的时候判断这个 Bean 是否需要代理,如果要直接放代理的对象不就完事儿了

是的,这个思路看起来没任何问题,问题就出在时机,这跟 Bean 的生命周期有关系。

正常代理对象的生成是基于后置处理器,是在被代理的对象初始化后期调用生成的所以如果你提早代理了其实是违背了 Bean 定义的生命周期

所以 Spring 先在一个三级缓存放置一个工厂,如果产生循环依赖,那么就调用这个工厂提早得到代理对象,如果没产生依赖,这个工厂根本不会被调用,所以 Bean 的生命周期就是对的。

至此,我想你应该明白为什么会有三级缓存了。

也明白,其实破坏循环依赖,其实只有二级缓存就够了,但是碍于生命周期的问题,提前暴露工厂延迟代理对象的生成。

对了,不用担心三级缓存有没有循环依赖,数据堆积的问题,最终单例 Bean 创建完毕都会加入一级缓存,此时会清理下面的二、三级缓存。

img

说下 Spring 由哪些重要的模块组成?

Core Container(核心容器)

  • Spring Core:提供了依赖注入(Dependency Injection, DI)和控制反转(Inversion of Control, IoC)的实现,所有其他Spring模块的基础,别的模块都会依赖此模块。
  • Spring Beans:负责管理Bean的定义和生命周期。通过IoC容器完成Bean的创建、依赖注入、初始化、销毁等操作。
  • Spring Context:基于Core和Beans的高级容器,提供了类似JNDI的上下文功能,还包含了国际化、事件传播、资源访问等功能。
  • Spring Expression Language(SpEL):一个强大的表达式语言,用于在运行时查询和操作对象的值。

AOP(面向切面编程)

  • Spring AOP:提供面向切面编程的功能,可以在方法执行前后或抛出异常时动态插入额外的逻辑,比如日志记录、权限验证、事务管理等。

Data Access(数据访问)

  • Spring JDBC:简化了原生JDBC的操作,提供模板方法来管理连接、资源的释放和异常处理。
  • Spring ORM:支持与主流ORM框架(如Hibernate、JPA、MyBatis等)集成,简化持久层开发。
  • Spring Transaction(事务管理):提供声明式和编程式的事务管理机制,与数据库操作密切结合。

Web层

  • Spring Web:提供基础的Web开发支持,包括Servlet API的集成,适用于构建MVC架构。
  • Spring MVC:实现了Model-View-Controller(MVC)模式的框架,用于构建基于HTTP请求的Web应用。它是一个常用的模块,支持注解驱动的Web开发。
  • Spring WebFlux:提供基于Reactive Streams的响应式编程模型,专为高并发的异步非阻塞请求设计。

主要回答下核心模块,然后带一下切面、数据访问或者 web 相关的即可。

Spring的扩展模块

除了核心模块外,Spring还提供了许多扩展模块,以支持不同的技术需求:

  • Spring Batch:用于批处理的框架,支持大规模数据的处理与分块执行。
  • Spring Integration:提供消息驱动的应用程序集成方案,适用于构建企业集成架构(EAI)。
  • Spring Cloud:用于构建微服务架构的模块集合,支持分布式系统中的服务注册、配置管理、服务调用等功能。

模块化设计的灵活性

Spring 的模块化设计使得开发者可以按需选择需要的模块,而不是强制加载整个框架。例如,如果你只需要依赖注入功能,可以只使用Spring Core和Beans模块,而不引入Web、Security等模块。这种设计确保了Spring的灵活性和可扩展性。

Spring Boot 与 Spring Framework 的关系

Spring Boot是基于Spring Framework的简化开发方式。它自动配置了常用的Spring模块,极大地简化了Spring应用的初始化和配置工作。通过Spring Boot,开发者不再需要手动配置XML或Java配置类,很多默认配置能够让开发者快速上手。

Spring 中的 ApplicationContext 是什么?

ApplicationContext 是多个底层接口组合后的接口。从类图中我们可以看到,它主要提供了五大功能。

  1. 核心容器 BeanFactory
  2. 国际化 MessageSource
  3. 资源获取 ResourceLoader
  4. 环境信息 EnvironmentCapable
  5. 事件发布 ApplicationEventPublisher

image.png

下面,我们使用 SpringBoot 返回的 ConfigurableApplicationContext 来进行讲解。

1
2
3
4
5
6
@SpringBootApplication
public class Main {
public static void main(String[] args) throws Exception{
ConfigurableApplicationContext context = SpringApplication.run(Main.class, args);
}
}

核心容器

BeanFactory 是 Spring 的核心容器,是 ApplicationContext 的父接口。SpringBoot 默认返回的 ConfigurableApplicationContext 是 ApplicationContext 的子接口,它组合了一个 ConfigurableListableBeanFactory,并使用它对外提供 BeanFactory 的功能。

1
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

值得注意的是,控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由 BeanFactory 的实现类提供,BeanFactory 接口本身只定义了 Bean 容器基本的存取方法。

image.png

ApplicationContext 中的使用方法如下。

1
2
3
4
5
6
7
8
// 使用 bean 名字获取
Foo foo = (Foo) context.getBean("foo");

// 使用 bean 的类型获取
Foo foo = context.getBean(Foo.class);

// 使用 bean 名字以及类型获取
Foo foo = context.getBean("foo", Foo.class);

国际化

国际化就是把某个表示翻译为一种特定的语言。在实践中,翻译为哪种语言通常通过浏览器请求头携带。下面简单演示使用方法。

首先定义4个国际化配置文件。

messages.properties(空)

messages_en.properties

1
hi=Hello

messages_ja.properties

1
hi=こんにちは

messages_zh.properties

1
hi=你好

然后就使用 ApplicationContext 进行输出了。

1
2
3
System.out.println(context.getMessage("hi", null, Locale.CHINA));
System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
System.out.println(context.getMessage("hi", null, Locale.JAPANESE));

注意,空的 messages.properties 必须存在。

获取资源

1
2
3
4
5
6
// 磁盘找资源
context.getResources("file:xxx");
// 类路径
context.getResources("classpath:xxx");
// 类路径+所有jar包
context.getResources("classpath*:xxx");

环境信息

1
2
3
4
// 环境变量
context.getEnvironment().getProperty("java_home");
// application.properities中的变量
context.getEnvironment().getProperty("server.port");

发布事件

该功能属于观察者模式,用于解耦代码逻辑。

以用户注册需要发送短信这个业务场景为例。首先定义用户注册事件,事件对象需要继承 ApplicationEvent。

1
2
3
4
5
public class UserRegisteredEvent extends ApplicationEvent {
public UserRegisteredEvent(Object source) {
super(source);
}
}

业务流程中发布事件。

1
context.publishEvent(new UserRegisteredEvent(context));

定义接收对象,处理事件。

1
2
3
4
5
6
7
8
9
10
@Component
public class Component {
private static final Logger log = LoggerFactory.getLogger(Component.class);

@EventListener
public void receive(UserRegisteredEvent event) {
log.debug("{}", event);
log.debug("发送短信");
}
}

Spring 一共有几种注入方式?

  • 构造器注入,Spring 倡导构造函数注入,因为构造器注入返回给客户端使用的时候一定是完整的。
  • setter 注入,可选的注入方式,好处是在有变更的情况下,可以重新注入。
  • 字段注入,就是平日我们用 @Autowired 标记字段
  • 方法注入,就是平日我们用 @Autowired 标记方法
  • 接口回调注入,就是实现 Spring 定义的一些内建接口,例如 BeanFactoryAware,会进行 BeanFactory 的注入

其实官网上关于注入就写了构造器和setter :

img

像字段注入其实官方是不推荐的使用的,因为依赖注解,后没有控制注入顺序且无法注入静态字段。

什么是 AOP?

AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程范式,用于将跨领域的关注点(如日志记录、安全检查、事务管理等)与业务逻辑分离开来。它允许开发者通过“切面”(Aspect)将这些通用功能模块化,并将其应用到应用程序中的多个地方,从而避免代码重复。

  • 核心思想:AOP 的核心思想是将与业务逻辑无关的横切关注点抽取出来,通过声明的方式动态地应用到业务方法上,而不是将这些代码直接嵌入业务逻辑中。
  • 主要组成部分:AOP 包括几个关键概念:切面(Aspect)、连接点(Join Point)、通知(Advice)、切入点(Pointcut)和织入(Weaving)。

通俗理解

具体是含义可以理解为:通过代理的方式,在调用想要的对象方法时候,进行拦截处理,执行切入的逻辑,然后再调用真正的方法实现。

例如,你实现了一个 A 对象,里面有 addUser 方法,此时你需要记录该方法的调用次数。

那么你就可以搞个代理对象,这个代理对象也提供了 addUser 方法,最终你调用的是代理对象的 addUser ,在这个代理对象内部填充记录调用次数的逻辑,最终的效果就类似下面代码:

1
2
3
4
5
6
7
8
9
class A代理 {
A a;// 被代理的 A
void addUser(User user) {
count();// 计数
a.addUser(user);
}
}
最终使用的是:
A代理.addUser(user);

这就叫做面向切面编程,当然具体的代理的代码不是像上面这样写死的,而是动态切入

实现上代理大体上可以分为:动态代理静态代理

  • 动态代理,即在运行时将切面的逻辑进去,按照上面的逻辑就是你实现 A 类,然后定义要代理的切入点和切面的实现,程序会自动在运行时生成类似上面的代理类。
  • 静态代理,在编译时或者类加载时进行切面的织入,典型的 AspectJ 就是静态代理。

AOP 的核心概念

切面(Aspect):切面是一个模块,包含跨领域的关注点,比如日志、事务等。它可以包含多个通知(Advice)来定义在何时何地应用特定的逻辑。

1
2
3
4
5
6
7
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Logging before method execution");
}
}

连接点(Join Point):连接点是程序执行中的一个特定位置,例如方法调用或异常抛出。AOP 允许在这些点上插入切面逻辑。

通知(Advice):通知是定义在连接点执行的操作。常见的通知类型包括:

  • 前置通知(Before):在方法执行之前执行的操作。
  • 后置通知(After):在方法执行之后执行的操作。
  • 环绕通知(Around):在方法执行前后都可以执行的操作,可以控制方法是否执行。
  • 异常通知(AfterThrowing):在方法抛出异常后执行的操作。
  • 返回通知(AfterReturning):在方法成功返回后执行的操作。

切入点(Pointcut):切入点定义了在何处应用通知,通常是通过表达式来匹配方法或类。例如,可以定义某个包下所有方法为切入点。

1
2
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}

织入(Weaving):织入是将切面应用到目标对象的过程。可以在编译时、类加载时或运行时进行织入。

AOP 的主要应用场景

日志记录:通过 AOP 可以将日志逻辑分离到切面中,使日志代码与业务代码解耦。

1
2
3
4
5
6
7
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Method: " + joinPoint.getSignature().getName() + " is called");
}
}

事务管理:可以通过 AOP 实现事务管理,确保在特定方法执行时开启事务,并在方法执行成功或失败后提交或回滚事务。

1
2
3
4
@Transactional
public void transferMoney() {
// 事务管理
}

安全检查:AOP 可以用于权限验证,在方法执行前检查用户是否具有相应权限。

性能监控:通过环绕通知(Around advice),可以记录方法的执行时间,帮助监控应用性能。

Spring AOP 与 AspectJ 的区别