[Spring] 实例化Bean的三种方法
Spring
框架的核心是IOC
容器,它是个管理对象的工厂,常说的两个专业名词“控制反转”和“依赖注入”,就是它最重要的两大特征:
- 控制反转:创建对象的权利交给
IOC
容器,程序员负责声明要创建的对象 - 依赖注入:对象之间的依赖关系由
IOC
容器负责实现,程序员负责声明这种依赖关系,依赖关系就是指对象内部有哪些属性
Spring
容器中的对象叫做Bean
,本文要讲的实例化Bean
方法,说的就是“控制反转”这个特性,程序员怎么声明Bean
,容器怎么创建Bean
。
现在Spring
已经发展到Spring Boot
阶段了,大量使用注解配置,应用起来更加高阶,程序也更加“智能”。
但是我个人感觉,可能还是XML
配置更容器理解原理特性,因此,以下程序都采用XML
配置书写。
方法一:构造器
有一个对象类定义,该对象和其它对象没有依赖关系:
package com.comeheart.spring.entity;
public class User {
}
在Spring
标准XML
配置文件中,程序员需要用<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.comeheart.entity.User"></bean>
</beans>
其中,id
属性声明对象在容器中的唯一标识符,class
属性声明对象的全路径类名。
Spring
容器读取到XML
声明后,利用java
反射机制调用User
类的构造器,实例化该对象。以下源码有省略:
package org.springframework.beans;
public abstract class BeanUtils {
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
// 省略代码
return ctor.newInstance(argsWithDefaultValues);
}
}
方法二:静态工厂方法
改造一下User
类定义,首先禁用构造器实例化,然后提供静态工厂方法返回对象实例:
package com.comeheart.spring.entity;
public class User {
private static User user = new User();
private User() {
}
public static User createInstance() {
return user;
}
}
Spring
的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="user" class="com.comeheart.spring.entity.User" factory-method="createInstance"></bean>
</beans>
class
属性和factory-method
属性结合,表示使用User
类的静态工厂方法createInstance()
创建该对象。有两点需要注意:
-
factory-method
方法返回值必须是class
属性声明的类型 -
factory-method
方法本身必须属于class
属性声明的类型
为什么factory-method
方法一定要属于class
类型?
因为Spring
的<bean/>
定义里面,没有提供类似factory-class
这样的声明,也就是不能指定工厂类,所以,默认的,factory-method
属性声明的方法就属于class
属性声明的类了。
咋一看,静态工厂方法不如构造器简洁,但是它有好处,程序员可以在IOC
容器实例化对象的过程中,更*的操作对象。
Spring
容器读取XML
配置后,判断对象声明中有factory-method
属性和class
属性,就会调用该类的工厂方法,获取对象实例:
package org.springframework.beans.factory.support;
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 省略前后代码
// mbd.getFactoryMethodName() 就对应着 factory-method 声明
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
}
}
方法三:对象工厂方法
User
类定义回归到原来的样子:
package com.comeheart.spring.entity;
public class User {
}
新增一个Locator
类定义,表示工厂对象,其中的对象工厂方法创建User
类实例:
public class Locator {
private static User user = new User();
public User createUserInstance() {
return user;
}
}
此时,Locator
内部需要有一个实实在在的User
对象,这种“需要”就叫做“依赖”,Locator
类依赖User
类。
这种依赖关系如果交给Spring
容器去处理,就是“依赖注入”的概念。
在这里,我并没有这么做,依赖关系由Locator
自己管理,自己去new User()
对象。
说回来,Spring
的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="locator" class="com.comeheart.spring.entity.Locator"></bean>
<bean id="user" factory-bean="locator" factory-method="createUserInstance"></bean>
</beans>
IOC
容器读取XML
配置后,会先使用构造器方法创建Locator
对象,然后调用Locator
对象的createUserInstance()
工厂方法获取User
对象。
静态工厂方法、对象工厂方法,Spring
实现的两者代码逻辑都是一样的。
都会先判断factory-method
逻辑,这和上一节代码相同:
package org.springframework.beans.factory.support;
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 省略前后代码
// mbd.getFactoryMethodName() 就对应着 factory-method 声明
if (mbd.getFactoryMethodName() != null) {
return instantiateUsingFactoryMethod(beanName, mbd, args);
}
}
}
追踪其中的instantiateUsingFactoryMethod(...)
逻辑,可以得到:
package org.springframework.beans.factory.support;
class ConstructorResolver {
public BeanWrapper instantiateUsingFactoryMethod(String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
Object factoryBean;
Class<?> factoryClass;
boolean isStatic;
String factoryBeanName = mbd.getFactoryBeanName();
if (factoryBeanName != null) {
if (factoryBeanName.equals(beanName)) {
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, "factory-bean reference points back to the same bean definition");
}
factoryBean = this.beanFactory.getBean(factoryBeanName);
if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
throw new ImplicitlyAppearedSingletonException();
}
factoryClass = factoryBean.getClass();
isStatic = false;
} else {
// It's a static factory method on the bean class.
if (!mbd.hasBeanClass()) {
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, "bean definition declares neither a bean class nor a factory-bean reference");
}
factoryBean = null;
factoryClass = mbd.getBeanClass();
isStatic = true;
}
}
}
这段代码中,有两个方法域变量factoryBean
和factoryClass
:
-
factoryBean
是对象工厂方法需要的 -
factoryClass
是静态工厂方法需要的
通过factoryBeanName
做一个判断,就能确定该对象的实例化方式,是静态工厂方法,还是对象工厂方法。
容器启动与应用
容器加载配置启动成功以后,就可以使用容器提供的接口,获取各种实例化对象信息:
public class App {
public static void main(String[] args) {
// Spring容器启动
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
// 获取对象等使用方法
User user = context.getBean("user", User.class);
Class<?> clazz = context.getType("user");
}
}
上一篇: 深度优先搜索和广度优先搜索
下一篇: JavaWeb学习笔记-Web基础-03