IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。
为什么叫控制反转?
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
Spring提供了三种注入方式:
简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。
我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。
<!-- Constructor-arg with 'value' attribute -->
<bean id="..." class="...">
<constructor-arg value="..."/>
</bean>
@Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用@Component注解标注。@Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。@Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。@Component 注解作用于类,而@Bean注解作用于方法。
@Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean注解通常是我们在标有该注解的方法中定义产生这个bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
@Bean 注解比 @Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
@Bean注解使用示例:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
上面的代码相当于下面的 xml 配置
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
下面这个例子是通过 @Component 无法实现的。
@Bean
public OneService getService(status) {
case (status) {
when 1:
return new serviceImpl1();
when 2:
return new serviceImpl2();
when 3:
return new serviceImpl3();
}
}
Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean。

@Autowired 和@Resource使用的比较多一些。
Autowired 属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。
这会有什么问题呢? 当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。
这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。就比如说下面代码中的 smsService 就是我这里所说的名称
// smsService 就是我们上面所说的名称
@Autowired
private SmsService smsService;
举个例子,SmsService 接口有两个实现类: SmsServiceImpl1和 SmsServiceImpl2,且它们都已经被 Spring 容器所管理。
// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
我们还是建议通过 @Qualifier 注解来显示指定名称而不是依赖变量的名称。
@Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType。
@Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。
public @interface Resource {
String name() default "";
Class<?> type() default Object.class;
}
如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定name 和type属性(不建议这么做)则注入方式为byType+byName。
// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;
简单总结一下:
@Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。Autowired 默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为 byName(根据名称进行匹配)。@Autowired 和@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired 可以通过 @Qualifier 注解来显示指定名称,@Resource可以通过 name属性来显示指定名称。Spring 中 Bean 的作用域通常有下面几种:
singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Beanrequest (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话application/global-session (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean(应用websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。如何配置 bean 的作用域呢?
xml 方式:
<bean id="..." class="..." scope="singleton"></bean>
注解方式:
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
常见的有两种解决办法:
Bean 中尽量避免定义可变的成员变量。ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
对于普通的Java对象来说,它们的生命周期就是:
而对于Spring Bean的生命周期来说,可以分为四个阶段,其中初始化完成之后,就代表这个Bean可以使用了:
Bean实例化的时机也分为两种,BeanFactory管理的Bean是在使用到Bean的时候才会实例化Bean,ApplicantContext管理的Bean在容器初始化的时候就回完成Bean实例化。
Bean详细生命周期

Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点。


说明:
Bean 容器找到配置文件中 Spring Bean 的定义。Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。set()方法设置一些属性值。Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名字。Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入ClassLoader对象的实例。Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入BeanFactory对象的实例。*.Aware接口,就调用相应的方法。Bean 的 Spring 容器相关的 BeanPostProcessor对象,执行postProcessBeforeInitialization() 方法Bean 实现了InitializingBean接口,执行afterPropertiesSet()方法。Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。Bean 的 Spring 容器相关的 BeanPostProcessor对象,执行postProcessAfterInitialization() 方法Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。代码演示(基本跟上图是一样的流程):
package com.example.demo.test.beantest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
public class Person implements BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean {
private String name;
private String address;
private int phone;
private BeanFactory beanFactory;
private String beanName;
public Person() {
System.out.println("7、【构造器】调用Person的构造器实例化");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("10、【注入属性】注入属性name");
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
System.out.println("9、【注入属性】注入属性address");
this.address = address;
}
public int getPhone() {
return phone;
}
public void setPhone(int phone) {
System.out.println("11、【注入属性】注入属性phone");
this.phone = phone;
}
@Override
public String toString() {
return "Person [address=" + address + ", name=" + name + ", phone="
+ phone + "]";
}
// 这是BeanFactoryAware接口方法
@Override
public void setBeanFactory(BeanFactory arg0) throws BeansException {
System.out.println("13、【BeanFactoryAware接口】调用BeanFactoryAware.setBeanFactory()");
this.beanFactory = arg0;
}
// 这是BeanNameAware接口方法
@Override
public void setBeanName(String arg0) {
System.out.println("12、【BeanNameAware接口】调用BeanNameAware.setBeanName()");
this.beanName = arg0;
}
// 这是InitializingBean接口方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("15、【InitializingBean接口】调用InitializingBean.afterPropertiesSet()");
}
// 这是DiposibleBean接口方法
@Override
public void destroy() throws Exception {
System.out.println("22、【DiposibleBean接口】调用DiposibleBean.destory()");
}
// 通过的init-method属性指定的初始化方法
public void myInit() {
System.out.println("16、【init-method】调用的init-method属性指定的初始化方法" );
}
// 通过的destroy-method属性指定的初始化方法
public void myDestory() {
System.out.println("23、【destroy-method】调用的destroy-method属性指定的初始化方法" );
}
}
package com.example.demo.test.beantest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public MyBeanFactoryPostProcessor() {
super();
System.out.println("2、这是BeanFactoryPostProcessor实现类构造器!!");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0)
throws BeansException {
System.out.println("3、BeanFactoryPostProcessor调用postProcessBeanFactory方法");
BeanDefinition bd = arg0.getBeanDefinition("person");
bd.getPropertyValues().addPropertyValue("phone", "110");
}
}
package com.example.demo.test.beantest;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
super();
System.out.println("4、这是BeanPostProcessor实现类构造器!!");
// TODO Auto-generated constructor stub
}
@Override
public Object postProcessAfterInitialization(Object arg0, String arg1)
throws BeansException {
System.out.println("17、BeanPostProcessor接口方法postProcessAfterInitialization对属性进行更改!");
return arg0;
}
@Override
public Object postProcessBeforeInitialization(Object arg0, String arg1)
throws BeansException {
System.out.println("14、BeanPostProcessor接口方法postProcessBeforeInitialization对属性进行更改!");
return arg0;
}
}
package com.example.demo.test.beantest;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import java.beans.PropertyDescriptor;
public class MyInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
public MyInstantiationAwareBeanPostProcessor() {
super();
System.out.println("5、这是InstantiationAwareBeanPostProcessorAdapter实现类构造器!!");
}
// 接口方法、实例化Bean之前调用
@Override
public Object postProcessBeforeInstantiation(Class beanClass,
String beanName) throws BeansException {
System.out.println("6、InstantiationAwareBeanPostProcessor调用postProcessBeforeInstantiation方法");
return null;
}
// 接口方法、实例化Bean之后调用
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("18、InstantiationAwareBeanPostProcessor调用postProcessAfterInitialization方法");
return bean;
}
// 接口方法、设置某个属性时调用
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs,
PropertyDescriptor[] pds, Object bean, String beanName)
throws BeansException {
System.out.println("8、InstantiationAwareBeanPostProcessor调用postProcessPropertyValues方法");
return pvs;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="beanPostProcessor" class="com.example.demo.test.beantest.MyBeanPostProcessor">
</bean>
<bean id="instantiationAwareBeanPostProcessor" class="com.example.demo.test.beantest.MyInstantiationAwareBeanPostProcessor">
</bean>
<bean id="beanFactoryPostProcessor" class="com.example.demo.test.beantest.MyBeanFactoryPostProcessor">
</bean>
<bean id="person" class="com.example.demo.test.beantest.Person" init-method="myInit"
destroy-method="myDestory" scope="singleton" p:name="张三" p:address="广州"
p:phone="1590000" />
</beans>
package com.example.demo.test.beantest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanLifeCycle {
public static void main(String[] args) {
System.out.println("1、现在开始初始化容器");
ApplicationContext factory = new ClassPathXmlApplicationContext("classpath*:beans.xml");
System.out.println("19、容器初始化成功");
//得到Preson,并使用
Person person = factory.getBean("person",Person.class);
System.out.println(person); // 20
System.out.println("21、现在开始关闭容器!");
((ClassPathXmlApplicationContext)factory).registerShutdownHook();
}
}
执行结果:
1、现在开始初始化容器
15:19:30.624 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@707f7052
15:19:30.765 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 4 bean definitions from URL [file:/E:/workspace/datasource/demo/target/classes/beans.xml]
15:19:30.797 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanFactoryPostProcessor'
2、这是BeanFactoryPostProcessor实现类构造器!!
3、BeanFactoryPostProcessor调用postProcessBeanFactory方法
15:19:30.820 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'beanPostProcessor'
4、这是BeanPostProcessor实现类构造器!!
15:19:30.820 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'instantiationAwareBeanPostProcessor'
5、这是InstantiationAwareBeanPostProcessorAdapter实现类构造器!!
15:19:30.824 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'person'
6、InstantiationAwareBeanPostProcessor调用postProcessBeforeInstantiation方法
7、【构造器】调用Person的构造器实例化
8、InstantiationAwareBeanPostProcessor调用postProcessPropertyValues方法
9、【注入属性】注入属性address
10、【注入属性】注入属性name
11、【注入属性】注入属性phone
12、【BeanNameAware接口】调用BeanNameAware.setBeanName()
13、【BeanFactoryAware接口】调用BeanFactoryAware.setBeanFactory()
14、BeanPostProcessor接口方法postProcessBeforeInitialization对属性进行更改!
15、【InitializingBean接口】调用InitializingBean.afterPropertiesSet()
16、【init-method】调用<bean>的init-method属性指定的初始化方法
17、BeanPostProcessor接口方法postProcessAfterInitialization对属性进行更改!
18、InstantiationAwareBeanPostProcessor调用postProcessAfterInitialization方法
19、容器初始化成功
Person [address=广州, name=张三, phone=110]
21、现在开始关闭容器!
15:19:30.888 [SpringContextShutdownHook] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Closing org.springframework.context.support.ClassPathXmlApplicationContext@707f7052, started on Sat Nov 05 15:19:30 CST 2022
22、【DiposibleBean接口】调用DiposibleBean.destory()
23、【destroy-method】调用<bean>的destroy-method属性指定的初始化方法
方法一:懒加载的形式——@Lazy
(未论证)使用懒加载的方式,所有待注入的对象都是在直接调用的时候创建,比如说A中注入B在创建A的时候并不是直接创建A对象而是返回A的代理对象,在代理A的过程中我们不创建B对象,而是将getB()方法前面添加this.b = new B();通过这样,达到只有当我们使用对象时才去创建它,因此就不存在循环问题了。

方法二:改用Setter方式注入
所谓的循环依赖指的就是在A对象的构造方法中Spring要注入B,而在B对象中Spring要注入A。这个时候会形成一个闭环因为Spring不知道该先注入哪一个接着会抛出异常。而Spring建议的处理方式是说如果遇到这种情况的话就改用Setter方式注入。
可参考:https://blog.csdn.net/a924382407/article/details/125906079
数据源application.yml内容如下
my-profile:
name: dfdd
email: zhuwang@163.com
library:
location: 中华人名共和国
books:
- name: 天才基本法
description: 防辐射的发挥是是
- name: 时间的秩序
description: 发顺丰是粉红色的和
- name: 了不起的我
description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻?
1、@Value(常用)
使用 @Value("${property}") 读取比较简单的配置信息:
@Value("${server.port}")
private Integer serverPort;
2、@ConfigurationProperties(常用)
@ConfigurationProperties主要是一个读取配置文件信息的注解,可以按照配置的前缀信息读取对应的配置信息。可以作用于类和方法上。
在application.properties中写入以下信息
test.properties.name=1111
test.properties.age=12
test.properties.note=gwgasfsdfa
test.properties.location-info=vsgfrqwfw
创建对应的实体类(加在类上面)
/**
* 1)注解@ConfigurationProperties中的prefix用于设置前缀
* 2)下方的属性名称必须和要获取的配置信息名称一致,比如必须叫port,否则获取值为null
*/
@ConfigurationProperties(prefix = "server")//这个注解是用找到类
@Component //生效的两种方式:方式1:配置@Component,方式2:启动类添加 @EnableConfigurationProperties(ReadProperties.class)
@Data
public class MyConfigurationProperties {
String name;
int age;
String note;
/**
* 驼峰命名
* driver-class-name”这种带横杠的情况,在POJO里面的命名规则是 下划线转驼峰 就可以绑定成功,所以就是“driverClassName”
*/
String locationInfo;
}
或者可以加在(方法)上面
@Configuration
public class PropertiesConfiguration {
@Bean("myConfigurationProperties")
@ConfigurationProperties(prefix = "test.properties")
public MyConfigurationProperties myConfigurationProperties(){
return new MyConfigurationProperties();
}
}
使用
@Autowired
MyConfigurationProperties myConfigurationProperties;
@Component
@ConfigurationProperties(prefix = "library")
class LibraryProperties {
@NotEmpty
private String location;
private List<Book> books;
@Setter
@Getter
@ToString
static class Book {
String name;
String description;
}
省略getter/setter
......
}
3、@PropertySource(不常用)
@PropertySource读取指定 properties 文件
4、Environment获取属性值
# 属性配置类的
server:
port: 8082
spring:
main:
banner-mode: console
# 自定义
alipay:
pay:
appid: 123456
notify: http://www.xxx.com
@RestController
public class ReadPropertiesEnvironment {
@Autowired
private Environment environment;
@GetMapping("/read/file")
public Map<String,Object> readInfo(){
Map<String,Object> map = new HashMap<>();
map.put("port",environment.getProperty("server.port"));
map.put("appid",environment.getProperty("alipay.pay.appid"));
map.put("notify",environment.getProperty("alipay.pay.notify"));
map.put("javaversion",environment.getProperty("java.version"));
map.put("javahome",environment.getProperty("JAVA_HOME"));
map.put("mavenhome",environment.getProperty("MAVEN_HOME"));
return map;
}
public static void main(String[] args) {
Properties properties = System.getProperties();
Set<String> strings = properties.stringPropertyNames();
for (String string : strings) {
System.out.println(string+"===>"+properties.get(string));
}
}
}
5、使用原生方式读取配置文件
@SpringBootApplication
public class DemoApplication implements InitializingBean {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void afterPropertiesSet() throws Exception {
Properties props = new Properties();
try {
InputStreamReader inputStreamReader = new InputStreamReader(
this.getClass().getClassLoader().getResourceAsStream("application.properties"),
StandardCharsets.UTF_8);
props.load(inputStreamReader);
} catch (IOException e1) {
System.out.println(e1);
}
System.out.println("Properties Name:" + props.getProperty("profile.name"));
}
}