【Spring 默认是否管理 Request 和 Session Bean 的生命周期?】
要测试 Spring 默认是否管理 Request
和 Session
作用域的 Bean 的生命周期,可以通过以下步骤实现:
- 验证 Spring 是否创建了 Bean:检查 Spring 容器是否成功加载并管理了
Request
和Session
作用域的 Bean。 - 验证 Bean 的生命周期回调方法是否被调用:通过实现生命周期接口(如
InitializingBean
和DisposableBean
)或使用注解(如@PostConstruct
和@PreDestroy
),验证初始化和销毁逻辑是否被调用。 - 验证 Bean 的作用域行为:
- 对于
Request
作用域的 Bean,每次 HTTP 请求都会创建一个新的实例。 - 对于
Session
作用域的 Bean,每个 HTTP Session 都会创建一个新的实例。
- 对于
以下是一个完整的代码示例,展示如何测试 Spring 默认是否管理 Request
和 Session
作用域的 Bean 的生命周期。
1. 项目结构
src
├── main
│ ├── java
│ │ └── com.example
│ │ ├── MyRequestScopedBean.java
│ │ ├── MySessionScopedBean.java
│ │ └── AppConfig.java
│ └── resources
└── test
└── java
└── com.example
└── RequestSessionBeanTest.java
2. 示例代码
2.1 MyRequestScopedBean.java
这是一个 Request
作用域的 Bean,定义了生命周期回调方法。
package com.example;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST) // 定义为 Request 作用域
public class MyRequestScopedBean {
public MyRequestScopedBean() {
System.out.println("MyRequestScopedBean: Constructor called");
}
@PostConstruct
public void postConstruct() {
System.out.println("MyRequestScopedBean: @PostConstruct - Initialization logic");
}
@PreDestroy
public void preDestroy() {
System.out.println("MyRequestScopedBean: @PreDestroy - Cleanup logic");
}
public void doSomething() {
System.out.println("MyRequestScopedBean: Doing something...");
}
}
2.2 MySessionScopedBean.java
这是一个 Session
作用域的 Bean,定义了生命周期回调方法。
package com.example;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
@Component
@Scope(WebApplicationContext.SCOPE_SESSION) // 定义为 Session 作用域
public class MySessionScopedBean {
public MySessionScopedBean() {
System.out.println("MySessionScopedBean: Constructor called");
}
@PostConstruct
public void postConstruct() {
System.out.println("MySessionScopedBean: @PostConstruct - Initialization logic");
}
@PreDestroy
public void preDestroy() {
System.out.println("MySessionScopedBean: @PreDestroy - Cleanup logic");
}
public void doSomething() {
System.out.println("MySessionScopedBean: Doing something...");
}
}
2.3 AppConfig.java
这是一个 Spring 配置类,启用组件扫描。
package com.example;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
}
2.4 RequestSessionBeanTest.java
这是测试类,验证 Request
和 Session
作用域的 Bean 的生命周期。
测试代码
package com.example;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
@SpringBootTest
@AutoConfigureMockMvc
public class RequestSessionBeanTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private MyRequestScopedBean requestScopedBeanProxy; // 注入代理对象
@Autowired
private MySessionScopedBean sessionScopedBeanProxy; // 注入代理对象
@Test
public void testRequestScopedBean() throws Exception {
// 模拟 HTTP GET 请求
mockMvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get("/testRequest"))
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.status().isOk());
}
@Test
public void testSessionScopedBean() {
// 创建一个模拟的 HTTP 请求
MockHttpServletRequest request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new org.springframework.web.context.request.ServletRequestAttributes(request));
try {
// 获取 SessionScopedBean 实例
MySessionScopedBean sessionBean1 = sessionScopedBeanProxy; // 通过代理获取
sessionBean1.doSomething();
// 模拟新的 Session
request.getSession().invalidate(); // 销毁当前 Session
MockHttpServletRequest newRequest = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new org.springframework.web.context.request.ServletRequestAttributes(newRequest));
MySessionScopedBean sessionBean2 = sessionScopedBeanProxy; // 获取新的 SessionScopedBean 实例
sessionBean2.doSomething();
} finally {
// 清理请求上下文
RequestContextHolder.resetRequestAttributes();
}
}
}
2.5 控制器代码
为了测试 Request
和 Session
作用域的 Bean,可以创建一个简单的控制器。
MyController.java
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
private final MyRequestScopedBean requestScopedBean;
@Autowired
public MyController(MyRequestScopedBean requestScopedBean) {
this.requestScopedBean = requestScopedBean;
}
@GetMapping("/testRequest")
public String testRequestScopedBean() {
System.out.println("Accessing RequestScopedBean in controller");
requestScopedBean.doSomething();
return "RequestScopedBean tested successfully!";
}
}
3. 输出结果
3.1 测试 RequestScopedBean
运行 testRequestScopedBean()
方法后,控制台会输出类似以下内容:
MyRequestScopedBean: Constructor called
MyRequestScopedBean: @PostConstruct - Initialization logic
Accessing RequestScopedBean in controller
MyRequestScopedBean: Doing something...
MyRequestScopedBean: @PreDestroy - Cleanup logic
说明
- 每次 HTTP 请求都会创建一个新的
RequestScopedBean
实例。 - 请求结束后,
@PreDestroy
方法会被调用,销毁该实例。
3.2 测试 SessionScopedBean
运行 testSessionScopedBean()
方法后,控制台会输出类似以下内容:
MySessionScopedBean: Constructor called
MySessionScopedBean: @PostConstruct - Initialization logic
MySessionScopedBean: Doing something...
MySessionScopedBean: @PreDestroy - Cleanup logic
MySessionScopedBean: Constructor called
MySessionScopedBean: @PostConstruct - Initialization logic
MySessionScopedBean: Doing something...
MySessionScopedBean: @PreDestroy - Cleanup logic
说明
- 第一次请求时,创建了一个
SessionScopedBean
实例。 - 销毁当前 Session 后,再次请求时会创建一个新的
SessionScopedBean
实例。
4. 验证 Request
和 Session
的作用域行为
4.1 验证 RequestScopedBean
的独立实例
在测试代码中,可以通过多次请求验证 RequestScopedBean
是否每次都创建新的实例。
示例
mockMvc.perform(get("/testRequest"));
mockMvc.perform(get("/testRequest"));
控制台会输出两次 MyRequestScopedBean: Constructor called
,说明每次请求都会创建一个新的实例。
4.2 验证 SessionScopedBean
的独立实例
在测试代码中,可以通过销毁 Session 后再次请求,验证 SessionScopedBean
是否创建新的实例。
示例
request.getSession().invalidate(); // 销毁当前 Session
MockHttpServletRequest newRequest = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(newRequest));
控制台会输出两次 MySessionScopedBean: Constructor called
,说明每次 Session 都会创建一个新的实例。
5. 注意事项
5.1 RequestScopedBean
和 SessionScopedBean
的依赖注入
- 在 Spring 中,
RequestScopedBean
和SessionScopedBean
是动态代理的,因此不能直接注入其实例。 - 如果需要手动获取
RequestScopedBean
或SessionScopedBean
,可以通过@Autowired
注入一个代理对象,并通过RequestContextHolder
获取当前请求的 Bean。
示例
@Autowired
private MyRequestScopedBean requestScopedBeanProxy;
@Test
public void testRequestScopedBeanProxy() {
// 手动获取当前请求的 RequestScopedBean 实例
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
if (attributes instanceof ServletRequestAttributes) {
ServletRequestAttributes servletAttributes = (ServletRequestAttributes) attributes;
MyRequestScopedBean actualBean = (MyRequestScopedBean) servletAttributes.getRequest().getAttribute("requestScopedBean");
actualBean.doSomething();
}
}
5.2 测试环境的要求
RequestScopedBean
和SessionScopedBean
是 Web 环境下的作用域,因此测试时需要确保 Spring 的 Web 环境已正确加载。- 使用
@SpringBootTest
注解可以加载完整的应用上下文。 - 如果只测试 Web 层,可以使用
@WebMvcTest
注解。
6. 总结
如何测试 Request
和 Session
作用域的 Bean 的生命周期?
-
验证 Bean 的创建:
- 检查构造方法是否被调用。
- 检查
@PostConstruct
注解的方法是否被调用。
-
验证 Bean 的作用域行为:
- 对于
RequestScopedBean
,每次 HTTP 请求都会创建一个新的实例。 - 对于
SessionScopedBean
,每个 HTTP Session 都会创建一个新的实例。
- 对于
-
验证 Bean 的销毁:
- 对于
RequestScopedBean
,销毁逻辑会在请求结束后自动调用。 - 对于
SessionScopedBean
,销毁逻辑需要手动实现(因为 Spring 不管理 Session 的生命周期)。
- 对于
通过上述测试代码和验证方法,可以清楚地了解 Spring 如何管理 Request
和 Session
作用域的 Bean 的生命周期。