简介
Spring作为一款经典框架,并且作为Spring家族的老大哥,也是SpringBoot,SpringCloud的一个基石,在我们工作中使用频率非常高,所以深入了解Spring的实现就很有必要。
IoC(或DI)是Spring框架的核心功能之一,是Spring生态系统的基础。
此处有一个很重要的容器,容器的作用:用来存储对象,BeanDefinitionMap使用key,value形式来存储对象,key为BeanName,value为BeanDefinition对象,在spring中一般存在三级缓存,singletonObjects(一级缓存)存放完整的Bean对象,Bean的生命周期也都是由容器来进行管理。
Spring IoC的主要功能是将项目中的各种POJO虚拟成一个个Bean,管理这些Bean的生命周期以及Bean之间的依赖关系。开发人员在需要使用某个POJO时,只需要通过Spring容器/工厂获取即可,不必关心该对象的创建过程和内部复杂的依赖关系,因此大大简化了Java开发。
我们可以将Spring的IoC功能理解成容器或者工厂。对于某些singleton
作用域的Bean而言,Spring IoC需要对其进行缓存,以便每次获取都保证是同一个对象,因此充当了容器的功能。实际上Spring内部使用了Map
的数据结构保存这些单例Bean。对于某些prototype
作用域的Bean而言,每次获取都需要创建新的对象返回,此时Spring IoC充当了工厂的功能。
上图对日常使用Spring IoC的流程进行了抽象。BeanFactory
接口是Spring IoC对其容器/工厂功能的最顶层抽象,它有许多实现类可用于不同的应用场景。POJO类表示日常开发中的项目逻辑。配置文件中定义了Bean之间的依赖关系,表示将项目中各种POJO抽象成Bean,并交给Spring IoC管理。其中@Annotion
配置包括Java配置类和POJO中的注解声明两种方式。
因此,日常开发流程如下:
- 根据项目需求开发各种POJO类;
- 通过配置文件(XML文件、Java配置类等)定义需要被管理的Bean以及它们之间的依赖关系;
- 根据不同应用场景,使用
BeanFactory
接口的具体容器/工厂实现类加载配置文件,它内部会对相关Bean进行管理; - 在需要使用POJO时,调用上述容器/工厂实现类的
getBean()
方法获取Bean并使用。
启动流程
在看源码之前,先熟悉一下启动流程:
流程介绍
源码有兴趣的小伙伴可以下去看,就不在详细去看了,我总结一下源码的一个调用过程。
1、对于对象的声明一般有两种方式,xml配置文件中声明<bean>标签,或者是通过注解的方式去扫描使用了@Compoent、@Service、@Controller、@Bean等注解声明的类或者方法
2、IOC创建过程会涉及到一个顶级接口BeanFactory,以及常用的DefaultListableBeanFactory,通过createBeanFactory创建Bean工厂(DefaultListableBeanFactory)
,它们会向Bean工厂设置一些参数信息,如BeanPostProcessor,Aware接口的子类等
3、加载解析Bean对象,通过BeanDefinitionReader对xml、注解方式进行解析,解析为BeanDefinition对象
4、BeanFactoryPostProcessor的处理,扩展(PlaceholderConfigurerSupport , ConfigurationClassPostProcessor)
5、BeanPostProcessor的注册功能,方便后续对Bean进行功能的扩展
6、Bean初始化过程(Bean生命周期)
6.1、循环创建对象,因为容器中的Bean都是单例的,所以先用getBean,doGetBean进行查找,如果找不到就通过createBean,doCreateBean方法,以反射的方式去创建对象,把BeanDefinition对象实例化为一个具体的bean对象
6.2、调用populateBean方法进行对象属性填充(此处涉及循环依赖问题)
6.3、调用aware接口相关方法:invokeAwareMethod(完成BeanName,BeanFactory,BeanClassLoader对象属性设置)
6.4、调用BeanPostProcessor的前置处理方法:使用较多的有(ApplicationContextPostProcessor,设置ApplicationContext,Environment,ResourceLoader,EmbeddValueResolver等对象)
6.5、执行init-method方法:initInvokeMethod,判断是否实现了InitializationBean接口,如果实现了,调用afterpropertiesSet方法,如果没有不调用
6.6、调用BeanPostProcessor的后置处理方法:springAOP就是在此处实现的,AbstractAutoProxyCreator
6.7、注册必要的Destruction相关回调接口
6.8、获取到完整的对象,可以通过getBean方法直接获取
6.9、销毁流程:1、判断是否实现了DispoableBean接口 2、调用destoryMethod方法
6.2提到了循环依赖问题,简单说一下
循环依赖问题的产生:A、B对象相互引用形成闭环,导致循环依赖问题
其实对象是存在的,不过此时不是完整的状态,只完成了实例化未完成初始化
以两个Bean A和B相互依赖为例,Spring的处理流程如下:
-
实例化Bean A,此时A还未完成属性填充和初始化。
-
将A的ObjectFactory放入三级缓存singletonFactories中。
-
在填充A的属性时,发现A需要依赖Bean B,于是开始创建Bean B。
-
实例化Bean B,此时B也未完成属性填充和初始化。
-
将B的ObjectFactory放入三级缓存singletonFactories中。
-
在填充B的属性时,发现B需要依赖Bean A,此时从三级缓存中获取A的ObjectFactory,然后从中获取A的早期引用。
-
将A的早期引用放入二级缓存earlySingletonObjects中,并从三级缓存中移除A的ObjectFactory。
-
将A的早期引用注入到B中,完成B的属性填充和初始化,然后将B放入一级缓存singletonObjects中。
-
返回到A的创建过程,注入完整的B,完成A的属性填充和初始化,然后将A也放入一级缓存singletonObjects中。
查找缓存的顺序(一级缓存、二级缓存、三级缓存 (一级缓存存放的是完整对象、二级缓存存放的是非完整对象)),三级缓存的value类型是ObjectFactory,是一个函数式接口,三级缓存保证在整个容器运行过程中同名的bean对象只能有一个。
开发POJO类——项目逻辑
这里我们简单创建出A
和B
两个类,代表我们日常开发中的各种具体业务逻辑。在这里它们没有什么复杂的业务逻辑,仅仅是用于举例。
类A
:
public class A {
private String fieldA;
private B b;
public String getFieldA() {
return fieldA;
}
public void setFieldA(String fieldA) {
this.fieldA = fieldA;
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
类B
:
public class B {
private String fieldB;
public String getFieldB() {
return fieldB;
}
public void setFieldB(String fieldB) {
this.fieldB = fieldB;
}
}
1. Spring IoC配置及使用
当我们完成了项目相关Java类的开发后,需要通过配置文件将项目中各种类声明为Bean,并配置好它们之间的依赖关系。随后Spring IoC容器/工厂加载这些配置文件进行Bean的管理。
根据配置文件的不同,后续的加载和使用流程都有些区别。这里我们分别以XML文件和Java配置文件为例,介绍它们的使用流程。
1.1 XML配置
1、创建XML配置文件
我们创建一个SpringApplication.xml
文件,用于声明项目中类A
和类B
为Bean以及它们之间的依赖关系。
<?xml version="1.0" encoding="UTF-8"?>
<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 id="beanA" class="com.xianhuii.spring.bean.A">
<property name="fieldA" value="A的属性"></property>
<property name="b" ref="beanB"></property>
</bean>
<bean id="beanB" class="com.xianhuii.spring.bean.B">
<property name="fieldB" value="B的属性"></property>
</bean>
</beans>
2、加载及使用
对于XML配置文件,我们需要使用ClassPathXmlApplicationContext
容器/工厂实现类对配置文件进行加载,然后从该容器中获取Bean并使用。
public class SpringApplication {
public static void main(String[] args) {
// 加载XML配置文件
ApplicationContext beanFactory = new ClassPathXmlApplicationContext("SpringApplication.xml");
// 获取并使用Bean
A beanA = (A)beanFactory.getBean("beanA");
System.out.println(beanA.getFieldA());
System.out.println(beanA.getB().getFieldB());
}
}
运行该程序,最后的输出如下:
A的属性
B的属性
1.2 Java配置类
1、创建Java配置类
同样,我们可以使用Java配置类声明Bean以及它们之间的依赖关系。Java配置类本质上与XML配置文件没有任何区别。
@Configuration
public class SpringApplicationConfig {
@Bean("beanB")
public B b() {
B b = new B();
b.setFieldB("B的属性");
return b;
}
@Bean("beanA")
public A a(){
A a = new A();
a.setFieldA("A的属性");
a.setB(b());
return a;
}
}
2、加载及使用
对于Java配置类,我们需要使用AnnotationConfigApplicationContext
容器/工厂实现类进行加载,后续也使用该类对象获取并使用Bean。
public class SpringApplication {
public static void main(String[] args) {
// 加载Java配置类
ApplicationContext beanFactory = new AnnotationConfigApplicationContext(SpringApplicationConfig.class);
// 获取并使用Bean
A beanA = (A)beanFactory.getBean("beanA");
System.out.println(beanA.getFieldA());
System.out.println(beanA.getB().getFieldB());
}
}
最后得到的结果与之前一样:
A的属性
B的属性
1.3 注解配置
1、直接在POJO类中声明为Bean并指定依赖关系
上述两种方法将Bean的声明和依赖关系的配置集合到了特定的文件中。实际上我们可以在项目开发过程中,直接通过注解的方式指定依赖关系。因此,我们可以将类A
和B
进行修改。
类A
:
@Component("beanA")
public class A {
@Value("A的属性")
private String fieldA;
@Autowired
private B b;
public String getFieldA() {
return fieldA;
}
public void setFieldA(String fieldA) {
this.fieldA = fieldA;
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
类B
:
@Component("beanB")
public class B {
@Value("B的属性")
private String fieldB;
public String getFieldB() {
return fieldB;
}
public void setFieldB(String fieldB) {
this.fieldB = fieldB;
}
}
2、加载及使用
因为我们使用注解进行配置,可以直接使用AnnotationConfigApplicationContext
容器/工厂实现类对指定包进行扫描,它会将这个包里的所有Bean加载到容器中进行管理。后续我们也可以使用这个容器获取并使用Bean。
public class SpringApplication {
public static void main(String[] args) {
// 扫描指定包下的Bean
ApplicationContext beanFactory = new AnnotationConfigApplicationContext("com.xianhuii.spring.bean");
// 获取并使用Bean
A beanA = (A)beanFactory.getBean("beanA");
System.out.println(beanA.getFieldA());
System.out.println(beanA.getB().getFieldB());
}
}
更麻烦一点,我们也可以将ComponentScan
属性配置到XML配置文件或Java配置类中,然后通过上述方法使用特定容器/工厂实现类对XML配置文件或Java配置类进行加载和使用。这种方法实际上是为多种场景提供了组合的可能。
XML配置文件中配置ComponentScan
属性:
<?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: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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.spring.bean"/>
</beans>
Java配置类中配置ComponentScan
属性:
@Configuration
@ComponentScan("com.spring.bean")
public class SpringApplicationConfig {
}
总结
以上对Spring IoC的基本使用流程及各种配置方式进行了简要介绍。从中我们可以发现对于Sprig IoC开发而言,它包含了三个要素:POJO类、配置文件和对应的容器/工厂实现类。
POJO类代表了实际项目的业务逻辑,与Spring IoC容器的关系不大。
配置文件代表了我们需要将哪些POJO类交给Spring容器管理,并且定义了它们之间的依赖关系。配置文件可以是XML文件或Java配置类,也可以直接通过注解的方式在POJO类中进行配置。
容器/工厂实现类负责处理特定的配置方式:
- 对于XML配置文件,使用
ClassPathXmlApplicationContext
; - 对于Java配置类或注解方式,使用
AnnotationConfigApplicationContext
。
本质上Java配置类和注解方式没有任何区别,因为Java配置类也需要声明@Configuration
注解,因此它们使用的容器/工厂实现类是一样的。
2.Spring IoC配置
通过上述的介绍我们知道,对Bean的配置是Spring IoC开发过程中的主要工作,它将POJO与Spring容器关联起来,是Spring容器获取Bean信息的入口。
对Bean的配置可以分成以下方式:
- XML配置文件
- @Annotation方式:
- Java配置类
- POJO注解声明
我们首先创建出两个POJO类,作为后续介绍的工具。
类A
:
public class A {
private String fieldA;
private B b;
private List<String> list;
private Set<String> set;
private Map<String, String> map;
public String getFieldA() {
return fieldA;
}
public void setFieldA(String fieldA) {
this.fieldA = fieldA;
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public Set<String> getSet() {
return set;
}
public void setSet(Set<String> set) {
this.set = set;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
类B
:
public class B {
private String fieldB;
public B() {
}
public B(String fieldB) {
this.fieldB = fieldB;
}
public String getFieldB() {
return fieldB;
}
public void setFieldB(String fieldB) {
this.fieldB = fieldB;
}
}
2.1 XML配置文件
XML配置文件的形式相对来说比较古老,如今在日常开发中一般不会使用,我们可以简单了解一下其使用方法。
我们首先在项目中创建一个XML文件,文件名可以自己指定。然后我们就可以在该文件的<beans></beans>
标签内使用各种标签声明Bean,并指它们之间的定依赖关系。
<?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: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 https://www.springframework.org/schema/context/spring-context.xsd">
</beans>
后续我们可以使用ClassPathXmlApplicationContext
容器加载该配置文件,从而实现对Bean的管理和使用。
2.1.1 声明Bean
我们使用<bean>
标签将POJO声明为Bean。例如我们要将类B
声明为一个Bean,可以在XML文件中配置如下:
<bean id="beanB" class="com.spring.bean.B"></bean>
此时表示Spring容器将会管理类B
,当你需要获取类B
的实例时,Spring容器会使用无参构造器生成一个单例对象。如果类B
中没有无参构造器,那么就会报错。
上述步骤相当于执行了以下代码:
B beanB = new B();
2.1.2 指定依赖关系
当我们要在配置文件中指定POJO之间相互依赖时,可以使用构造器或者setXxx()
方法注入属性。
1、构造函数指定——<constructor-arg>
默认情况下,Spring容器会通过无参构造器进行实例化。我们也可以手动指定某个构造函数进行实例化,并且在实例化过程中完成对依赖关系的注入。
例如,我们通过<constructor-arg>
标签明确指定使用类B
的B(String fieldB)
构造函数注入属性fieldB
:
<bean id="beanB" class="com.spring.bean.B">
<constructor-arg name="fieldB" value="B的属性"/>
</bean>
其中name
属性用于指定构造函数的形参,value
用于指定注入的值。
上述步骤相当于执行了以下代码:
B beanB = new B("B的属性");
2、setXxx()
函数指定——<property>
我们还可以使用不同属性的setXxx()
函数进行注入,表明在实例化完成后,继续调用setXxx()
方法进行设值。
例如,我们通过<property>
标签明确指定使用类B
的setFieldB(String fieldB)
方法注入属性fieldB
:
<bean id="beanB" class="com.spring.bean.B">
<property name="fieldB" value="B的属性"/>
</bean>
其中name
属性用于指定构造函数的形参,value
用于指定注入的值。
上述步骤相当于执行了如下代码:
B beanB = new B();
beanB.setFieldB("B的属性");
让我们开一下脑洞,如果我们对同一个属性同时使用了构造函数注入和setXxx()
方法注入,那么会怎么样呢?我们配置如下:
<bean id="beanB" class="com.spring.bean.B">
<constructor-arg name="fieldB" value="constructor"/>
<property name="fieldB" value="setter"/>
</bean>
通过ClassPathXmlApplicationContext
容器实现类加载,我们可以发现beanB
的fieldB
属性最后变成了:setter
。
这是因为上述配置相当于执行了如下代码:
B beanB = new B("constructor");
beanB.setFieldB("setter");
因此,我们可将上述两种灵活地组合起来,实现我们实际的业务需求。
2.1.3 各种类型属性的注入
上面对属性的构造函数和setXxx()
方法两种注入方式进行了介绍,代表了Spring容器会调用POJO中的不同方法对属性进行注入。但是针对具体不同类型的属性,它们的注入形式又有点区别。这种区别与注入方式无关,而与属性的类型有关。
1、注入字面量
这里的字面量包括Java中的各种基本类型和String
等常用类。我们使用<constructor-arg>
或<property>
标签的value
属性进行注入。
例如,我们对类B
的fieldB
属性注入字符串:
<bean id="beanB" class="com.spring.bean.B">
<property name="fieldB" value="B的属性"/>
</bean>
2、注入Bean
在配置文件中的Bean表示POJO对象,在日常项目中难免会有POJO之间的相互引用,这时候我们就需要使用<constructor-arg>
或<property>
标签的ref
属性进行注入。
例如,我们对类A
注入类B
的Bean引用:
<bean id="beanB" class="com.spring.bean.B"></bean>
<bean id="beanA" class="com.spring.bean.A">
<property name="b" ref="beanB"/>
</bean>
上述配置相当于如下代码:
B beanB = new B();
A beanA = new A();
beanA.setB(beanB);
3、注入List
Java中List
对其中的对象进行了包装。同样,在XML配置中使用<list>
标签,其内部可以进一步指定<value>
或<ref>
标签具体内容。
例如,我们对类A
的list
属性注入列表:
<bean id="beanA" class="com.spring.bean.A">
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
</list>
</property>
</bean>
上述配置相当于执行了以下代码:
A beanA = new A();
List<String> list = new ArrayList();
list.add("list1");
list.add("list2");
list.add("list3");
a.setList(list);
4、注入Set
注入Set
的方式与上述注入List
的方式极其类似。只不过是将<list>
标签换成了<set>
标签。
例如,我们注入类A
的set
属性:
<bean id="beanA" class="com.spring.bean.A">
<property name="set">
<set>
<value>set1</value>
<value>set2</value>
<value>set3</value>
</set>
</property>
</bean>
上述配置相当于执行了如下代码:
A beanA = new A();
Set<String> set = new LinkedHashSet();
set.add("set1");
set.add("set2");
set.add("set3");
beanA.setSet(set);
5、注入Map
Map
是键-值对集合,配置文件中使用<map>
标签表示一个Map
,并在其中使用<entry>
标签表示某一个键-值对,<entry>
的key
和value
属性分别表示键-值对的键和值。
例如,我们注入类A
的map
属性:
<bean id="beanA" class="com.spring.bean.A">
<property name="map">
<map>
<entry key="key1" value="val1"/>
<entry key="key2" value="val2"/>
</map>
</property>
</bean>
上述配置相当于执行了如下代码:
A beanA = new A();
Map<String,String> map = new LinkedHashMap();
map.put("key1", "val1");
map.put("key2", "val2");
beanA.setMap(map);
2.1.4 其他配置
1、Bean的生命周期
通过为Bean指定生命周期方法,可以在使用Bean前预先执行载入资源,在销毁Bean之前释放资源等操作。在XML中使用<bean>
标签的init-method
和destroy-method
属性分别为该Bean执行初始化方法和销毁方法。
2、组件扫描
通过配置<context:component-scan base-package=""/>
,Spring容器会扫描指定包下的Java类。如果该类声明了相关注解,那么会自动将其加入到Spring容器中进行管理。这对注解配置方式至关重要。
3、导入配置文件
通过配置<import resource=""/>
,可以将其他XML配置文件导入到本配置文件中。而通过将Java配置类声明为Bean<bean id="config" class="config.ApplicationConfig.java"/>
则可以导入Java配置类。
在日常开发中,我们可以声明一个根XML配置文件,然后通过以上方法将其他配置文件导入。最后Spring容器实现类只需要加载根XML配置文件,即可读取到所有配置信息。这种方式有利于模块化开发。
4、作用域
通过<bean scope="prototype"/>
中的scope
属性可以指定该Bean的类型:singleton
或prototype
等。
其中singleton
表明该Bean是单例的,Spring容器会保存该实例,从而保证每次获取都是同一个对象。而prototype
表明Spring容器不会保存该实例,每次获取时都会重新创建一个新的对象。
一般来说我们日常开发中遇到的Controller
或Service
等组件都是单例的,因为它们不保存状态信息,而是作为工具使用,每次使用时都是一样的。
2.2 Java配置类
Java配置类与XML配置文件的功能类似,都是通过文件的形式项目中的Bean集中起来声明和管理。日常开发中我们在配置某些框架的核心类时,一般来说都会采用此方法。
首先,我们需要在项目中创建一个类,并添加@Configuration
注解,表示其为配置类。配置类实际上是一个特殊的Bean,我们也可以从Spring容器中获取其引用,进而获取其属性或调用其方法。
@Configuration
public class SpringApplicationConfig {
}
后续我们可以使用各种方法加载此配置类:
- 通过
AnnotationConfigApplicationContext
容器实现类指定加载此配置类。 - 通过
AnnotationConfigApplicationContext
容器实现类扫描此配置类所在包。 - 通过XML配置文件声明此配置类为Bean,然后通过
ClassPathXmlApplicationContext
进行加载。
2.2.1 声明Bean
通过@Bean
对某个方法进行注解可以声明Bean:
@Bean
public B b() {
return new B();
}
如果没有显示为Bean指定id,那么默认为方法名,在这里返回的Bean的id就为b
。
除了指定id,在@Bean
注解中还可指定初始化方法initMethod
、销毁方法destroyMethod
和是否可以被自动注入autowireCandidate
。
2.2.2 依赖注入
通过Java配置类进行依赖注入的方式显而易见,就是直接使用Java的构造函数或setXxx()
方法进行赋值,较为灵活多变。
对于Java内置的基本数据类型、String
和集合等的注入,直接使用构造函数或setXxx()
方法赋值即可。而对于Bean的注入,则可能会感到有些奇怪。
第一种方法:
@Bean("beanB")
public B b() {
return new B();
}
@Bean("beanA")
public A a() {
A beanA = new A();
beanA.setB(b());
return beanA;
}
第一种方法中,我们先通过b()
方法声明了beanB
。但是在a()
方法中我们直接调用b()
,在这种情况下,由于b()
方法添加了@Bean
注解,该注解会拦截对b()
的直接调用,转而在Spring容器中寻找beanB
并赋值。因此可以保证beanB
是的作用域是正确的。但是这种方法要求beanB
和beanA
的声明在同一个Java配置类中。
我们假设将上述beanB
的声明移动到另外一个配置文件中,此时为了获取beanB
的引用,我们可以使用第二种方法:
@Bean("beanA")
public A a(B b) {
A beanA = new A();
beanA.setB(b);
return beanA;
}
此时,由于a()
方法添加了@Bean
注解,当需要获取beanA
时,首先会按照形参类型在Spring容器中找到beanB
,然后在a()
方法中进行注入。而如果beanB
没有被声明,那么就会报错。
2.2.3 其他配置
1、Bean的生命周期
如前所述,Bean的生命周期在其声明过程中通过@Bean
注解的initMethod
、destroyMethod
属性指定。
2、Bean的作用域
Bean的作用域通过@Scope
注解声明:
@Scope("prototype")
@Bean("beanB")
public B b() {
return new B("BBB");
}
3、组件扫描
通过在Java配置类上添加@ConponentScan
注解,Spring容器会扫描指定包下的Java类(默认为当前包)。如果该类声明了相关注解,那么会自动将其加入到Spring容器中进行管理。这对注解配置方式至关重要。
@ComponentScan("com.spring.bean")
@Configuration
public class SpringApplicationConfig {
}
4、导入配置文件
通过在Java配置类上添加@Import
注解可以导入额外Java配置类,添加@ImportResource
注解则可以导入额外XML配置文件。
@Import(Config2.class)
@ImportResource("SpringApplication.xml")
@Configuration
public class SpringApplicationConfig {
}
2.3 POJO注解声明
POJO注解声明方式指的是我们在开发POJO类的过程中,直接在类中通过各种注解的方式配置依赖关系。这是日常开发中使用最多的方式。
在这个过程中,由于项目中的Bean配置分散到各个POJO类中,因此组件扫描功能至关重要。此时,Spring容器实现类有以下方式读取相关配置进行加载:
- 通过XML配置文件中设置
<context:component-scan base-package=""/>
标签,随后通过ClassPathXmlApplicationContext
容器实现类加载该XML配置文件。 - 通过Java配置类中设置
@ComponentScan
注解,随后通过AnnotationConfigApplicationContext
容器实现类加载此Java配置类。 - 直接使用
AnnotationConfigApplicationContext
容器实现类扫描相关包。
2.3.1 声明Bean
在POJO对象上添加@Component
注解,即可将该类声明为一个Bean。在Spring MVC开发中,根据不同层次的语义,可以进一步使用@Controller
、@Service
、@Repository
和@Mapper
等注解。之前介绍的@Configuration
注解也可以将该类声明为一个Bean,只不过该Bean比较特殊,专门用来处理配置关系。
@Component
public class B {
}
此时,如果不在@Component
中指定id,那么默认使用类名首字母小写作为id,此时为b
。
2.3.2 依赖注入
1、注入字面量
使用@Value
注解可以为Bean注入字面量,包括各种基本数据类型和String
。
我们还可以在不同位置上使用该注解进行注入:属性或setXxx()
方法。
在属性上注入:
@Value("B的属性")
private String fieldB;
在setXxx()
方法上注入:
@Value("B的属性")
public void setFieldB(String fieldB) {
this.fieldB = fieldB;
}
2、注入Bean
通过在属性、构造函数、setXxx()
方法或配置方法上添加@Autowired
注解,会在Spring容器中找到符合条件的Bean进行注入。
通过属性注入:
@Autowired
private B b;
通过构造函数注入:
@Autowired
public A (B b) {
this.b = b;
}
通过setXxx()
方法注入:
@Autowired
public void setB(B b) {
this.b = b;
}
无论是通过构造函数或setXxx()
方法进行注入,它本质上都会根据形参的类型在Spring容器中查找相关Bean,如果找到唯一一个满足条件则顺利注入,否则报错。
如果需要特别指定注入哪个Bean,还可以同时使用@Qualifier
注解进行声明。
@Autowired
@Qualifier("beanB")
private B b;
另外,@Autowired
还可以对List
、Set
和Map
进行注入,它会在Spring容器中查找所有该实体类的Bean,并将它们添加到相应的集合中。
2.3.3 其他配置
1、Bean的作用域
通过在POJO类上添加@Scope
注解以声明其作用域:
@Scope("prototype")
@Component
public class B {
}
总结
我们对Spring IoC的各种配置方式进行了详细介绍。总的来说,配置方式可以分成三种:
- XML配置文件。
- Java配置类。
- POJO注解声明。
其中,XML配置文件是很古老的方式,现在日常开发中一般很少使用。
而Java配置类和POJO注解声明方式本质上没有区别,都是通过相关注解指定Bean之间的关系。只不过Java配置类显式将所有Bean集中到类中进行配置,而POJO注解声明方式则分散到各个POJO中。
日常开发中我们通常将Java配置类和POJO注解声明这两种方式结合使用。对于代表普通项目逻辑的POJO使用注解声明,而对于某些框架中的核心类使用Java配置类进行显式配置。因为Java配置类在Spring容器中也是Bean,我们可以使用AnnotationConfigApplicationContext
容器实现类直接扫描相关的包,就可以把所有Bean管理起来。
3 常用Spring IoC容器
Spring IoC的配置方式根本上可以分成两种:XML配置文件和注解配置。
针对于这两种不同的配置方式,Spring IoC为我们提供了不同的容器实现类。接下来我们对其中最常见的两种进行介绍:
ClassPathXmlApplicationContext
。AnnotationConfigApplicationContext
。
3.1 ClassPathXmlApplicationContext
顾名思义,ClassPathXmlApplicationContext
是针对类路径下的XML配置文件的容器实现类。它会自动将类路径补齐,从而能准确定位文件位置。这种方式主要用于测试或者读取Jar包中的配置文件。
如果加载了多个XML配置文件,那么对于同一个Bean,后面添加的XML配置文件会对之前的进行覆盖。
ClassPathXmlApplicationContext
是一站式的容器实现类,它将XML配置文件的定位、读取和加载过程封装了起来,方便我们使用,但是也缺失了灵活性。
我们可以结合使用更加底层的GenericApplicationContext
容器实现类和XmlBeanDefinitionReader
配置文件读取类进行灵活设置。
回到ClassPathXmlApplicationContext
,其使用方法十分简单,它提供了各种重载的构造方法,用于不同情况下的加载。其中,最基础的构造函数如下:
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
它的作用是根据给定的父容器,创建一个新的ClassPathXmlApplication
容器,并加载指定类路径下的XML配置文件。此时加载XML配置文件实际上只是将文件路径赋给了属性Resource[]
。
我们一般可以使用形参只有XML文件路径的构造函数,分别可以加载一个或多个XML配置文件:
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String... configLocations)throws BeansException{
this(configLocations, true, null);
}
3.2 AnnotationConfigApplicationContext
顾名思义,AnnotationConfigApplicationContext
是针对诸如@Configuration
、@Component
和@Inject
等注解的类进行加载的容器实现类。
同样,如果加载了多个@Configuration
,那么后加载的@Bean
会对之前加载的进行覆盖。
AnnotationConfigApplicationContext
也提供了很多重载的构造方法,但其核心是额外提供的两个方法:
@Override
public void register(Class<?>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
this.reader.register(componentClasses);
}
@Override
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
this.scanner.scan(basePackages);
}
这两个方法的功能分别是加载Java注解类、扫描指定包下的所有注解类。
而我们常用的构造函数,内部都会调用上述两个方法:
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
refresh();
}
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
因此,我们也可以发现AnnotationConfigApplicationContext
既可以指定加载的注解类,也可以扫描某包下的所有注解类。
4 总结
本文对Spring IoC的使用进行了详细介绍,日常开发流程中大体上可以分成三个部分:
- POJO:日常项目中的各种Java类。
- 配置文件:POJO的依赖关系。
- 容器/工厂类:管理Bean。
其中POJO是日常项目逻辑涉及到的Java类,而容器/工厂类由Spring Ioc提供,因此我们的主要工作内容就是配置POJO之间的依赖关系。
传统使用XML配置文件进行配置,而目前流行的是使用注解声明的方式配置。我们只要熟悉掌握配置Bean依赖关系的各种注解,就能够在日常开发中灵活使用Spring。
标签:容器,Java,配置文件,Spring,Bean,IOC,public,最爱问 From: https://blog.csdn.net/wswpomos/article/details/143257635