Spring IoC容器
Spring IoC容器相关知识整理
3.1 IoC容器
概念与核心作用
IoC即控制反转(Inversion of Control),它是一种设计思想。传统开发中,对象创建与依赖关系管理由程序自身负责,这导致代码耦合度高、可维护性差。IoC容器的出现改变了这一局面,它负责对象的创建、管理以及依赖注入。通过IoC,对象的控制权从应用程序转移到了容器,实现了松耦合,提高了软件的可扩展性和可维护性。例如在一个电商系统中,商品服务(ProductService)可能依赖于商品数据访问对象(ProductDAO)。在没有IoC容器时,ProductService内部需要手动创建ProductDAO实例,而有了IoC容器,容器会自动创建并注入ProductDAO实例到ProductService中。
IoC容器的工作原理
- 对象定义:开发人员通过配置文件(XML)或注解,在IoC容器中定义对象(也称为Bean)以及它们之间的依赖关系。比如定义一个UserService的Bean,同时声明它依赖于UserDAO的Bean。
- 容器初始化:在应用程序启动时,IoC容器会读取配置信息,创建并管理这些Bean。它会按照配置的依赖关系,递归地创建所有相关的Bean。例如先创建UserDAO的实例,再创建UserService的实例,并将UserDAO实例注入到UserService中。
- 依赖注入:当应用程序需要使用某个Bean时,IoC容器会将该Bean以及其依赖的其他Bean提供给应用程序。这可以通过构造函数注入、Setter方法注入或者字段注入来实现。比如通过构造函数注入UserDAO到UserService中,UserService类就可以如下定义:
public class UserService {
private UserDAO userDAO;
public UserService(UserDAO userDAO) {
this.userDAO = userDAO;
}
// 业务方法
}
Spring中的IoC容器实现
Spring框架提供了多种IoC容器的实现,最常用的是BeanFactory和ApplicationContext。
- BeanFactory:是Spring IoC容器的基础接口,提供了基本的IoC功能,如创建Bean、获取Bean等。它采用延迟加载的方式,只有当应用程序第一次请求某个Bean时,才会创建该Bean实例。这种方式适合资源有限、对启动性能要求较高的场景。
- ApplicationContext:是BeanFactory的子接口,它在BeanFactory的基础上,增加了许多企业级功能,如事件发布、国际化支持等。ApplicationContext在容器启动时,会预先实例化所有的单例Bean,这有助于在启动时发现配置错误。同时,它还支持多种配置方式(XML、注解等),更适合企业级应用开发。例如在一个Web应用中,通常使用ApplicationContext来管理Bean,因为它可以更好地与Spring的其他模块(如Spring MVC)集成。
Bean的生命周期
在IoC容器管理下,Bean的生命周期涵盖多个阶段:
- 实例化:IoC容器根据配置信息创建Bean的实例,比如通过
new关键字调用类的构造函数。若Bean存在依赖,此时依赖尚未注入。例如在创建UserService实例时,仅完成对象的初步构建,UserDAO依赖尚未处理。 - 属性赋值:实例化后,容器按照配置进行依赖注入,为Bean的属性设置值。如将
UserDAO实例注入到UserService的对应属性中,可通过构造函数、Setter方法或字段注入实现。 - 初始化前:在属性赋值后,Bean实例准备进入初始化阶段前,若Bean实现了
BeanNameAware、BeanFactoryAware或ApplicationContextAware接口,容器会调用对应的setBeanName、setBeanFactory、setApplicationContext方法,使Bean能获取自身名称、所在的工厂或应用上下文等信息。 - 初始化:此阶段执行Bean的初始化逻辑。若Bean实现了
InitializingBean接口,容器会调用其afterPropertiesSet方法;若在配置中指定了init - method,容器会调用该方法。比如在UserService中定义了init方法用于初始化资源,配置<bean id="userService" class="com.example.service.UserService" init - method="init"/>,容器会在合适时机调用init方法。 - 使用:Bean初始化完成后,可供应用程序使用,处理业务请求等操作。如在控制器中调用
UserService的业务方法进行用户相关业务处理。 - 销毁前:当容器关闭时,在Bean销毁前,若Bean实现了
DisposableBean接口,容器会调用其destroy方法;若配置中指定了destroy - method,容器会调用该方法。例如UserService实现资源清理逻辑在destroyService方法,配置<bean id="userService" class="com.example.service.UserService" destroy - method="destroyService"/>,容器关闭时会执行destroyService。 - 销毁:Bean完成生命周期,资源被释放,从容器中移除。
3.2 基于XML管理Bean
XML配置文件基础
在Spring中,通过XML配置文件来定义Bean及其依赖关系是一种传统且常用的方式。XML配置文件通常命名为applicationContext.xml,当然也可以自定义文件名。文件结构一般如下:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 在这里定义Bean -->
</beans>
Bean的定义与配置
- 定义Bean:使用
<bean>标签来定义一个Bean,通过id属性唯一标识Bean,class属性指定Bean的实现类。例如定义一个UserService的Bean:
<bean id="userService" class="com.example.service.UserService"/>
- 设置属性值:可以通过
<property>标签来设置Bean的属性值。如果UserService类有一个名为userDAO的属性,且提供了对应的Setter方法,那么可以如下配置:
<bean id="userService" class="com.example.service.UserService">
<property name="userDAO" ref="userDAO"/>
</bean>
<bean id="userDAO" class="com.example.dao.UserDAO"/>
这里name属性指定属性名,ref属性引用另一个Bean的id,表示依赖注入。
3. 构造函数注入:如果要使用构造函数注入,可以通过<constructor-arg>标签。假设UserService的构造函数接收一个UserDAO参数,配置如下:
<bean id="userService" class="com.example.service.UserService">
<constructor-arg ref="userDAO"/>
</bean>
<bean id="userDAO" class="com.example.dao.UserDAO"/>
<constructor-arg>标签的顺序对应构造函数参数的顺序。
4. 设置基本类型属性:对于基本类型(如int、String等)的属性,可以直接设置值。比如UserService类有一个名为defaultUserName的String属性,配置如下:
<bean id="userService" class="com.example.service.UserService">
<property name="defaultUserName" value="admin"/>
</bean>
高级XML配置
- Bean的作用域:通过
scope属性来定义Bean的作用域。常见的作用域有:- singleton(单例,默认):在IoC容器中只存在一个实例,所有对该Bean的请求都返回同一个实例。
- prototype(原型):每次请求该Bean时,都会创建一个新的实例。例如对于一个无状态的工具类Bean,可以使用singleton作用域以减少内存开销;而对于有状态的、与用户会话相关的Bean,可能更适合使用prototype作用域。配置示例:
<bean id="userService" class="com.example.service.UserService" scope="singleton"/>
- 依赖注入集合属性:如果Bean的属性是集合类型(如List、Set、Map等),也可以在XML中配置。例如UserService类有一个List类型的userRoles属性,配置如下:
<bean id="userService" class="com.example.service.UserService">
<property name="userRoles">
<list>
<value>admin</value>
<value>user</value>
</list>
</property>
</bean>
对于Map类型,示例如下:
<bean id="userService" class="com.example.service.UserService">
<property name="userPermissions">
<map>
<entry key="read" value="true"/>
<entry key="write" value="false"/>
</map>
</property>
</bean>
- 引用外部属性文件:可以将一些配置信息(如数据库连接字符串)放在外部属性文件中,然后在XML中引用。首先在src/main/resources目录下创建一个jdbc.properties文件,内容如下:
jdbc.url=jdbc:mysql://localhost:3306/mydb
jdbc.username=root
jdbc.password=123456
然后在XML中配置如下:
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
这里<context:property-placeholder>标签用于加载外部属性文件,${}语法用于引用属性文件中的值。
Bean生命周期在XML配置中的体现
在XML配置中,可通过init - method和destroy - method属性明确指定Bean的初始化和销毁方法。例如:
<bean id="customService" class="com.example.service.CustomService"
init - method="initCustom" destroy - method="cleanupCustom"/>
在CustomService类中需对应定义initCustom和cleanupCustom方法,容器在合适阶段(初始化和销毁时)会调用它们,实现Bean生命周期特定阶段的自定义逻辑。
3.3 基于注解管理Bean(☆)
常用注解介绍
- @Component:这是一个通用的组件注解,用于将一个类标记为Spring中的一个组件,会被IoC容器扫描并管理。例如定义一个工具类:
@Component
public class Utils {
// 工具方法
}
- @Repository:用于标记数据访问层(DAO层)的组件。它不仅具备@Component的功能,还可以在发生数据访问异常时,进行特定的异常转换。例如:
@Repository
public class UserDAO {
// 数据库操作方法
}
- @Service:用于标记业务逻辑层(Service层)的组件。它同样具备@Component的功能,并且可以在该类中进行事务管理等业务逻辑相关的操作。例如:
@Service
public class UserService {
private UserDAO userDAO;
// 假设通过构造函数注入
public UserService(UserDAO userDAO) {
this.userDAO = userDAO;
}
// 业务方法
}
- @Controller:用于标记表现层(如Spring MVC中的控制器)的组件。它也继承了@Component的功能,主要用于处理Web请求。例如:
@Controller
public class UserController {
private UserService userService;
// 假设通过构造函数注入
public UserController(UserService userService) {
this.userService = userService;
}
// 处理请求的方法
}
依赖注入注解
- @Autowired:这是最常用的依赖注入注解。它可以用在构造函数、Setter方法、字段上,用于自动装配Bean。例如在UserService中注入UserDAO:
@Service
public class UserService {
@Autowired
private UserDAO userDAO;
// 业务方法
}
当用在构造函数上时,如果构造函数只有一个参数,@Autowired注解可以省略。例如:
@Service
public class UserService {
private UserDAO userDAO;
@Autowired
public UserService(UserDAO userDAO) {
this.userDAO = userDAO;
}
// 业务方法
}
- @Qualifier:当有多个相同类型的Bean时,@Autowired无法确定要注入哪个Bean,此时可以结合@Qualifier注解使用。例如有两个UserDAO的实现类UserDAOImpl1和UserDAOImpl2:
@Repository
public class UserDAOImpl1 implements UserDAO {
// 实现方法
}
@Repository
public class UserDAOImpl2 implements UserDAO {
// 实现方法
}
在UserService中指定注入UserDAOImpl1:
@Service
public class UserService {
@Autowired
@Qualifier("userDAOImpl1")
private UserDAO userDAO;
// 业务方法
}
这里@Qualifier中的值对应Bean的名称(默认是类名首字母小写)。 3. @Resource:这是JDK提供的注解,也可以用于依赖注入。它有一个name属性,用于指定要注入的Bean的名称。例如:
@Service
public class UserService {
@Resource(name = "userDAO")
private UserDAO userDAO;
// 业务方法
}
@Resource注解如果不指定name属性,默认会按照Bean的名称进行注入。如果Bean的名称与注入点的变量名相同,也可以省略name属性。
注解驱动的配置
要启用基于注解的Bean管理,需要在Spring配置文件中进行相应的配置。如果使用XML配置文件,可以通过<context:component-scan>标签来指定要扫描的包,Spring会自动扫描该包及其子包下的所有类,将标记了@Component及其衍生注解的类注册为Bean。例如:
<context:component-scan base-package="com.example"/>
这里base-package属性指定要扫描的基础包路径。如果使用Java配置类,可以通过@ComponentScan注解来实现相同的功能。例如:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 配置类中的其他配置方法
}
在实际开发中,基于注解的Bean管理方式更加简洁、灵活,能够减少大量的XML配置代码,提高开发效率,因此在现代Spring项目中被广泛应用。同时,它也可以与基于XML的配置方式混合使用,根据具体的业务场景选择最合适的配置方式。
Bean生命周期在注解配置中的体现
- 实现特定接口:与XML配置类似,若Bean实现
InitializingBean接口,容器在属性注入完成后会调用afterPropertiesSet方法进行初始化;实现DisposableBean接口,容器关闭时会调用destroy方法进行销毁。例如:
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
@Service
public class LifecycleService implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// 初始化逻辑
}
@Override
public void destroy() throws Exception {
// 销毁逻辑
}
}
- 使用@PostConstruct和@PreDestroy注解:这是Java EE提供的注解,在Spring中也可用于管理Bean生命周期。
@PostConstruct注解的方法会在Bean属性注入后执行,类似XML中的init - method;@PreDestroy注解的方法会在Bean销毁前执行,类似XML中的destroy - method。示例如下:
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Service;
@Service
public class AnotherLifecycleService {
@PostConstruct
public void init() {
// 初始化逻辑
}
@PreDestroy
public void cleanUp() {
// 销毁逻辑
}
}
通过这些方式,无论是基于XML还是注解配置,开发人员都能精确控制Bean在IoC容器中的生命周期,确保资源合理初始化与释放,提升应用程序的稳定性和性能 。
Spring 生态是以 Spring Framework 为核心,衍生出的一系列相互关联、功能互补的技术和工具集合,用于简化企业级应用开发,覆盖从单体应用到分布式微服务、从 Web 开发到数据处理等诸多场景。