在Spring框架中,Bean的作用域决定了Bean实例的创建方式和生命周期。理解作用域对于构建正确的Spring应用至关重要,特别是在多线程环境和Web应用中。
单例模式是Spring默认的作用域,也是使用最广泛的一种。在实际项目中,我遇到过不少因为错误使用单例导致的线程安全问题。比如:
java复制@Component
public class CounterService {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
这个简单的计数器在多线程环境下会出现竞态条件。解决方法通常有:
重要提示:单例Bean中要特别注意成员变量的线程安全性。无状态Bean(没有成员变量或只有final成员变量)是最安全的单例实现方式。
原型作用域每次都会创建新实例,适合以下场景:
在Spring MVC中,控制器默认是单例的。如果控制器需要维护状态,可以这样配置:
java复制@Controller
@Scope("prototype")
public class UserController {
// 控制器逻辑
}
对于request、session、application和websocket作用域,有几个关键点需要注意:
java复制@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences userPreferences() {
return new UserPreferences();
}
理解Bean的生命周期对于编写高质量的Spring应用至关重要。下面我将结合自己多年的实践经验,详细解析每个阶段。
Spring通过反射调用构造方法创建Bean实例。这里有几个常见问题:
一个典型的构造方法注入示例:
java复制@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
Spring提供了多种依赖注入方式:
| 注入方式 | 示例 | 适用场景 |
|---|---|---|
| 构造器注入 | 如上例 | 强依赖,推荐方式 |
| Setter注入 | @Autowired public void setXxx(Xxx xxx) |
可选依赖 |
| 字段注入 | @Autowired private Xxx xxx; |
不推荐,测试困难 |
| 方法注入 | @Autowired public void configure(Xxx xxx) |
特殊配置 |
实际经验:构造器注入是首选,它使依赖关系明确且支持不可变对象。字段注入虽然方便,但会隐藏依赖关系并增加测试难度。
Aware接口让Bean能获取Spring容器的信息。常见的应用场景:
java复制@Component
public class MyBean implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
public void useContext() {
// 使用context获取其他Bean等
}
}
java复制@Component
public class EnvConfig implements EnvironmentAware {
private Environment env;
@Override
public void setEnvironment(Environment env) {
this.env = env;
}
public String getProperty(String key) {
return env.getProperty(key);
}
}
BeanPostProcessor是Spring扩展机制的核心。我们可以自定义处理器来实现各种功能:
java复制@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("Before init: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof MyService) {
return Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(proxy, method, args) -> {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(bean, args);
System.out.println("After method: " + method.getName());
return result;
});
}
return bean;
}
}
这个例子展示了如何为特定Bean创建代理,实现AOP类似的功能。
初始化阶段的方法执行有严格顺序:
一个典型示例:
java复制@Component
public class ComplexBean implements InitializingBean {
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct method");
}
@Override
public void afterPropertiesSet() {
System.out.println("InitializingBean.afterPropertiesSet()");
}
public void customInit() {
System.out.println("Custom init method");
}
}
@Configuration
public class AppConfig {
@Bean(initMethod = "customInit")
public ComplexBean complexBean() {
return new ComplexBean();
}
}
输出顺序将是:
与初始化对应,销毁阶段也有类似机制:
一个常见的用例是释放资源:
java复制@Component
public class ResourceHolder implements DisposableBean {
private final SomeResource resource;
public ResourceHolder() {
this.resource = acquireResource();
}
@PreDestroy
public void cleanup() {
System.out.println("Cleaning up with @PreDestroy");
}
@Override
public void destroy() {
System.out.println("DisposableBean.destroy()");
resource.release();
}
public void customDestroy() {
System.out.println("Custom destroy method");
}
}
Spring Boot的自动配置是其最强大的特性之一,理解其工作原理对于定制和扩展至关重要。
自动配置是通过@EnableAutoConfiguration和spring.factories文件实现的。典型流程:
一个简单的自动配置类示例:
java复制@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyServiceProperties properties) {
return new MyService(properties);
}
}
Spring Boot提供了一系列条件注解来控制Bean的创建:
| 注解 | 说明 | 示例 |
|---|---|---|
| @ConditionalOnClass | 类路径存在指定类时生效 | @ConditionalOnClass(DataSource.class) |
| @ConditionalOnMissingBean | 容器中不存在指定Bean时生效 | @ConditionalOnMissingBean(DataSource.class) |
| @ConditionalOnProperty | 配置属性满足条件时生效 | @ConditionalOnProperty(name="feature.enabled", havingValue="true") |
| @ConditionalOnWebApplication | Web应用环境下生效 | @ConditionalOnWebApplication(type=Type.SERVLET) |
在实际项目中,我经常使用这些注解来实现特性开关和模块化配置。
创建自定义starter的步骤:
创建autoconfigure模块:
创建starter模块:
命名规范:
一个真实案例:我们曾为公司内部的消息服务创建了messaging-spring-boot-starter,大大简化了各服务的集成工作。
当自动配置行为不符合预期时,可以通过以下方式调试:
启用调试日志:
properties复制logging.level.org.springframework.boot.autoconfigure=DEBUG
使用条件评估报告:
java复制@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApp.class);
app.setLogStartupInfo(false);
app.run(args);
}
}
启动时添加--debug参数:
code复制java -jar myapp.jar --debug
使用@AutoConfigureBefore和@AutoConfigureAfter调整自动配置顺序
Spring通过三级缓存解决构造器注入的循环依赖问题。在实际开发中,我推荐以下做法避免循环依赖:
一个使用@Lazy的例子:
java复制@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
当在单例Bean中注入作用域更短的Bean(如prototype)时,需要使用代理:
java复制@Configuration
public class MyConfig {
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.TARGET_CLASS)
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
}
代理模式有两种:
Bean创建失败:
属性注入失败:
生命周期方法不执行:
在实际项目中,我通常会通过以下步骤诊断Bean相关问题: