IOC(Inversion of Control,控制反转)
控制反转 是一种设计原则,旨在将对象创建和依赖管理的控制权从应用程序代码中转移到外部容器或框架中。换句话说,应用程序不再负责创建和管理依赖,而是将这些职责交给外部的容器或框架。
DI(Dependency Injection,依赖注入)
依赖注入 是实现控制反转的一种方式,表示对象所依赖的其他对象(依赖)是由外部注入的,而不是自己创建的。DI 的核心目的是将对象的创建和管理职责从类内部转移到外部。
package xin.links.spring.ioc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.Arrays;
// Spring主程序入口
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
// ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器
ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args);
// 调试输出IOC容器
System.out.println("ioc = " + ioc);
System.out.println("增强for调试输出");
// 增强for调试输出IOC容器中所有组件
for (String beanDefinitionName : ioc.getBeanDefinitionNames()) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
System.out.println("forEach调试输出");
// forEach调试输出IOC容器中所有组件
// ioc.getBeanDefinitionNames()获取的默认为数组,不支持forEach,转为流后Stream有这个API
Arrays.stream(ioc.getBeanDefinitionNames()).forEach(System.out::println);
}
}
组件创建前先创建实体类使用@Data注解实现get/set
@Data注解不可用说明没有导入lombok依赖
可以借助IDEA中的Maven Search插件快速导入
package xin.links.spring.ioc.bean;
import lombok.Data;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.bean
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/1 15:44
*/
@Data
public class Person {
private String name;
private int age;
private String gender;
}
直接在主程序中创建(以用户组件为例)
package xin.links.spring.ioc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import xin.links.spring.ioc.bean.Person;
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
// ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器
ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args);
}
// 使用@Bean注解来声明组件
@Bean
public Person person(){
Person person = new Person();
person.setName("张三");
person.setAge(22);
person.setGender("男");
return person;
}
}
通过配置类创建(配置类本身也是一个组件)
在主程序目录下新建Config目录,创建配置类(以用户组件为例)PersonConfig.class
package xin.links.spring.ioc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import xin.links.spring.ioc.bean.Person;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.config
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/1 16:14
*/
//使用@Configuration注解来声明一个配置类
@Configuration
public class PersonConfig {
// 使用@Bean注解来声明组件,组件名默认是方法名
@Bean
public Person person(){
Person person = new Person();
person.setName("张三");
person.setAge(22);
person.setGender("男");
return person;
}
// 通过修改方法名来修改组件名
@Bean
public Person lisi() {
Person person = new Person();
person.setName("李四");
person.setAge(30);
person.setGender("女");
return person;
}
// 通过@Bean注解的name属性来修改组件名
@Bean(name = "wangWu")
public Person wangWu() {
Person person = new Person();
person.setName("王五");
person.setAge(28);
person.setGender("男");
return person;
}
}
基于上述容器内容
通过 Bean 名称获取:
System.out.println("ioc.getBean(\"lisi\", Person.class) = " + ioc.getBean("person");
通过 Bean 类型获取:
System.out.println("ioc.getBean(\"lisi\", Person.class) = " + ioc.getBean(Person.class);
通过 Bean 名称和类型获取:
System.out.println("ioc.getBean(\"lisi\", Person.class) = " + ioc.getBean("lisi", Person.class));
通过类型获取所有 Bean 实例:
ioc.getBeansOfType(Person.class).forEach((name, person) -> System.out.println(name + " = " + person));
注意:如果不慎在创建组件时发生了重复,只会将首个创建的组件放入容器
异常信息汇总
NoSuchBeanDefinitionException
bean
名称不存在时(getBean(String name)
)。bean
类型不存在时(getBean(Class<T> requiredType)
)。bean
名称和类型都不存在或不匹配时(getBean(String name, Class<T> requiredType)
)。错误消息示例:
No bean named 'person1' available
No qualifying bean of type 'Person' available
No bean named 'person1' available of required type 'Person'
NoUniqueBeanDefinitionException
bean
时,使用 getBean(Class<T> requiredType)
会抛出此异常。错误消息示例:
No qualifying bean of type 'Person' available: expected single matching bean but found 2: person1, person2
Empty Result
for getBeansOfType
getBeansOfType(Class<T> type)
方法不会抛出异常,如果没有找到符合条件的 bean
,会返回一个空的 Map
。MVC是常用的Web应用架构
这些注解所表示类所在文件夹应该在主程序所在根包下,用这些注解来声明并组件放入容器
内部有组件使用@bean组件声明
@Controller
控制器,用于标识一个类作为控制器。
package xin.links.spring.ioc.controller;
import org.springframework.stereotype.Controller;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.controller
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/1 18:43
*/
// 示例
@Controller
public class UserController {
}
@Repository
数据访问对象注解,用于标识一个类作为数据访问对象(DAO)。
package xin.links.spring.ioc.dao;
import org.springframework.stereotype.Repository;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.dao
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/1 18:43
*/
// 示例
@Repository
public class UserDao {
}
@Service
服务层注解,用于标识一个类作为服务类。
package xin.links.spring.ioc.service;
import org.springframework.stereotype.Service;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.service
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/1 18:44
*/
// 示例
@Service
public class UserService {
}
@Component
通用组件注解,用于标识一个类作为Spring容器管理的组件。
javapackage xin.links.spring.ioc.component;
import org.springframework.stereotype.Component;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.component
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/1 20:29
*/
@Component
public class ComponentTest {
}
导入第三方配置类到容器中作为组件,导入前需要确认配置类的类路径可用
假定的三方配置类
package outclass;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.outclass
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/1 20:03
*/
public class OutClass {
public void sayHello(){
System.out.println("Hello World");
}
}
通过new对象的方式导入
package xin.links.spring.ioc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import outclass.OutClass;
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
// ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器
ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args);
// 新建对象引入外部配置类
new OutClass().sayHello();
}
}
通过@Import注解导入
package xin.links.spring.ioc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;
import outclass.OutClass;
//@Import引入外部配置类
@Import(OutClass.class)
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
// ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器
ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args);
ioc.getBean(OutClass.class).sayHello();
}
}
在主程序入口中,默认扫描当前根路径下的组件类
如果要获取当前根路径上层文件,需要更改扫描范围
假定在xin.links.spring.scan
路径下存在ScanTest类,而主程序根路径为 xin.links.spring.ioc
那么ScanTest类默认不会被扫描,想要扫描需要借助@ComponentScan注解扩大扫描范围,可以一次指定多个包
此外还可以用来只扫描指定注解,排除指定注解,关闭默认注解只扫描的=定义的指定注解,以及懒加载
package xin.links.spring.scan;
import org.springframework.stereotype.Component;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.scan
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/1 21:19
*/
@Component
public class ScanTest {
public void test(){
System.out.println("ScanTest");
}
}
package xin.links.spring.ioc.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.config
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/1 20:04
*/
@Import(ScanTest.class)
@ComponentScan(
// 指定要扫描的包 指定路径的所有子包也会被扫描
basePackages = {"xin.links.spring.ioc", "xin.links.spring.scan"},
// 仅包含 @Controller 注解的类
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class),
// 排除所有 @Service 注解的类
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class),
// 关闭默认扫描行为,只使用 includeFilters 指定的过滤规则
useDefaultFilters = false,
// 延迟初始化所有的组件(即懒加载)
lazyInit = true
)
@Configuration
public class MainConfig {
}
用于控制 Spring 管理的 bean 的生命周期和可见性
这是默认的作用域,Spring 容器中只会创建一个 bean 实例,该实例在容器的整个生命周期内唯一。适用于线程安全的、状态无关的对象。
跟随容器创建,可以使用@Lazy注解实现懒加载
@Component
@Scope("singleton")
public class MySingletonBean {
// 单例 bean 的代码
}
每次请求都会创建一个新的 bean 实例,即使在同一个容器中也会有多个实例。适用于需要每次都获取新实例的情况。
@Component
@Scope("prototype")
public class MyPrototypeBean {
// 原型 bean 的代码
}
在每个 HTTP 请求中,Spring 会创建一个新的 bean 实例。该作用域主要用于 Web 应用程序。适用于每次 HTTP 请求都需要新实例的情况。
@Component
@Scope("request")
public class MyRequestBean {
// 请求作用域 bean 的代码
}
在每个 HTTP 会话中,Spring 会创建一个新的 bean 实例。每个用户的会话都有自己独立的 bean 实例。适用于在用户会话中共享数据的情况。
@Component
@Scope("session")
public class MySessionBean {
// 会话作用域 bean 的代码
}
与 singleton 类似,但它的作用域与 Spring 容器的生命周期相同,整个应用程序范围内共享 bean 实例。通常用于 Java EE 应用程序。
@Component
@Scope("application")
public class MyApplicationBean {
// 应用作用域 bean 的代码
}
WebSocket(了解)
在 WebSocket 环境中,Spring 会为每个 WebSocket 会话创建一个新的 bean 实例。适用于 WebSocket 连接中的 bean。
@Component
@Scope("websocket")
public class MyWebSocketBean {
// WebSocket 作用域 bean 的代码
}
涉及到复杂的对象需要动态创建,可以使用 FactoryBean 来实现
在容器中的类型取决于泛型指定的类型,组件名为工厂类的名称
import org.springframework.beans.factory.FactoryBean;
public class MyComplexObjectFactoryBean implements FactoryBean<MyComplexObject> {
// 返回创建的 bean 实例
@Override
public MyComplexObject getObject() throws Exception {
// 创建并配置 MyComplexObject 实例
MyComplexObject obj = new MyComplexObject();
// 进行一些复杂的配置
return obj;
}
// 返回创建的 bean 实例的类型
@Override
public Class<?> getObjectType() {
return MyComplexObject.class;
}
@Override
public boolean isSingleton() {
//true或 false,取决于你的需求,true为单实例 false为多实例(即原型作用域)
return true;
}
}
在 配置中类使用 FactoryBean
@Configuration
public class AppConfig {
@Bean
public FactoryBean<MyComplexObject> myComplexObjectFactoryBean() {
return new MyComplexObjectFactoryBean();
}
}
@Conditional
基本注解,用于根据一个自定义的条件类来决定是否创建一个 Bean。你可以实现自己的条件逻辑,例如检查某些属性或环境变量是否满足特定条件。
使用场景:需要自定义复杂的条件逻辑时。
@ConditionalOnProperty
根据某个配置属性(如 application.properties
中的属性)是否存在或具有特定值来决定是否创建一个 Bean。
使用场景:需要根据外部配置(如系统属性或配置文件中的属性)来控制 Bean 的创建。
@ConditionalOnClass
只有在类路径中存在指定类时才创建 Bean。它检查的是编译期类路径中是否存在特定类的定义。
使用场景:需要在某个第三方库存在时自动配置相关的 Bean。
@ConditionalOnMissingBean
只有在上下文中缺少指定名称或类型的 Bean 时才创建新的 Bean。
使用场景:提供默认的 Bean 配置,当没有其他符合条件的 Bean 时使用。
@ConditionalOnBean
只有在上下文中存在指定名称或类型的 Bean 时才创建新的 Bean。
使用场景:需要依赖其他 Bean 存在的条件下才创建当前 Bean,例如在依赖链中使用。
@ConditionalOnResource
只有在类路径中存在指定的资源(如配置文件)时才创建 Bean。
使用场景:需要根据外部资源(如文件或配置)的存在与否来控制 Bean 的创建。
@Profile
只有在指定的 Spring 环境 Profile 激活时才创建 Bean,例如开发环境(development
)或生产环境(production
)。
使用场景:根据不同环境(如开发、测试、生产)来控制 Bean 的创建。
@ConditionalOnMissingClass
只有在类路径中缺少指定类时才创建 Bean。
使用场景:在某个类不可用时提供备用实现。
Bean 在 Spring 容器中的唯一标识符,通常用于通过名称查找 Bean。可以在配置时显式指定(例如通过 @Component("myBean")
或 <bean id="myBean">
),如果未指定则默认使用类名的首字母小写形式。
Bean 的类型,即 Bean 所对应的 Java 类。这决定了 Bean 的行为、方法和属性。
Bean 在 Spring 容器中的实际对象引用。实例是 Spring Bean 的具体对象,每次调用 getBean
方法获取的就是这个实例,具体实例的数量由作用域(Scope)属性决定。
Bean 的作用域,定义了 Bean 实例的创建方式和生命周期。常见的作用域有:单实例(Singleton)在整个 Spring 容器中共享一个实例(默认),多实例(Prototype)每次请求时创建一个新的实例,Web 作用域如 Request
(每个 HTTP 请求一个实例)和 Session
(每个会话一个实例)。
Bean 在创建和销毁时调用的方法,定义了 Bean 的生命周期管理逻辑。例如,通过 @PostConstruct
和 @PreDestroy
注解的方法。
定义 Bean 之间的依赖关系,通过构造器注入、Setter 注入或字段注入的方式进行管理。
指的是在 Spring 容器启动时,不立即创建 Bean 实例,而是在第一次被请求或需要时才进行实例化。懒加载可以优化启动性能,减少内存占用,避免在启动时立即加载不必要的 Bean。
@Autowired
(重点)Spring 的核心注解之一,用于自动注入依赖。可以用于构造器、字段(属性)、setter 方法。
默认情况下按类型自动注入,如果有多个同类型 Bean 可以配合 @Qualifier
或 @Primary
使用。
//通过设置required属性用于指定在自动注入时,是否跳过注入,false状态下,依赖的 Bean 就会保持 null
@Autowired(required = false)
@Qualifier
配合 @Autowired
使用,用于指定注入 Bean 的名称。当有多个相同类型的 Bean 时,可以用 @Qualifier
来明确指定要注入的具体 Bean。
@Primary
用于标记一个默认的首选 Bean,当有多个相同类型的 Bean 可供注入时,优先选择带有 @Primary
注解的那个。
@Resource
Java 的标准注解(JSR-250),功能类似 @Autowired
,但默认按名称注入。可以通过 name
或 type
属性进行指定。它是 Java EE 提供的注解,Spring 也支持它来进行依赖注入。
通过 setter
方法注入依赖对象。Spring 会调用这些方法来设置依赖。
public class CalculatorService {
private Calculator calculator;
@Autowired
public void setCalculator(Calculator calculator) {
this.calculator = calculator;
}
}
通过类的构造函数来注入依赖。适用于依赖项为不可变对象(final
)或依赖关系必须在对象创建时就提供的情况。
public class CalculatorService {
private final Calculator calculator;
@Autowired
public CalculatorService(Calculator calculator) {
this.calculator = calculator;
}
}
xxxAware
Spring 提供的注解接口(例如 ApplicationContextAware
, BeanFactoryAware
等),当实现这些接口时,Spring 会注入相关的容器对象(如 ApplicationContext
或 BeanFactory
)到你的 Bean 中,通常用于获取 Spring 容器自身的资源。
感知接口(Aware Interface)在 Spring 中是一种特殊的机制,允许 Bean “感知”到 Spring 容器的内部资源,如 ApplicationContext
、BeanFactory
、ServletContext
等。通过实现这些接口,Bean 可以直接访问这些容器对象或资源,从而与 Spring 的底层框架进行更紧密的集成。
ApplicationContextAware
ApplicationContext
(即 Spring 容器上下文),从而可以访问其他 Bean、发布事件等。BeanFactoryAware
BeanFactory
(Spring 容器的基础实现之一),可以直接访问和操作 Bean 的工厂方法。EnvironmentAware
Environment
对象,可以访问当前环境的属性和配置(如系统属性、环境变量等)。ResourceLoaderAware
ResourceLoader
,从而可以用来加载资源文件(如类路径下的文件、URL 资源等)。ServletContextAware
ServletContext
(Web 应用程序上下文),仅适用于 Web 应用。实现感知接口非常简单,只需要让你的 Bean 类去实现相应的接口,并覆盖接口中定义的方法即可。
例如,下面的类可以感知到 ApplicationContext
并在启动时打印所有 Bean 的名称。
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class MyApplicationContextAwareBean implements ApplicationContextAware {
private ApplicationContext applicationContext;
// 实现接口中的方法,当 Bean 被创建时,Spring 会调用这个方法
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
System.out.println("所有 Bean 名称如下:");
for (String beanName : applicationContext.getBeanDefinitionNames()) {
System.out.println(beanName);
}
}
}
注意事项
虽然感知接口提供了对 Spring 内部机制的强大访问能力,但过度使用它们可能会导致你的代码和 Spring 框架之间的耦合变得过于紧密。
理想情况下,应该尽量使用依赖注入(DI)来获得 Bean 的依赖,而不是直接访问容器本身,这样可以保持更好的解耦和模块化设计。
感知接口的使用是一把双刃剑,应谨慎使用,确保你的设计不因此变得复杂和难以维护。
@Value
(重点)用于将外部化的值(如配置文件中的值)注入到 Bean 的属性中。可以直接在字段、构造函数或 setter 方法上使用,支持 SpEL 表达式语言。
@Value("${app.name}")
private String appName;
一种强大的表达式语言(Spring Expression Language),可以在 Spring 中用于注入复杂的值,如计算表达式、方法调用、正则表达式匹配等。
通常与 @Value
注解一起使用。
@Value("#{1 + 2}") // 注入计算结果 3
private int sum;
@Value("#{systemProperties['user.name']}") // 注入系统属性 "user.name"
private String userName;
@Value("#{T(java.util.UUID).randomUUID().toString()}") // 注入UUID
private String uuid;
@PropertySource
用于加载外部属性文件到 Spring 的 Environment
中,以便通过 @Value
注解访问这些属性。
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
}
@Profile
(重点)
用于定义 Bean 在特定环境(Profile)下才会被创建。可以用在类或方法上,通常用于区分开发、测试、生产环境的配置。
@Configuration
@Profile("development")
public class DevConfig {
}
示例说明(创建User组件,调试输出各个生命周期)
可以使用@Bean底层提供的初始化销毁方法,也可以使用InitializingBean, DisposableBean或者是
@PostConstruct可以在构造器之后执行(后置处理钩子)
@PreDestroy可以在接口实现的销毁方法前执行,实现销毁预处理
@BeanPostProcessor可以在初始化之前和初始化之后分别操作
package xin.links.spring.ioc.bean;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Data;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.bean
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/2 20:22
*/
@Data
public class User implements InitializingBean, DisposableBean {
private String username;
private String password;
private Person person;
@Autowired
public void setPerson(Person person) {
this.person = person;
System.out.println("自动注入Person");
}
public User() {
System.out.println("User无参构造函数");
}
public void initUser(){
System.out.println("User初始化方法");
}
public void destroyUser(){
System.out.println("User销毁方法");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("User接口实现初始化方法");
}
@Override
public void destroy() throws Exception {
System.out.println("User接口实现销毁方法");
}
@PostConstruct
public void initUserFun(){
System.out.println("User注解后置处理钩子");
}
@PreDestroy
public void destroyUserFun(){
System.out.println("User注解销毁预处理");
}
}
package xin.links.spring.ioc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import xin.links.spring.ioc.bean.User;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.config
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/2 20:22
*/
//创建配置类,将组件放入容器
@Configuration
public class UserConfig {
// bean注解在底层提供了声明周期方法
// initMethod = "init" 指定初始化方法
// destroyMethod = "destroy" 指定销毁方法
// @Bean(initMethod = "init",destroyMethod = "destroy")
// 1.参数中的名字为组件中的方法名
// 2.可以指定bean的id,如果不指定,默认使用方法名作为id
// 3.可以指定bean的初始化方法和销毁方法
// 还可以使用实现InitializingBean, DisposableBean接口的方案初始化和销毁
@Bean(initMethod = "initUser",destroyMethod = "destroyUser")
public User user(){
return new User();
}
}
package xin.links.spring.ioc.PostProcessor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import xin.links.spring.ioc.bean.User;
/**
* ClassName: SpringFramework
* Package: xin.links.spring.ioc.PostProcessor
* Description:
*
* @author Jane
* @version 1.0
* @since 2024/9/2 20:32
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("beanName = " + beanName + ",bean = " + bean);
System.out.println("Bean初始化之前执行");;
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("beanName = " + beanName + ",bean = " + bean);
System.out.println("Bean初始化之后执行");
if (bean instanceof User user) {
user.setUsername("张三");
}
return bean;
}
}
package xin.links.spring.ioc;
import lombok.ToString;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import xin.links.spring.ioc.bean.User;
@SpringBootApplication
public class SpringIocApplication {
public static void main(String[] args) {
// ConfigurableApplicationContext继承于ApplicationContext(Spring的应用上下文对象)也就是IOC容器
ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocApplication.class, args);
System.out.println("IOC容器创建完成");
System.out.println("ioc.getBean(User.class) = " + ioc.getBean(User.class));
// User组件生命周期的执行顺序
// 1.构造方法
// 2.属性赋值(自动注入)
// 3.初始化前 后置处理器
// 4.注解初始化方法
// 5.接口初始化方法
// 6.初始化方法
// 7.初始化后 后置处理器
// 8.使用
// 9.销毁预处理
// 10.接口销毁方法
// 11.销毁方法
}
}
@Autowired注解是Spring框架中用于自动装配bean的注解。
它可以通过在构造函数、属性、setter方法上使用,来自动注入依赖项。
@Autowired注解的工作原理是自动寻找匹配的bean,并将其注入到使用@Autowired注解的变量、方法或构造函数中。
@Autowired注解的实现主要依赖于AutowiredAnnotationBeanPostProcessor这个后处理器。
当Spring容器创建Bean的时候,会调用BeanPostProcessor的postProcessBeforeInitialization方法。
在这个过程中,AutowiredAnnotationBeanPostProcessor会检查当前Bean的属性上是否标注了@Autowired注解。
如果发现@Autowired注解,就会通过反射的方式,获取属性对应的类型,然后在Spring容器中查找匹配的Bean,并将其注入到当前Bean的属性中。
具体来说,AutowiredAnnotationBeanPostProcessor会遍历当前Bean的所有属性,对于每个属性,它会检查是否标注了@Autowired注解。
如果标注了,就会通过TypeFilter匹配器,根据属性类型在Spring容器中查找匹配的Bean。找到之后,就会通过BeanFactory将这个Bean注入到当前属性中。
步骤简要描述:
Person
类有一个uuid
属性,它被UUID
注解标记
UUIDProcess
类是一个自定义的`BeanPostProcessor
PersonConfig
类是一个配置类,用于声明Person
bean
Person
类有一个uuid
属性,它被UUID
注解标记。
在Spring容器初始化Person
bean之前,UUIDProcess
为Person
bean中带有UUID
注解的String
类型字段生成一个随机的UUID并赋值。
package xin.links.spring.ioc.bean;
import lombok.Data;
import xin.links.spring.ioc.annotation.UUID;
@Data
public class Person {
private String name;
private int age;
private String gender;
@UUID
private String uuid;
}
package xin.links.spring.ioc.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import xin.links.spring.ioc.bean.Person;
@Configuration
public class PersonConfig {
@Bean
@ConditionalOnMissingBean(name = "UserService")
public Person person() {
Person person = new Person();
person.setName("张三");
person.setAge(22);
person.setGender("男");
return person;
}
}
package xin.links.spring.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface UUID {
}
package xin.links.spring.ioc.annotation;
import lombok.SneakyThrows;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
/**
* 在Bean初始化之前对Bean进行后置处理。
*
* @param bean 要处理的Bean实例
* @param beanName Bean的名称
* @return 处理后的Bean实例
*/
@Override
@SneakyThrows
public Object postProcessBeforeInitialization(Object bean, String beanName) {
// 获取Bean的所有字段
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
// 如果字段上有UUID注解且类型为String
if (field.isAnnotationPresent(UUID.class) && field.getType().equals(String.class)) {
// 设置字段为可访问,以便进行赋值操作
field.setAccessible(true);
// 生成一个随机的UUID字符串
String s = java.util.UUID.randomUUID().toString();
// 将生成的UUID字符串赋值给字段
field.set(bean, s);
}
}
return bean;
}
© 著作权归作者所有
本文由 趣代码Blog 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。