Spring核心概念深度解析:IoC/DI、Bean作用域与SpEL

一、控制反转(IoC)与依赖注入(DI)

容器管理Bean的生命周期

Spring容器负责管理Bean的完整生命周期,从创建到销毁。典型生命周期如下:

图1

实践建议

  • 对于轻量级初始化逻辑使用@PostConstruct注解方法
  • 对于重量级资源释放使用@PreDestroy注解方法
  • 实现*Aware接口时要谨慎,这会增加与Spring框架的耦合

依赖注入的三种方式

  1. 构造器注入(Spring官方推荐)

    @Service
    public class OrderService {
     private final PaymentService paymentService;
     
     @Autowired // Spring 4.3+ 可省略
     public OrderService(PaymentService paymentService) {
         this.paymentService = paymentService;
     }
    }
  2. Setter注入

    @Service
    public class UserService {
     private UserRepository userRepository;
     
     @Autowired
     public void setUserRepository(UserRepository userRepository) {
         this.userRepository = userRepository;
     }
    }
  3. 字段注入(不推荐)

    @Service
    public class ProductService {
     @Autowired
     private ProductRepository productRepository;
    }

实践建议

  • 强制依赖使用构造器注入
  • 可选依赖使用Setter注入
  • 避免字段注入(不利于测试和不变性)

@Autowired与@Resource的区别

特性@Autowired@Resource
来源Spring框架JSR-250标准
默认注入方式按类型按名称
名称限定@Qualifiername属性
是否支持构造函数支持不支持
是否支持required支持不支持
// @Autowired使用示例
@Service
public class ExampleService {
    @Autowired
    @Qualifier("mainDataSource")
    private DataSource dataSource;
}

// @Resource使用示例
@Service
public class ExampleService {
    @Resource(name = "mainDataSource")
    private DataSource dataSource;
}

二、Bean的作用域

Spring支持以下Bean作用域:

作用域描述适用场景
singleton容器中只存在一个实例(默认)无状态服务类
prototype每次请求都创建新实例有状态对象
request每个HTTP请求一个实例Web请求相关对象
session每个HTTP会话一个实例用户会话相关对象
applicationServletContext生命周期全局应用对象
websocketWebSocket会话生命周期WebSocket相关对象

配置方式

// Java配置方式
@Bean
@Scope("prototype")
public PrototypeBean prototypeBean() {
    return new PrototypeBean();
}

// 注解方式
@Service
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedService {
    // ...
}

实践建议

  • 默认使用singleton,减少对象创建开销
  • 有状态Bean使用prototype
  • Web作用域Bean要配合proxyMode使用
  • 注意线程安全问题,特别是prototype作用域

三、SpEL(Spring表达式语言)

基本语法

SpEL支持丰富的表达式功能:

// 字面量表达式
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();

// 方法调用
parser.parseExpression("'Hello World'.concat('!')").getValue();

// 属性访问
parser.parseExpression("'Hello World'.bytes").getValue();

// 运算符
parser.parseExpression("2 * 3 + 5").getValue(Integer.class);

在Spring配置中的应用

  1. 注解中的SpEL

    @Value("#{systemProperties['db.username']}")
    private String username;
    
    @Value("#{ T(java.lang.Math).random() * 100.0 }")
    private double randomNumber;
  2. XML配置中的SpEL

    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
     <property name="jdbcUrl" value="#{systemEnvironment['DB_URL']}"/>
     <property name="username" value="#{systemEnvironment['DB_USER']}"/>
    </bean>
  3. 条件化Bean

    @Bean
    @ConditionalOnExpression("#{environment.getProperty('app.feature.enabled') == 'true'")
    public FeatureService featureService() {
     return new FeatureServiceImpl();
    }

安全考虑

SpEL表达式可能成为注入攻击的入口,使用时应注意:

  • 避免使用用户输入直接构建表达式
  • 对于复杂表达式考虑使用自定义PermissionEvaluator
  • 在生产环境中限制评估上下文

实践建议

  • 使用SpEL简化配置,特别是环境相关的值
  • 避免在SpEL中编写复杂业务逻辑
  • 对用户提供的表达式要进行严格校验

总结

Spring的核心概念构成了整个框架的基础:

  1. IoC/DI实现了松耦合的组件管理
  2. Bean作用域提供了灵活的对象生命周期控制
  3. SpEL增强了配置的灵活性和表达能力

掌握这些核心概念后,开发者可以更高效地使用Spring框架构建可维护的应用程序。建议在实际项目中根据具体需求选择合适的依赖注入方式,合理规划Bean的作用域,并适度使用SpEL提升配置的灵活性。

添加新评论