Spring Beans初始化
在Spring Framework
框架中,各个对象都被当为Bean
来进行管理,具体由IoC
容器来进行初始化、存储及销毁等。
在IoC
容器内部,这类Bean
具体由BeanDefinition
来表示,该对象定义了如下信息:
- 全路径类名
Bean
的行为特性(如:scope范围、生命周期回调等)Bean
的依赖信息- 其它配置信息
1. Bean的定义
在Spring Framework
框架中,每一个Bean
都有一个或多个唯一的标识。可以使用传统的XML
的形式配置Bean
,也可以使用Java Config
的形式来配置Bean
。
在XML
的元信息配置中,可以使用id
或name
来标识Bean
。其中,name
可以设置多个值,用,
或;
或空格隔开。不指定id
或name
时,Spring
会生成一个默认的标识。
自动生成Bean的唯一标识,遵循Java标准规则,即使用
java.beans.Introspector#decapitalize
方法,若前两个字母都是大写,则不会变换,否则,将首字母小写
2. 初始化Bean
Bean
的初始化主要有以下三种方式:
- 构造方法
- 静态工厂方法
- 工厂方法
首先,来看构造器初始化的方式
新建maven工程,并引入Spring Framework相关依赖,如下:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<springFramework.version>5.2.0.RELEASE</springFramework.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springFramework.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
引入spring-context
模块实际会引入Spring Framework好几个模块,如:spring-core
、spring-beans
、spring-aop
、spring-expression
等。
2.1 构造器方式
首先来看XML的形式,创建一个普通的PoJo对象,如下:
package com.vielat.springframework.bean.service;
/**
* TODO
*
* @author Michael
* @since 2021/12/3
*/
public class ConstructorClientService {
}
在src/main/resources
目录下新建META-INF/applicationContext.xml
文件,内容如下:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="service" class="com.vielat.springframework.bean.service.ConstructorClientService" />
</beans>
在src/test/java
目录下编写Junit测试类,如下:
public class XmlApplicationDemo {
@Test
public void testConstructorInitializing(){
ApplicationContext ac = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
System.out.println(ac.getBean("service"));
}
}
运行Junit测试类后,执行结果如下:
com.vielat.springframework.bean.service.ConstructorClientService@77ec78b9
Process finished with exit code 0
若为该PoJo添加有参的构造函数,如下:
public class ConstructorClientService {
public ConstructorClientService(String name){
}
}
再次执行Junit测试类,则会抛出如下异常:
十二月 03, 2021 7:57:00 上午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service' defined in class path resource [META-INF/applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.vielat.springframework.bean.service.ConstructorClientService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.vielat.springframework.bean.service.ConstructorClientService.<init>()
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service' defined in class path resource [META-INF/applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.vielat.springframework.bean.service.ConstructorClientService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.vielat.springframework.bean.service.ConstructorClientService.<init>()
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1320)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1214)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
at com.vielat.springframework.bean.XmlApplicationDemo.testConstructorInitializing(XmlApplicationDemo.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.vielat.springframework.bean.service.ConstructorClientService]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.vielat.springframework.bean.service.ConstructorClientService.<init>()
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1312)
... 38 more
Caused by: java.lang.NoSuchMethodException: com.vielat.springframework.bean.service.ConstructorClientService.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78)
... 39 more
由上述日志可以看到,在XML中的bean标签若只配置id和class属性(其中id属性可以省略),会直接使用class对应对象的无参构造器进行初始化,若该对象没有提供无参构造器,则会抛出异常。若想使用带参数的构造器进行初始化,需要在XML配置中进行相应的配置,如下:
...
<bean id="service" class="com.vielat.springframework.bean.service.ConstructorClientService">
<constructor-arg name="name" value="Michael" />
</bean>
上述配置,除了使用name字段属性,还可以使用index下标的方式进行指定。
除此之外,在Spring Framework
3.1之后,还提供一种更加简化的命名空间的形式,如下:
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="service" class="com.vielat.springframework.bean.service.ConstructorClientService">
<constructor-arg name="name" value="Michael" />
</bean>
<bean id="service2" class="com.vielat.springframework.bean.service.ConstructorClientService" c:name="Michael">
</bean>
</beans>
需要在beans签名上新增xmlns:c="http://www.springframework.org/schema/c",在指定构造参数时可以直接配置为c:属性名的形式。
2.2 静态工厂方式
编写一个静态工厂类,如下:
public class ClientService {
private static final ClientService service = new ClientService();
public static ClientService getInstance() {
return service;
}
public ClientService getService(){
return service;
}
}
XML配置信息如下:
<bean name="clientService" class="com.vielat.springframework.bean.service.ClientService" factory-method="getInstance" />
测试类如下:
@Test
public void testStaticFactoryInitializing(){
ApplicationContext ac = new ClassPathXmlApplicationContext("META-INF/application.xml");
System.out.println(ac.getBean("clientService"));
}
2.3 工厂方法
与静态工厂方法类似,也是通过FactoryBean来初始化对象,只不过初始化方法没有static进行修饰。
工厂方法定义如下:
public class DefaultServiceLocator {
public ClientService createClientService() {
return new ClientService();
}
}
XML定义如下:
<bean id="defaultServiceLocator" class="com.vielat.springframework.bean.service.DefaultServiceLocator"/>
<bean id="serviceLocator" factory-bean="defaultServiceLocator" factory-method="createClientService" />
测试类如下:
@Test
public void testFactoryInitializing() {
ApplicationContext ac = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
System.out.println(ac.getBean("serviceLocator"));
}
无论是静态工厂还是普通工厂类,均可以初始化多个
Bean
,只需在工厂类中定义多个方法即可,代码略。这类工厂即被称之为FactoryBean