在 Spring Boot 中,手动装配 Bean有哪些方式
my:@Bean /扫描 /Import /xml
在 Spring Boot 中,手动装配 Bean 是指显式地将类注册到 Spring 容器中,而不是依赖于自动扫描和注解。
以下是几种常见的手动装配方式:
1. 使用 @Bean
注解
通过在配置类中定义方法,将返回的对象注册为 Spring 容器中的 Bean。
实现步骤
- 创建一个配置类,使用
@Configuration
注解。 - 在方法上使用
@Bean
注解,将方法返回值注册为 Bean。
代码示例
@Configuration public class MyConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }
- 优点:简单直接,适用于单个或少量 Bean 的手动注册。
- 场景:需要对 Bean 的实例化过程进行控制或设置属性。
2. 使用 @ComponentScan
配置自定义扫描路径
通过指定扫描路径或排除特定包/类来手动控制哪些类需要注册到 Spring 容器。
实现步骤
- 使用
@ComponentScan
注解配置扫描路径。 - 配合
@Component
、@Service
等注解自动注册。
代码示例
@SpringBootApplication @ComponentScan(basePackages = "com.example.custom") public class MyApplication { }
- 优点:灵活性高,适合需要动态调整扫描路径的场景。
- 场景:自定义模块化项目时,按需加载不同模块的 Bean。
3. 使用 @Import
注解
通过显式地导入其他配置类或直接导入组件类,将其注册到容器中。
实现步骤
- 在主配置类中使用
@Import
注解导入类。 - 导入的类可以是配置类或普通类。
代码示例
@Configuration @Import({MyService.class, MyRepository.class}) public class MyConfig { }
- 优点:适用于需要显式注册多个相关类时。
- 场景:模块化开发或导入外部库的配置。
4. 使用 SpringApplication
的 API
通过 Spring Boot 提供的 SpringApplication
类,手动注册 Bean。
实现步骤
- 定义自定义 Bean。
- 在启动类中手动注册 Bean。
代码示例
public static void main(String[] args) { SpringApplication app = new SpringApplication(MyApplication.class); app.addInitializers((applicationContext) -> { applicationContext.getBeanFactory().registerSingleton("myBean", new MyServiceImpl()); }); app.run(args); }
- 优点:高度动态化,适合在运行时决定注册哪些 Bean。
- 场景:需要动态调整注册逻辑。
5. 使用 BeanDefinitionRegistry
通过手动操作 BeanDefinitionRegistry
,动态地向容器注册 Bean。
实现步骤
- 实现
BeanDefinitionRegistryPostProcessor
接口。 - 在
postProcessBeanDefinitionRegistry
方法中注册 Bean。
代码示例
@Component public class MyBeanRegistrar implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyServiceImpl.class); registry.registerBeanDefinition("myService", builder.getBeanDefinition()); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { } }
- 优点:极大的灵活性,可动态配置。
- 场景:框架开发或需要动态注册 Bean 的复杂场景。
6. 使用 XML 配置
尽管在 Spring Boot 中 XML 配置已不常用,但仍可以通过引入 XML 文件来注册 Bean。
实现步骤
- 创建一个 XML 配置文件,定义 Bean。
- 在主配置类中通过
@ImportResource
导入 XML。
代码示例
<!-- applicationContext.xml --> <beans xmlns="http://www.springframework.org/schema/beans"> <bean id="myService" class="com.example.MyServiceImpl" /> </beans>
@Configuration @ImportResource("classpath:applicationContext.xml") public class MyConfig { }
- 优点:适合遗留项目中整合 XML 配置。
- 场景:迁移旧项目时保持兼容性。
Spring 提供了多种方式来管理事务
Spring 提供了多种方式来管理事务,主要包括以下几种方式:
1. 编程式事务管理(Programmatic Transaction Management)
-
概述:通过编程的方式在代码中显式地控制事务的开始、提交和回滚。这种方式需要手动管理事务,通常通过
TransactionTemplate
或PlatformTransactionManager
来实现。 -
优点:可以灵活控制事务的细节,适用于一些复杂的事务处理逻辑。
-
缺点:代码耦合度较高,事务的管理较为繁琐,容易出错,通常不推荐使用。
-
示例:
@Autowired private PlatformTransactionManager transactionManager; public void someBusinessMethod() { DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setName("SomeTransaction"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = transactionManager.getTransaction(def); try { // 执行数据库操作 transactionManager.commit(status); } catch (RuntimeException e) { transactionManager.rollback(status); throw e; } }
2. 声明式事务管理(Declarative Transaction Management)
-
概述:通过配置和注解的方式来管理事务,不需要手动管理事务的开启和提交,Spring 会根据配置自动处理事务。常用的方式是使用
@Transactional
注解和 AOP(面向切面编程)来实现。 -
优点:简洁且易于维护,事务管理与业务逻辑分离,减少了开发者的工作量。
-
缺点:灵活性较差,不能处理一些非常复杂的事务需求。
-
示例:
-
使用注解:
@Transactional public void someBusinessMethod() { // 执行数据库操作,事务自动管理 }
-
使用 XML 配置:
<tx:annotation-driven /> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
-
3. 基于 AOP 的事务管理(AOP-based Transaction Management)
- 概述:基于 AOP(面向切面编程),Spring 使用切面技术来拦截方法调用并在方法执行之前和之后处理事务。声明式事务管理就是通过 AOP 技术来实现的。
- 优点:事务管理与业务代码分离,代码简洁,适合用于简单的事务管理。
- 缺点:复杂的事务逻辑可能不适合使用 AOP,需要额外的配置和理解 AOP 的原理。
- 示例:上面提到的
@Transactional
注解和 AOP 结合使用,Spring 会在运行时自动生成代理类来拦截方法并管理事务。
4. XML 配置的事务管理(XML-based Transaction Management)
- 概述:通过在 XML 配置文件中配置事务管理器和事务的细节来管理事务。这种方式较为传统,通常与声明式事务管理结合使用。
- 优点:可以集中配置事务管理,灵活性较高。
- 缺点:配置较为繁琐,且不如注解方式简洁。
- 示例:
<tx:annotation-driven /> <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
Spring 中的自动装配方式
Spring 中的自动装配是指 Spring 容器自动将需要的依赖注入到 Bean 中,从而减少了开发人员手动配置和创建 Bean 的工作。Spring 提供了多种自动装配的方式,主要通过注解和配置来实现。下面是几种常见的自动装配模式:
1. @Autowired 注解
@Autowired
是 Spring 用来自动装配 Bean 的最常用注解。它可以作用于构造器、字段、方法等,通过根据类型自动匹配依赖的 Bean。
1.1 按类型自动装配
Spring 默认会根据 Bean 的类型来匹配依赖的对象,如果有多个相同类型的 Bean,会抛出异常,除非指定了 @Qualifier
注解来指定具体的 Bean。
@Autowired private MyService myService; // 按类型自动装配
1.2 按构造函数自动装配
@Autowired
也可以加在构造函数上,Spring 会根据构造函数参数的类型自动装配 Bean。
@Autowired public MyController(MyService myService) { this.myService = myService; }
1.3 按方法自动装配
@Autowired
还可以应用于普通的 setter 方法上,Spring 会调用这些 setter 方法进行自动装配。
@Autowired public void setMyService(MyService myService) { this.myService = myService; }
2. @Qualifier 注解
当容器中有多个相同类型的 Bean 时,Spring 会根据类型进行自动装配,但如果有多个匹配的 Bean,就会抛出异常。在这种情况下,可以使用 @Qualifier
来指定要注入的具体 Bean。
@Autowired @Qualifier("myServiceImpl") private MyService myService;
这里 @Qualifier("myServiceImpl")
指定了具体的 Bean 名称,Spring 会根据该名称来选择注入的 Bean。
3. @Primary 注解
@Primary
是一个用于解决 Bean 冲突的注解。当多个相同类型的 Bean 存在时,可以通过 @Primary
注解标记一个 Bean,表示当有多个符合条件的 Bean 时,优先选择标记了 @Primary
的 Bean。
@Bean @Primary public MyService myPrimaryService() { return new MyServiceImpl(); }
使用 @Primary
标记的 Bean 会成为优先选择的 Bean,从而解决多个同类型 Bean 时的冲突问题。
4. @Value 注解
@Value
用于注入基本类型、配置文件中的值等。它允许将外部配置值(如 application.properties
文件中的属性)直接注入到 Bean 中。
@Value("${my.property.name}") private String propertyName;
@Value
也可以用于注入常量或表达式计算结果。
5. @Inject 注解
@Inject
是 JSR-330 标准的注解,功能与 @Autowired
类似。它由 Java EE 和 Spring 支持,但它没有 @Qualifier
和 @Primary
这样的配合注解,所以通常不如 @Autowired
强大。
@Inject private MyService myService;
6. 自动装配的启用方式
自动装配通常通过 @Configuration
和 @ComponentScan
配合使用,Spring 会自动扫描指定包中的所有类,并将它们作为 Bean 注入到容器中。
@Configuration @ComponentScan(basePackages = "com.example.service") public class AppConfig { // 配置类会自动扫描 com.example.service 包中的所有类并将其注入容器 }
7. @Component, @Service, @Repository, @Controller 等注解
这些注解是 Spring 提供的用于标记类的注解,它们都可以作为自动装配的候选类。它们的作用类似,区别在于它们的语义:
- @Component:一般用来标记普通的 Bean。
- @Service:通常用来标记服务层 Bean。
- @Repository:通常用来标记 DAO 层 Bean。
- @Controller:通常用来标记 Spring MVC 控制器类。
8. 自动装配的工作原理
Spring 容器使用反射机制和 BeanFactory 的 postProcessBeforeInitialization
等钩子方法来进行自动装配。在应用启动时,Spring 会通过以下过程来完成自动装配:
- 扫描 Bean 定义:通过
@ComponentScan
或配置类扫描容器中的所有 Bean 定义。 - 创建 Bean 实例:根据定义的 Bean 创建对应的实例。
- 自动装配依赖关系:通过
@Autowired
等注解将 Bean 的依赖关系自动注入,通常是通过类型匹配来实现的。
在 Spring 事务管理中,事务传播行为
在 Spring 事务管理中,事务传播行为(Transaction Propagation)定义了一个事务方法如何参与另一个事务的方法。
Spring 提供了以下几种常用的事务传播行为:
1. **PROPAGATION_REQUIRED
**(默认传播行为)
- 说明:如果当前存在事务,则加入该事务;如果当前没有事务,则新建一个事务。
- 场景:这是最常见的传播行为,适用于大多数情况,尤其是当方法依赖于已有事务时。
- 特性:如果没有事务,则会创建一个新的事务;如果已有事务,则加入该事务。
2. PROPAGATION_REQUIRES_NEW
- 说明:无论当前是否存在事务,都会新建一个事务。如果当前存在事务,则会把当前事务挂起,直到新的事务完成后再恢复。
- 场景:适用于需要独立事务的情况,确保事务不受外部事务的影响。常用于日志记录、支付等操作,独立处理时不想受父事务回滚的影响。
- 特性:会挂起当前事务,启动新事务,执行完后再恢复原事务。
3. PROPAGATION_NESTED
- 说明:如果当前存在事务,则在当前事务中创建一个嵌套事务;如果没有事务,则行为与
PROPAGATION_REQUIRED
相同。嵌套事务使用保存点(Savepoint),可以回滚到保存点。 - 场景:适用于需要在一个大事务中处理子事务,并且希望子事务能够回滚但不影响整个事务的情况。
- 特性:使用保存点管理事务,支持嵌套回滚。
4. PROPAGATION_SUPPORTS
- 说明:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
- 场景:适用于一些业务逻辑可能不需要事务控制,但如果已有事务时则需要加入该事务的场景。
- 特性:当前事务存在时,加入该事务;否则以非事务方式执行。
5. PROPAGATION_NOT_SUPPORTED
- 说明:如果当前存在事务,则将其挂起,并以非事务方式执行当前方法。
- 场景:适用于需要执行一些不支持事务的操作时,如导出文件等,当前事务不应影响这些操作。
- 特性:挂起当前事务,以非事务方式执行。
6. PROPAGATION_NEVER
- 说明:当前方法不能在事务中执行。如果当前存在事务,则抛出异常。
- 场景:适用于某些方法不能参与事务,且如果事务存在则抛出异常的情况。
- 特性:如果当前已有事务,则抛出异常;如果没有事务,则正常执行。
7. PROPAGATION_MANDATORY
- 说明:当前方法必须在一个事务中执行,如果当前没有事务,则抛出异常。
- 场景:适用于必须要求事务存在的情况,确保方法执行时有一个事务。
- 特性:如果当前没有事务,则抛出异常;如果当前有事务,则加入该事务。
事务传播行为对比:
传播行为 | 当前有事务时 | 当前没有事务时 | 适用场景 |
---|---|---|---|
PROPAGATION_REQUIRED | 加入当前事务 | 新建事务 | 默认行为,大多数场景 |
PROPAGATION_REQUIRES_NEW | 挂起当前事务,开启新事务 | 新建事务 | 需要独立事务的情况 |
PROPAGATION_NESTED | 在当前事务中创建嵌套事务 | 新建事务 | 嵌套事务,允许回滚 |
PROPAGATION_SUPPORTS | 加入当前事务 | 非事务方式执行 | 可能没有事务,加入已有事务 |
PROPAGATION_NOT_SUPPORTED | 挂起当前事务,非事务方式执行 | 非事务方式执行 | 不支持事务的操作 |
PROPAGATION_NEVER | 抛出异常 | 非事务方式执行 | 不允许事务 |
PROPAGATION_MANDATORY | 加入当前事务 | 抛出异常 | 必须在事务中执行 |
Spring 框架中常用的设计模式
my:单例 / 工厂 / AOP 代理 ,策略 / restTemplate 模版
包装 inputStream / 观察者 ApplicationListner / 适配器 /
Spring 框架是一个大型的应用程序开发框架,广泛应用了多种设计模式,以实现松耦合、高内聚和可扩展性。
以下是 Spring 框架中常用的一些设计模式:
1. 单例模式(Singleton Pattern)
- 用途:保证整个 Spring 容器中只创建一个实例。
- 应用:在 Spring 中,默认情况下,Bean 的作用域是单例的(除非显式指定为其他作用域)。这意味着每个 Bean 在容器中只会创建一个实例,所有请求都会共享这个实例。
- 示例:
@Component
、@Service
、@Repository
等注解创建的 Bean 默认使用单例模式。
2. 工厂模式(Factory Pattern)
- 用途:提供一个创建对象的接口,使得子类决定实例化哪个类。
- 应用:Spring 使用工厂模式来实例化 Bean。Spring 的
BeanFactory
和ApplicationContext
都是工厂类,用于从容器中创建和管理 Bean。 - 示例:
BeanFactory
和AnnotationConfigApplicationContext
。
3. 依赖注入(Dependency Injection,DI)
- 用途:通过将对象的依赖关系注入到类中,从而解耦类与类之间的依赖。
- 应用:Spring 的核心就是实现了依赖注入,提供了多种方式来注入依赖(构造函数注入、Setter 注入、字段注入)。
- 示例:
@Autowired
注解、构造器注入。
4. 代理模式(Proxy Pattern)
- 用途:为对象提供一个代理对象,控制对真实对象的访问。
- 应用:Spring AOP 使用代理模式为对象提供切面功能。通过代理模式,Spring 在方法执行前后插入切面逻辑,而不需要修改原始对象代码。
- 示例:Spring AOP 默认基于 JDK 动态代理或 CGLIB 代理。
5. 观察者模式(Observer Pattern)
- 用途:一种对象状态改变时,依赖它的所有对象都会得到通知并自动更新。
- 应用:Spring 的事件机制就是基于观察者模式实现的。
ApplicationEventPublisher
用于发布事件,ApplicationListener
用于监听事件。 - 示例:
@EventListener
注解用于监听 Spring 事件。
6. 模板方法模式(Template Method Pattern)
- 用途:定义一个操作中的算法的骨架,将一些步骤的执行延迟到子类中。
- 应用:Spring 提供了多种模板方法,如
JdbcTemplate
、JpaTemplate
等,用于简化常见的数据库操作。这些模板类会定义操作的基本步骤,而具体的操作逻辑由子类或调用者提供。 - 示例:
JdbcTemplate
用于数据库操作。
7. 适配器模式(Adapter Pattern)
- 用途:将一个类的接口转换成客户端所期待的另一个接口。
- 应用:Spring 的
HandlerAdapter
适配器模式将 HTTP 请求与处理请求的 Controller 之间的接口进行了适配,以支持多种类型的处理器(如SimpleControllerHandlerAdapter
和AnnotationMethodHandlerAdapter
)。 - 示例:
HandlerAdapter
和HandlerMapping
。
8. 责任链模式(Chain of Responsibility Pattern)
- 用途:通过多个处理对象的链式传递来处理请求,每个处理对象可以决定是否处理请求,或者将请求传递给下一个处理对象。
- 应用:Spring 中的 AOP 框架通过责任链模式来处理方法拦截。多个切面(Advice)可以按照顺序处理方法的执行。
- 示例:多个
Advice
组成责任链,在方法执行前后执行额外的逻辑。
9. 构建者模式(Builder Pattern)
- 用途:使用多个简单的对象一步一步构建成一个复杂的对象。
- 应用:Spring 在配置 Bean 时,尤其是复杂的 Bean 配置,使用了构建者模式。
@Configuration
注解的类往往是构建复杂 Bean 对象的地方。 - 示例:
@Bean
方法中的 Bean 构建过程,ApplicationContext
的配置。
10. 策略模式(Strategy Pattern)
- 用途:定义一系列算法,把它们一个个封装起来,并使它们可以相互替换。
- 应用:Spring 通过策略模式使得不同的 Bean 配置方式能够相互替换,例如不同的
MessageSource
实现(ReloadableResourceBundleMessageSource
和ResourceBundleMessageSource
)都可以通过同一个接口MessageSource
被使用。 - 示例:Spring 配置中的不同 Bean 加载策略。
11. 迭代器模式(Iterator Pattern)
- 用途:提供一种方法来顺序访问一个集合对象中的元素,而不暴露集合的内部表示。
- 应用:Spring 的
Iterable
接口和for-each
循环就是基于迭代器模式的。通过Iterable
,我们可以在不暴露底层数据结构的情况下遍历集合。 - 示例:Spring 中的
Iterable
,如ApplicationContext
中的BeanDefinition
集合。
12. 装饰器模式(Decorator Pattern)
- 用途:动态地给一个对象添加一些额外的职责,而不影响其他对象。
- 应用:Spring 使用装饰器模式来增强 Bean 的功能。例如,使用
ProxyFactoryBean
可以对 Bean 进行代理增强。 - 示例:Spring AOP 中的代理机制,通过代理增强 Bean。
Spring MVC 的工作原理
Spring MVC 的工作原理可以分为以下几个主要步骤:
-
客户端发送请求:
- 客户端(通常是浏览器)通过 HTTP 向服务器发送请求。请求可以是 GET、POST、PUT、DELETE 等类型。
-
请求到达 DispatcherServlet:
- Spring MVC 的核心组件是
DispatcherServlet
,它是前端控制器(Front Controller)。所有的 HTTP 请求都会首先到达DispatcherServlet
。这个 Servlet 是 Spring MVC 请求处理的起点,负责将请求分发到适当的处理器(Controller)。
- Spring MVC 的核心组件是
-
请求映射到 Controller:
DispatcherServlet
根据请求的 URL 和配置的映射关系(如注解@RequestMapping
或 XML 配置)将请求分发到对应的 Controller。Controller 是处理请求的核心组件,它包含了业务逻辑。
-
Controller 处理请求:
- Controller 从
HttpServletRequest
中提取请求信息(如参数、表单数据等),并调用相应的业务逻辑。处理完毕后,Controller 会返回一个 ModelAndView 对象,或直接返回数据。ModelAndView
包含了模型数据和视图名称。
- Controller 从
-
视图解析:
DispatcherServlet
接收到ModelAndView
后,会根据返回的视图名称,结合配置的视图解析器(如InternalResourceViewResolver
),解析出对应的 视图(如 JSP 文件、Thymeleaf 模板等)。
-
渲染视图:
- 视图解析器将模型数据与视图结合,最终渲染成 HTML 页面返回给客户端。渲染过程中,模型数据可以通过模板引擎填充到视图模板中。
-
客户端接收响应:
- 客户端接收到服务器返回的响应数据后(通常是 HTML 页面或 JSON 格式的数据),并呈现给用户。
Spring MVC 的请求处理流程图
Client Request ---> DispatcherServlet ---> Handler Mapping ---> Controller ---> Model ---> View Resolver ---> View ---> Response to Client
详细工作流程
-
客户端请求:
- 用户通过浏览器向服务器发送请求,URL 可能包含路径、查询参数等。
-
DispatcherServlet:
DispatcherServlet
捕获所有的请求,它是请求处理的中央调度器。它会根据请求 URL 找到对应的处理方法。
-
Handler Mapping:
DispatcherServlet
会调用 HandlerMapping 组件来查找与请求 URL 匹配的 Controller 方法。Spring MVC 提供了多种类型的 HandlerMapping,如基于注解的RequestMappingHandlerMapping
,通过注解配置的 URL 映射到对应的 Controller 方法。
-
Controller:
DispatcherServlet
根据 HandlerMapping 返回的控制器,将请求交给 Controller 来处理。Controller 是业务逻辑处理的核心组件,负责解析请求数据,调用服务层进行逻辑处理,并返回模型数据。
-
ModelAndView:
- Controller 处理完请求后,将业务数据和视图名称封装成
ModelAndView
对象,返回给DispatcherServlet
。其中Model
包含了数据,View
是视图的逻辑名称。
- Controller 处理完请求后,将业务数据和视图名称封装成
-
视图解析:
DispatcherServlet
调用 ViewResolver 组件根据返回的视图名称解析出具体的视图(如 JSP 页面或模板文件)。
-
视图渲染:
- 视图解析器加载视图并将模型数据渲染到视图中,最终生成 HTML 页面返回给客户端。
-
客户端响应:
- 客户端收到 HTML 响应后,浏览器将其渲染出来,展示给用户。
核心组件解析
-
DispatcherServlet:
- Spring MVC 的核心控制器,所有的请求都由它来接收、分发、处理。
-
HandlerMapping:
- 负责根据请求的 URL 查找对应的处理器(Controller)。常用的有
RequestMappingHandlerMapping
(基于注解)和BeanNameUrlHandlerMapping
(基于 Bean 名称)。
- 负责根据请求的 URL 查找对应的处理器(Controller)。常用的有
-
Controller:
- 处理请求的核心组件,包含业务逻辑。
-
ViewResolver:
- 负责将逻辑视图名称解析为具体的视图实现(如 JSP 或 Thymeleaf 模板)。
-
ModelAndView:
- 是一个对象,封装了模型数据和视图信息。模型包含了要展示给用户的数据,视图包含了如何展示这些数据的模板信息。
Spring 中的 Bean 生命周期
Spring 中的 Bean 生命周期是 Spring 容器管理 Bean 实例化、配置、初始化、销毁等过程的各个阶段。了解 Bean 的生命周期有助于更好地管理 Bean 的状态、资源的释放以及在应用中进行扩展。
Spring Bean 生命周期概述
Spring Bean 的生命周期包括以下几个主要阶段:
-
实例化:Spring 容器根据 Bean 定义(如
@Component
注解或 XML 配置)创建 Bean 实例。 -
属性填充:Spring 容器会根据配置文件或注解中的信息,将依赖注入到 Bean 的属性中。这一步通过反射完成,类似于构造方法注入或字段注入。
-
调用初始化方法:
- 如果 Bean 实现了
InitializingBean
接口,则 Spring 会调用afterPropertiesSet()
方法。 - 如果 Bean 配置了自定义的初始化方法(例如在 XML 配置文件中的
init-method
属性或通过注解@PostConstruct
),Spring 会在这个阶段调用这些方法。
- 如果 Bean 实现了
-
使用 Bean:在这一步,Bean 被 Spring 容器完全初始化并可以使用。
-
销毁:
- 如果 Bean 实现了
DisposableBean
接口,则 Spring 会调用destroy()
方法。 - 如果 Bean 配置了自定义的销毁方法(例如在 XML 配置文件中的
destroy-method
属性或通过注解@PreDestroy
),Spring 会在容器关闭时调用这些方法。
- 如果 Bean 实现了
Spring Bean 生命周期的详细步骤
-
实例化:
- Spring 根据配置的元数据(如 XML 文件、注解、Java 配置类等)创建 Bean 实例。实例化时,Spring 会使用无参构造方法或通过构造器注入的方式。
-
填充属性:
- Spring 使用反射机制,通过构造方法注入(constructor injection)或通过 setter 方法注入(setter injection)等方式,将依赖的属性或 Bean 填充到该 Bean 中。
-
调用初始化方法:
InitializingBean
接口:如果 Bean 实现了InitializingBean
接口,Spring 会调用其afterPropertiesSet()
方法。- 自定义初始化方法:如果在配置文件(XML)中通过
init-method
属性指定了初始化方法,或者在 Java 配置中使用了@Bean(initMethod = "methodName")
注解指定了初始化方法,Spring 会调用该方法。 @PostConstruct
注解:在 Java 配置类中,Bean 上可以标注@PostConstruct
注解的方法,在所有属性填充完成后会被调用。
-
容器管理下的使用:
- 此时,Bean 已经完全初始化并可以由应用程序使用。容器管理的 Bean 可以通过
ApplicationContext.getBean()
方法获取,或者在需要的地方注入。
- 此时,Bean 已经完全初始化并可以由应用程序使用。容器管理的 Bean 可以通过
-
销毁 Bean:
DisposableBean
接口:如果 Bean 实现了DisposableBean
接口,Spring 容器在销毁该 Bean 时会调用其destroy()
方法。- 自定义销毁方法:如果配置了自定义销毁方法(如在 XML 配置文件中通过
destroy-method
属性指定,或者在 Java 配置类中通过@Bean(destroyMethod = "methodName")
注解指定),Spring 会在容器关闭时调用该方法。 @PreDestroy
注解:在 Java 配置类中,Bean 上可以标注@PreDestroy
注解的方法,在销毁 Bean 时调用。
Spring Bean 生命周期的图示
+--------------------+ +------------------------+ | | | | | Bean实例化 | | 填充Bean属性 | | |------------> Bean依赖注入(setter、构造方法) | | | | | +--------------------+ +------------------------+ | | v +---------------------------+ +--------------------------+ | | | | | 调用初始化方法 |----> | 业务代码中的Bean使用 | | (InitializingBean、 | | 自定义的 init-method、 | | @PostConstruct) | | @PostConstruct等 | | | | | +---------------------------+ +--------------------------+ | | v +---------------------------+ +--------------------------+ | | | | | 销毁 Bean |<---- | 容器关闭,Bean销毁 | | (DisposableBean、 | | 自定义destroy-method、 | | @PreDestroy) | | @PreDestroy等 | | | | | +---------------------------+ +--------------------------+
常用的生命周期回调
-
实现
InitializingBean
接口:public class MyBean implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { // 初始化逻辑 System.out.println("MyBean initialized."); } }
-
实现
DisposableBean
接口:public class MyBean implements DisposableBean { @Override public void destroy() throws Exception { // 销毁逻辑 System.out.println("MyBean destroyed."); } }
-
使用
@PostConstruct
注解:@Component public class MyBean { @PostConstruct public void init() { // 初始化逻辑 System.out.println("MyBean initialized with @PostConstruct."); } }
-
使用
@PreDestroy
注解:@Component public class MyBean { @PreDestroy public void cleanup() { // 销毁逻辑 System.out.println("MyBean destroyed with @PreDestroy."); } }
Bean 的作用域
在 Spring 框架中,Bean 的作用域 (Scope) 指定了 Spring 容器管理的 Bean 实例的生命周期和可见性。Spring 提供了多种作用域,用来控制 Bean 在容器中的创建、存在时间以及它们如何与其他 Bean 交互。常见的 Bean 作用域有以下几种:
1. 单例 (Singleton) — 默认作用域
- 描述:在 Spring 容器启动时,只有一个 Bean 实例会被创建,并且该实例在整个 Spring 容器的生命周期内都会被复用。
- 作用域范围:全局范围,Spring 容器中只有一个 Bean 实例。
- 用法:这是默认的作用域。如果没有显式指定作用域,Spring 会创建一个单例 Bean。
- 示例:
java
@Component @Scope("singleton") // 默认值,可以省略 public class MySingletonBean { }
2. 原型 (Prototype)
- 描述:每次从 Spring 容器请求 Bean 时,都会创建一个新的 Bean 实例。
- 作用域范围:每次请求都会返回一个新的 Bean 实例。
- 用法:适用于需要每次都创建新的 Bean 实例的场景,比如状态不同的对象。
- 示例:
java
@Component @Scope("prototype") public class MyPrototypeBean { }
3. 请求 (Request)
- 描述:每个 HTTP 请求都会创建一个新的 Bean 实例。这个作用域仅在基于 web 的 Spring 应用中有效(如 Spring MVC)。
- 作用域范围:每个 HTTP 请求会有一个独立的 Bean 实例。
- 用法:适用于与每个 HTTP 请求相关的 Bean,例如保存当前请求的状态或用户信息。
- 示例:
java
@Component @Scope("request") public class MyRequestBean { }
4. 会话 (Session)
- 描述:每个 HTTP 会话(HTTP session)会创建一个新的 Bean 实例。这个作用域仅在基于 web 的 Spring 应用中有效。
- 作用域范围:每个 HTTP 会话对应一个 Bean 实例。
- 用法:适用于需要在用户的会话中维持状态的 Bean,如保存用户信息、购物车等。
- 示例:
java
@Component @Scope("session") public class MySessionBean { }
5. 应用 (Application)
- 描述:在整个 Servlet 容器的生命周期内,只有一个 Bean 实例存在。这种作用域是基于 Web 应用的,每个应用中会有一个 Bean 实例。
- 作用域范围:在整个应用范围内共享一个 Bean 实例。
- 用法:适用于应用范围的共享 Bean,如某些跨会话的服务对象。
- 示例:
java
@Component @Scope("application") public class MyApplicationBean { }
6. 全局会话 (Global Session)
- 描述:此作用域类似于
session
,但是它仅在基于 portlet 的 web 应用中有效。全局会话作用域是针对多 portlet 应用的。 - 作用域范围:在全局会话范围内共享一个 Bean 实例。
- 用法:适用于多端口的 web 应用场景,通常在 portlet 环境下使用。
- 示例:
java
@Component @Scope("globalSession") public class MyGlobalSessionBean { }
7. 自定义作用域
- 描述:除了 Spring 提供的标准作用域,还可以自定义作用域。通过实现
Scope
接口,可以定义一个自定义的作用域。 - 用法:当默认的作用域不适合你的需求时,可以定义自己的作用域逻辑。
- 示例:
java
@Component @Scope("customScope") public class MyCustomScopeBean { }
作用域的总结:
作用域 | 描述 | 生命周期 | 使用场景 |
---|---|---|---|
Singleton | 单例作用域,容器中只有一个实例 | 整个应用生命周期 | 适用于全局共享的数据、服务、工具类等 |
Prototype | 每次请求创建一个新的 Bean 实例 | 每次请求一个新的实例 | 适用于每次请求需要不同实例的场景 |
Request | 每个 HTTP 请求创建一个新的 Bean 实例 | 每个 HTTP 请求周期内创建实例 | 适用于每个请求相关的状态或数据 |
Session | 每个 HTTP 会话创建一个新的 Bean 实例 | 每个 HTTP 会话生命周期内创建实例 | 适用于保存会话状态的对象,如用户信息 |
Application | 在整个 Servlet 容器中只有一个实例 | 整个 Web 应用生命周期 | 适用于应用级别的共享对象 |
GlobalSession | 在多 portlet 环境中每个全局会话有一个实例 | 整个全局会话生命周期 | 适用于多 portlet 的场景 |
作用域的使用:
- Spring XML 配置:
xml
<bean id="myBean" class="com.example.MyBean" scope="prototype"/>
- 注解配置:
java
@Component @Scope("singleton") public class MyBean { }
小结:
- Singleton 和 Prototype 是最常用的作用域,前者适合全局共享,后者适合每次创建新的实例。
- Request、Session、Application 等作用域适用于 Web 环境,用于处理 HTTP 请求、会话等特定场景。
IOC
IOC(Inversion of Control,控制反转)是 Spring 框架的核心思想之一,用来解耦对象之间的依赖关系。通过 IOC,Spring 容器管理对象的创建、初始化、生命周期和依赖注入,开发者只需关注业务逻辑,无需手动管理对象间的依赖关系。
IOC 的核心概念
1. 控制反转
- 传统方式:对象自己控制其依赖对象的创建和生命周期。
- IOC 方式:将对象的控制权交给 Spring 容器,由容器负责依赖对象的创建和管理。
2. 依赖注入
IOC 的实现方式主要是 依赖注入(Dependency Injection, DI)。通过 DI,容器将对象所依赖的其他对象注入进来。常见注入方式:
- 构造器注入:通过构造器传递依赖对象。
- Setter 方法注入:通过 Setter 方法传递依赖对象。
- 字段注入:直接在字段上使用注解注入依赖对象。
IOC 的工作原理
-
配置 Bean
- 开发者通过 XML 文件、注解(如
@Component
)或 Java 配置类(如@Configuration
)声明需要由 Spring 管理的 Bean。
- 开发者通过 XML 文件、注解(如
-
创建容器
- Spring 容器读取配置文件/类,扫描组件,加载并解析 Bean 的定义。
-
依赖解析
- Spring 根据 Bean 的依赖关系(通过构造器参数、
@Autowired
等)解析依赖。
- Spring 根据 Bean 的依赖关系(通过构造器参数、
-
Bean 实例化
- 容器通过反射创建 Bean 实例。
-
依赖注入
- 容器将依赖对象注入到目标 Bean 中。
-
管理生命周期
- Spring 通过
BeanPostProcessor
等机制,对 Bean 进行初始化、销毁等生命周期管理。
- Spring 通过
IOC 的优点
-
解耦:
- 对象之间通过接口和配置进行依赖关系管理,而不是直接创建对象,降低了模块之间的耦合。
-
易于扩展:
- 通过配置文件或注解,可以动态更换依赖对象而无需修改代码。
-
统一管理:
- 所有对象由容器管理,便于集中控制对象的生命周期和依赖关系。
-
方便测试:
- 可以通过容器注入 mock 对象,便于单元测试。
-
增强功能:
- IOC 容器支持 AOP、事务管理等功能,能轻松为 Bean 添加额外的逻辑。
IOC 的实现示例
1. 基于 XML 的 IOC 配置
<beans> <bean id="userService" class="com.example.UserService"> <property name="userRepository" ref="userRepository" /> </bean> <bean id="userRepository" class="com.example.UserRepository" /> </beans>
2. 基于注解的 IOC 配置
@Component public class UserService { @Autowired private UserRepository userRepository; } @Component public class UserRepository { }
3. 基于 Java 配置的 IOC
@Configuration public class AppConfig { @Bean public UserService userService() { return new UserService(userRepository()); } @Bean public UserRepository userRepository() { return new UserRepository(); } }
IOC 和 DI 的区别
- IOC 是一种思想,强调控制权的反转。
- DI 是实现 IOC 的一种具体方式,通过注入的方式解决对象依赖问题。
循环依赖
Spring 框架在创建和管理 Bean 的过程中可能会遇到循环依赖问题,即两个或多个 Bean 之间存在互相引用。Spring 通过多种方式解决循环依赖,具体方法取决于 Bean 的作用域和依赖注入的类型。
1. 单例作用域(Singleton Scope)下的循环依赖
原理
- Spring 使用 三级缓存 机制解决单例 Bean 的循环依赖:
- Singleton Objects Cache(一级缓存):
- 已经完全初始化并可供直接使用的单例 Bean。
- Early Singleton Objects(二级缓存):
- 提前暴露的原始单例对象,用于解决循环依赖。
- Singleton Factories(三级缓存):
- 存放 Bean 的 ObjectFactory,用于创建早期 Bean 实例。
- Singleton Objects Cache(一级缓存):
过程
- 当 A 和 B 存在循环依赖时:
- Bean A 初始化开始:A 的实例创建后,放入三级缓存中(通过 ObjectFactory 提供 Bean 的早期引用)。
- Bean B 初始化开始:B 需要 A 的引用,Spring 从三级缓存中获取 A 的早期引用,完成 B 的初始化。
- 完成 Bean A 初始化:A 获取 B 的引用并完成初始化。
- 缓存升级:将 A 和 B 从三级缓存移到一级缓存,完成依赖注入。
注意
- 循环依赖可以通过 Setter 注入 或 字段注入 解决。
- 如果使用 构造器注入,则无法解决循环依赖(见下文)。
2. Prototype 作用域下的循环依赖
- Spring 无法自动解决 Prototype Scope 的循环依赖。
- 原因:Prototype Bean 不会被缓存,Spring 每次需要都会创建新实例,无法提前暴露 Bean 的早期引用。
- 解决方法:
- 重构代码:避免循环依赖。
- 使用 Setter 注入:手动延迟依赖的注入。
3. 构造器注入的循环依赖
问题
- 如果两个或多个 Bean 使用 构造器注入 且相互依赖,Spring 无法解决循环依赖。
- 原因:构造器注入需要在 Bean 完全初始化之前完成依赖注入,而此时对象尚未生成,无法暴露早期引用。
解决方法
- 重构设计:消除循环依赖,例如将其中一个依赖转换为 Setter 注入。
- 使用
@Lazy
注解:- 延迟加载某些 Bean,避免在构造器中直接触发依赖注入。
4. @DependsOn
注解的使用
- Spring 提供
@DependsOn
注解,用于显式指定 Bean 的初始化顺序。 - 适用于某些特定场景,但不建议依赖此方式解决循环依赖。