Java Spring 核心容器、IOC
一、IOC(控制反转)核心概念
基本概念
控制反转 是一种将对象的创建、依赖关系的管理和生命周期的控制权从应用程序代码转移到外部容器的设计原则 。在传统编程中,对象的创建和依赖关系是由程序自身控制的,而在 IOC 模式下,控制权被反转给了一个专门的容器。这个容器负责创建对象、注入依赖,并管理对象的生命周期,使得代码更加松散耦合,提高了可维护性和可测试性。
为什么需要 IOC
降低耦合度
在传统的编程方式中,一个类往往需要自己负责创建和管理它所依赖的对象。这会导致类与类之间的耦合度很高 ,当依赖的对象发生变化时,需要修改多个地方的代码 。而使用 IOC 可以将对象的创建和依赖关系的管理交给容器,使得类只需要关注自身的业务逻辑,降低了类与类之间的耦合度。
提高可测试性
在使用 IOC 的情况下,可以很方便地为类注入模拟对象,从而进行单元测试。因为对象的依赖是通过外部注入的,所以可以在测试时轻松替换依赖对象,使得测试更加简单和可靠。
便于代码维护和扩展
由于对象的创建和依赖关系的管理都集中在容器中,当需要修改或扩展功能时,只需要修改容器的配置,而不需要修改大量的业务代码。这使得代码的维护和扩展变得更加容易。
传统方式 :对象主动创建依赖(
new
操作)。IOC 方式 :容器负责创建对象并注入依赖,实现解耦。
二、容器(Container)
Spring 容器是 Spring 框架的核心部分,它负责创建、管理和装配 Bean 对象,以及处理 Bean 之间的依赖关系。以下是关于 Spring 容器的详细介绍:
概念
Spring 容器是一个轻量级的反转控制(IoC)容器,也被称为依赖注入(DI)容器。它通过读取配置文件 或扫描注解来创建和管理对象,并将对象之间的依赖关系进行注入,从而实现了对象之间的解耦。
工作原理
- Bean 的定义和注册 :在 Spring 中,Bean 是被容器管理的对象。开发者通过配置文件(如 XML)或注解(如
@Component
@Service
等)来定义 Bean,并将其注册到 Spring 容器中。容器会解析这些配置信息,创建相应的 BeanDefinition 对象,用于描述 Bean 的各种属性,如类名、构造函数参数、属性值等。- Bean 的创建和初始化:当 Spring 容器启动时,它会根据 BeanDefinition 创建 Bean 实例。对于单例模式的 Bean,容器在启动时就会创建并初始化它们;对于原型模式的 Bean,在每次请求获取 Bean 时才会创建。在创建 Bean 的过程中,容器会调用 Bean 的构造函数进行实例化,然后根据配置信息设置 Bean 的属性值,并调用 Bean 的初始化方法(如果有指定)。
- 依赖注入 :Spring 容器通过依赖注入来解决 Bean 之间的依赖关系。当一个 Bean 依赖于其他 Bean 时,容器会在创建该 Bean 时,自动将其依赖的 Bean 注入到该 Bean 中。依赖注入的方式有三种:构造函数注入、Setter 方法注入和字段注入。
主要类型
- BeanFactory:是 Spring 容器的最基本接口,提供了基本的 Bean 管理功能,如获取 Bean 实例、检查 Bean 的存在等。它是一个延迟加载的容器,只有在真正需要使用 Bean 时才会创建和初始化。
- ApplicationContext:是 BeanFactory 的子接口,它在 BeanFactory 的基础上增加了许多企业级应用所需的功能,如国际化支持、资源加载、事件发布等。ApplicationContext 在启动时会预先实例化所有的单例 Bean,适用于大多数 Spring 应用场景。
作用
- 解耦对象之间的依赖关系:通过 Spring 容器的依赖注入功能,对象之间的依赖关系由容器来管理,使得对象之间的耦合度大大降低。这样,当一个对象的依赖发生变化时,只需要在容器的配置中进行修改,而不需要在使用该对象的代码中进行大量的修改。
- 提高代码的可维护性和可扩展性:Spring 容器将对象的创建和管理集中到一个地方,使得代码的结构更加清晰,易于维护。同时,当需要增加新的功能或修改现有功能时,可以通过在容器中注册新的 Bean 或修改现有 Bean 的配置来实现,而不需要对大量的业务代码进行修改,提高了代码的可扩展性。
- 方便进行面向切面编程(AOP):Spring 容器与 AOP 框架紧密集成,可以方便地实现对 Bean 的切面编程。通过 AOP,可以在不修改业务逻辑代码的情况下,对 Bean 的方法进行增强,如添加日志记录、事务管理、权限控制等功能。
创建 Spring 容器
在 Spring 中,常见的创建容器的方式有基于
ApplicationContext
和BeanFactory
两种,这里以ApplicationContext
为例,它有多个实现类,下面分别介绍基于 XML 配置和注解配置创建容器的方法。基于 XML 配置创建容器
javaimport org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class XmlBasedContainerExample { public static void main(String[] args) { // 通过加载类路径下的XML配置文件创建ApplicationContext容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 从容器中获取Bean Object myBean = context.getBean("myBean"); System.out.println(myBean); } }
基于注解配置创建容器
javaimport org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = "com.example.demo") class AppConfig { // 配置类 } public class AnnotationBasedContainerExample { public static void main(String[] args) { // 通过注解配置类创建ApplicationContext容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // 从容器中获取Bean Object myBean = context.getBean("myBean"); System.out.println(myBean); } }
Spring 容器类的层次结构
Spring 容器的核心接口是
BeanFactory
和ApplicationContext
,它们的类层次结构大致如下:
BeanFactory
:是 Spring 容器的最基础接口,提供了获取 Bean 的基本方法,如getBean
等。它是一个延迟加载的容器,只有在真正需要使用 Bean 时才会创建和初始化。HierarchicalBeanFactory
:继承自BeanFactory
,增加了对父子容器层次结构的支持,可以获取父容器。ListableBeanFactory
:继承自BeanFactory
,提供了可以枚举容器中所有 Bean 的功能,如获取所有 Bean 的名称、根据类型获取所有 Bean 等。ApplicationContext
:继承自HierarchicalBeanFactory
和ListableBeanFactory
,并扩展了许多企业级应用所需的功能,如国际化支持、资源加载、事件发布等。它在启动时会预先实例化所有的单例 Bean。
ClassPathXmlApplicationContext
:从类路径下的 XML 配置文件中加载 Bean 定义。FileSystemXmlApplicationContext
:从文件系统中的 XML 配置文件中加载 Bean 定义。AnnotationConfigApplicationContext
:基于 Java 注解配置类来加载 Bean 定义。
BeanFactory
BeanFactory
是 Spring 容器的核心接口,它是一个轻量级的容器,主要负责创建、管理和查找 Bean。以下是一个简单的BeanFactory
使用示例:
javaimport org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; // 注意:XmlBeanFactory在Spring 3.1中已被弃用,这里仅作示例 @SuppressWarnings("deprecation") public class BeanFactoryExample { public static void main(String[] args) { // 加载XML配置文件 Resource resource = new ClassPathResource("applicationContext.xml"); // 创建BeanFactory容器 BeanFactory factory = new XmlBeanFactory(resource); // 从容器中获取Bean Object myBean = factory.getBean("myBean"); System.out.println(myBean); } }
在实际开发中,由于
BeanFactory
功能相对基础,大多数情况下会使用功能更强大的ApplicationContext
。不过,了解BeanFactory
有助于理解 Spring 容器的底层实现原理。综上所述,Spring 容器的创建方式多样,类层次结构丰富,
BeanFactory
是其核心基础,不同的容器和接口适用于不同的应用场景。
三、Bean 的概念
在 Spring 中,Bean 指的是由 Spring 容器管理的对象。这些对象的创建、初始化、销毁等生命周期过程都由 Spring 容器负责。Spring 容器会根据配置信息(可以是 XML 配置、注解配置或者 Java 代码配置)来创建和管理 Bean,开发者只需关注业务逻辑的实现,而无需手动管理对象的生命周期。
1. Bean 的配置
在 Spring 里,对 Bean 进行配置主要有三种常见方式,分别是 XML 配置、注解配置和 Java 配置。
XML 配置
借助 XML 文件来定义和配置 Bean,这是早期 Spring 开发常用的手段。以下是一个简单的示例:
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"> <!-- 定义一个名为userService的Bean --> <bean id="userService" class="com.example.service.UserService"> <!-- 注入依赖 --> <property name="userRepository" ref="userRepository"/> </bean> <!-- 定义一个名为userRepository的Bean --> <bean id="userRepository" class="com.example.repository.UserRepository"/> </beans>
注解配置
利用注解在类上直接标记 Bean,能够简化配置流程。常见的注解有
@Component
、@Service
、@Repository
和@Controller
等。
javaimport org.springframework.stereotype.Service; @Service public class UserService { // 业务逻辑代码 }
同时,还需要在配置类里启用组件扫描:
javaimport org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = "com.example") public class AppConfig { // 配置类代码 }
Java 配置
运用 Java 代码来配置 Bean,这种方式更加灵活。
javaimport org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public UserService userService() { return new UserService(); } @Bean public UserRepository userRepository() { return new UserRepository(); } }
2. Bean 的作用范围的配置
Spring 为 Bean 提供了多种作用范围,可通过
scope
属性或者@Scope
注解来进行配置。singleton(单例)
这是 Spring 默认的作用范围
整个应用程序中只会创建一个 Bean 实例。
XML<bean id="userService" class="com.example.service.UserService" scope="singleton"/>
使用注解的方式:
javaimport org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; @Service @Scope("singleton") public class UserService { // 业务逻辑代码 }
prototype(原型)
每次请求该 Bean 时,都会创建一个新的实例。
XML<bean id="userService" class="com.example.service.UserService" scope="prototype"/>
注解方式:
javaimport org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; @Service @Scope("prototype") public class UserService { // 业务逻辑代码 }
request、session、application(仅适用于 Web 应用)
request
:每个 HTTP 请求都会创建一个新的 Bean 实例。session
:每个 HTTP 会话会创建一个新的 Bean 实例。application
:整个 Web 应用程序中只会创建一个 Bean 实例。3. Bean 的实例化(创建)
方式一:构造器实例化
Spring 默认会通过 Bean 类的无参构造器来创建实例。
javapublic class UserService { public UserService() { // 构造器代码 } }
方式二:静态工厂
使用静态工厂方法来创建 Bean 实例。
javapublic class UserServiceFactory { public static UserService createUserService() { return new UserService(); } }
XML 配置如下:
XML<bean id="userService" class="com.example.factory.UserServiceFactory" factory-method="createUserService"/>
方式三:实例工厂
先创建工厂类的实例,再通过该实例的方法创建 Bean。
javapublic class UserServiceInstanceFactory { public UserService createUserService() { return new UserService(); } }
XML 配置:
XML<bean id="userServiceFactory" class="com.example.factory.UserServiceInstanceFactory"/> <bean id="userService" factory-bean="userServiceFactory" factory-method="createUserService"/>
方式四:方式三的优化(重点)
使用
@Bean
注解在配置类中创建 Bean,这种方式结合了 Java 配置的灵活性。
javaimport org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AppConfig { @Bean public UserServiceInstanceFactory userServiceInstanceFactory() { return new UserServiceInstanceFactory(); } @Bean public UserService userService() { return userServiceInstanceFactory().createUserService(); } }
4. Bean 的生命周期(从创建到销毁)
Bean 的生命周期包含实例化、属性赋值、初始化、使用和销毁这几个阶段。
初始化方法
可以通过实现
InitializingBean
接口的afterPropertiesSet
方法、使用**@PostConstruct
**注解或者在 XML 中配置init-method
属性来指定初始化方法。
javaimport javax.annotation.PostConstruct; public class UserService { @PostConstruct public void init() { // 初始化代码 } }
销毁方法
可通过实现
DisposableBean
接口的destroy
方法、使用@PreDestroy
注解或者在 XML 中配置destroy-method
属性来指定销毁方法。解决 Destroy 方法未执行的问题
如果
Destroy
方法没有执行,通常是因为 JVM 虚拟机在执行该方法前关闭了。可以通过以下两种方式解决:
- 修改
ApplicationContext
接口,并调用close()
方法关闭容器:
javaimport org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); // 使用Bean ctx.close(); // 关闭容器,触发销毁方法 } }
- 使用
registerShutdownHook()
方法:
javaimport org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); ctx.registerShutdownHook(); // 注册关闭钩子,JVM关闭时触发销毁方法 // 使用Bean } }
在实际开发的 Web 应用中,通常不需要手动编写这些代码,因为随着 Tomcat 等服务器的关闭,Spring 容器会自动关闭,从而触发 Bean 的销毁方法。
综上所述,理解 Bean 的配置、作用范围、实例化方式以及生命周期,对于使用 Spring 框架进行开发至关重要。
四、IOC的实现方式-依赖注入(DI, Dependency Injection)
DI(Dependency Injection,依赖注入)是实现 IOC(控制反转)的关键技术,它描述了容器在创建对象时,将对象所依赖的其他对象传递给该对象的过程。以下结合你提供的图片内容进行详细讲解:
依赖注入方式
setter 注入
通过对象的 setter 方法来注入依赖。它允许在对象创建后再设置其依赖关系,较为灵活。
- 简单类型:对于像基本数据类型(如 int、String 等 ),可以在配置文件中直接指定值。例如在 XML 配置里:
XML<bean id="user" class="com.example.User"> <property name="name" value="张三"/> </bean>
这里通过
property
标签,利用User
类的setName
方法将字符串 "张三" 注入。
- 引用类型 :针对对象类型的依赖,比如一个
UserService
依赖UserRepository
,配置如下:
XML<bean id="userRepository" class="com.example.UserRepository"/> <bean id="userService" class="com.example.UserService"> <property name="userRepository" ref="userRepository"/> </bean>
上述配置会调用
UserService
的setUserRepository
方法,将userRepository
实例注入。构造器注入
在对象创建时,通过构造函数来注入依赖。构造器注入能确保依赖在对象创建时就已存在且不可变,增强了对象的稳定性。
- 简单类型:若一个类的构造函数接收基本数据类型参数,在 XML 中配置如下:
XML<bean id="order" class="com.example.Order"> <constructor - arg value="100"/> </bean>
假设
Order
类有个构造函数public Order(int amount)
,这里就会通过该构造函数将值100
注入。
- 引用类型 :当类依赖其他对象时,例如
ProductService
依赖ProductRepository
,且通过构造函数注入:
XML<bean id="productRepository" class="com.example.ProductRepository"/> <bean id="productService" class="com.example.ProductService"> <constructor - arg ref="productRepository"/> </bean>
即调用
ProductService
合适的构造函数(如public ProductService(ProductRepository productRepository)
) 进行注入。依赖注入方式选择
- 建议使用 setter 注入:因为它的灵活性高,对象创建后还能修改依赖关系,适用于依赖关系多变的场景。比如某些配置信息可能在运行时动态改变,使用 setter 注入就可以方便地重新设置。
- 第三方技术根据情况选择 :对于一些第三方类库,如果其构造函数已经被设计用于特定的初始化逻辑,可能构造器注入更合适;若第三方类库后续需要动态调整依赖,setter 注入会是更好的选择。
依赖自动装配
这是 IoC 容器自动处理依赖注入的机制,容器会根据 bean 所依赖的资源在容器中自动查找并注入到 bean 中。
- 按类型(常用) :容器在查找依赖时,会依据依赖对象的类型在容器中寻找匹配的 Bean 进行注入。例如一个类依赖
UserRepository
,容器会查找类型为UserRepository
的 Bean 注入。如果容器中存在多个同类型的 Bean ,会出现歧义,需要额外处理(比如结合@Primary
注解指定首选 Bean )。- 按名称 :根据依赖对象的名称来查找匹配的 Bean 注入。在 XML 配置中可通过
autowire - by - name
开启,在注解方式中,@Resource
注解默认按名称装配(若未指定名称则按类型 )。- 按构造方法:容器会查找与目标类构造函数参数类型匹配的 Bean 进行注入,主要用于构造器注入场景下的自动装配。
- 不启用自动装配:即不使用自动装配功能,开发者需完全显式地在配置文件或代码中指定依赖注入关系。
加载 properties 文件
在实际开发中,常需要从外部配置文件(
.properties
文件 )读取配置信息并注入到 Bean 中,具体操作如下:
- 开启 context 命名空间:在 XML 配置文件中,引入 Spring 的
context
命名空间,以便使用相关标签。示例如下:
XML<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
- 使用 context 命名空间,加载指定 properties 文件 :通过
<context:property - placeholder>
标签加载指定的.properties
文件,例如:
XML<context:property - placeholder location="jdbc.properties"/>
这会加载类路径下名为
jdbc.properties
的文件。
- ** 使用\({}读取加载的属性值**:在配置 Bean 属性值时,可通过 `\){属性名}` 的方式读取已加载文件中的属性值,如:
XML<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> </bean>
此外,还可以通过不同配置实现不加载系统属性(
system - properties - mode="NEVER"
)、加载多个.properties
文件(以逗号分隔文件路径 )、加载所有.properties
文件(使用通配符 )等功能。
五、配置方式对比
1. XML 配置(传统)
XML<bean id="userService" class="com.example.UserService"> <property name="userDao" ref="userDao"/> </bean>
运行 HTML
- 缺点:冗长,类型不安全,维护成本高。
2. 注解配置(现代 Spring 主流)
通过注解简化配置,结合组件扫描实现自动化管理。
六、注解配置详解(重点)
Spring 框架里基于注解的配置,涵盖注解开发定义 Bean、Bean 管理、依赖注入、加载
properties
文件以及管理第三方 Bean 等方面。1. 注解开发定义 Bean
在 Spring 里,
@Component
注解可把一个类标记成 Spring Bean,让 Spring 容器能够对其进行管理。此外,Spring 还提供了功能和
@Component
一样的三个注解,分别是@Repository
、@Service
和@Controller
。这些注解在语义上有所区别,分别用于不同的层:
@Repository
:一般用在数据访问层(DAO 层),代表这是一个数据仓库类。@Service
:常用于业务逻辑层(Service 层),代表这是一个业务服务类。@Controller
:一般用于表现层(Controller 层),代表这是一个控制器类。下面是示例代码:
javaimport org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import org.springframework.stereotype.Controller; // 数据访问层 @Repository public class UserDao { public void saveUser() { System.out.println("Save user to database"); } } // 业务逻辑层 @Service public class UserService { private UserDao userDao; public UserService(UserDao userDao) { this.userDao = userDao; } public void addUser() { userDao.saveUser(); } } // 表现层 @Controller public class UserController { private UserService userService; public UserController(UserService userService) { this.userService = userService; } public void handleUserRequest() { userService.addUser(); } }
2. 注解开发 - Bean 管理
依赖注入
依赖注入指的是把一个 Bean 注入到另一个 Bean 里。在 Spring 中,可使用
@Autowired
注解来达成依赖注入。@Autowired
注解能用在构造函数、setter 方法或者字段上。
javaimport org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserService { private UserDao userDao; @Autowired public UserService(UserDao userDao) { this.userDao = userDao; } public void addUser() { userDao.saveUser(); } }
加载 properties 文件
要加载
properties
文件,可使用@PropertySource
注解和@Value
注解。@PropertySource
注解用于指定properties
文件的位置,@Value
注解用于注入properties
文件里的属性值。
javaimport org.springframework.context.annotation.PropertySource; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component @PropertySource("classpath:config.properties") public class AppConfig { @Value("${database.url}") private String databaseUrl; @Value("${database.username}") private String databaseUsername; @Value("${database.password}") private String databasePassword; public String getDatabaseUrl() { return databaseUrl; } public String getDatabaseUsername() { return databaseUsername; } public String getDatabasePassword() { return databasePassword; } }
管理第三方 Bean
对于第三方库的 Bean,你可以编写一个方法来获取第三方 Bean,并且用
@Bean
注解把返回值设置成 Bean。
javaimport org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.fasterxml.jackson.databind.ObjectMapper; @Configuration public class AppConfig { @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); } }
3. 纯注解开发配置类
要实现纯注解开发,你需要创建一个配置类,用
@Configuration
注解标记该类,同时用@ComponentScan
注解开启组件扫描。
javaimport org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = "com.example.demo") public class AppConfig { // 配置类中的其他配置 }
4. 测试代码
以下是一个简单的测试代码,展示如何使用上述配置:
javaimport org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); UserController userController = context.getBean(UserController.class); userController.handleUserRequest(); AppConfig appConfig = context.getBean(AppConfig.class); System.out.println("Database URL: " + appConfig.getDatabaseUrl()); ObjectMapper objectMapper = context.getBean(ObjectMapper.class); System.out.println("ObjectMapper: " + objectMapper); context.close(); } }
小结
- 运用
@Component
、@Repository
、@Service
和@Controller
注解定义 Bean。- 借助
@Autowired
注解实现依赖注入。- 利用
@PropertySource
和@Value
注解加载properties
文件。- 使用
@Bean
注解管理第三方 Bean。- 采用
@Configuration
和@ComponentScan
注解实现纯注解开发。注解配置 vs XML 配置
特性 注解配置 XML 配置 可读性 代码内嵌,直观 外部文件,需上下文切换 灵活性 强类型,编译时检查 字符串配置,易出错 维护性 修改代码需重新编译 动态修改无需编译 适用场景 现代 Spring Boot 项目 遗留系统或复杂配置
总结
Spring 的 IOC 容器 通过注解配置(如 @Component
, @Autowired
, @Configuration
)实现了轻量级、高内聚的依赖管理。注解配置已成为现代 Spring 开发的主流方式,兼顾灵活性和代码简洁性,是 Spring Boot 自动配置的基石。