Spring Ioc 源码分析之Bean的加载和构造
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Ioc 源码分析之Bean的加载和构造相关的知识,希望对你有一定的参考价值。
我们都知道,Spring Ioc和Aop是Spring的核心的功能,因此花一点时间去研究还是很有意义的,如果仅仅是知其所以然,也就体会不到大师设计Spring的精华,还记得那句话,Spring为JavaEE开发带来了春天。
IOC就是Inversion of control 也就是控制反转的意思,另一种称呼叫做依赖注入,这个可能更直观一点,拿个例子来说吧:
@Component
public class UserService {
@Autowired
private UserMapper mapper;
}
比如在UserService可能要调用一个Mapper,这个Mapper去做DAO的操作,在这里我们直接通过@Autowired注解去注入这个Mapper,这个就叫做依赖注入,你想要什么就注入什么,不过前提它是一个Bean。至于是怎么注入的,那是Spring容器做的事情,也是我们今天去探索的。
在进行分析之前,我先声明一下,下面的这些代码并不是从spring 源码中直接拿过来,而是通过一步步简化,抽取spring源码的精华,如果直接贴源码,我觉得可能很多人都会被吓跑,而且还不一定能够学到真正的东西。
Spring要去管理Bean首先要把Bean放到容器里,那么Spring是如何获得Bean的呢?
首先,Spring有一个数据结构,BeanDefinition,这里存放的是Bean的内容和元数据,保存在BeanFactory当中,包装Bean的实体:
public class BeanDefinition {
//真正的Bean实例
private Object bean;
//Bean的类型信息
private Class beanClass;
//Bean类型信息的名字
private String beanClassName;
//用于bean的属性注入 因为Bean可能有很多熟属性
//所以这里用列表来进行管理
private PropertyValues propertyValues = new PropertyValues();
}
PerpertyValues存放Bean的所有属性
public class PropertyValues {
private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
}
PropertyValue存放的是每个属性,可以看到两个字段,name和valu。name存放的就是属性名称,value是object类型,可以是任何类型
public class PropertyValue {
private final String name;
private final Object value;
}
定义好这些数据结构了,把Bean装进容器的过程,其实就是其BeanDefinition构造的过程,那么怎么把一些类装入的Spring容器呢?
Spring有个接口就是获取某个资源的输入流,获取这个输入流后就可以进一步处理了:
public interface Resource {
InputStream getInputStream() throws IOException;
}
UrlResource 是对Resource功能的进一步扩展,通过拿到一个URL获取输入流。
public class UrlResource implements Resource {
private final URL url;
public UrlResource(URL url) {
this.url = url;
}
@Override
public InputStream getInputStream() throws IOException{
URLConnection urlConnection = url.openConnection();
urlConnection.connect();
return urlConnection.getInputStream();
}
ResourceLoader是资源加载的主要方法,通过location定位Resource,
然后通过上面的UrlResource获取输入流:
public class ResourceLoader {
public Resource getResource(String location){
URL resource = this.getClass().getClassLoader().getResource(location);
return new UrlResource(resource);
}
}
大家可能会对上面的这段代码产生疑问:
URL resource = this.getClass().getClassLoader().getResource(location);
为什么通过得到一个类的类类型,然后得到对应的类加载器,然后调用类加载器的Reource怎么就得到了URL这种类型呢?
我们来看一下类加载器的这个方法:
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
从这个方法中我们可以看出,类加载器去加载资源的时候,先会去让父类加载器去加载,如果父类加载器没有的话,会让根加载器去加载,如果这两个都没有加载成功,那就自己尝试去加载,这个一方面为了java程序的安全性,不可能你用户自己随便写一个加载器,就用你用户的。
接下来我们看一下重要角色,这个是加载BeanDefinition用的。
public interface BeanDefinitionReader {
void loadBeanDefinitions(String location) throws Exception;
}
这个接口是用来从配置中读取BeanDefinition:
其中registry key是bean的id,value存放资源中所有的BeanDefinition
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
private Map<String,BeanDefinition> registry;
private ResourceLoader resourceLoader;
protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
this.registry = new HashMap<String, BeanDefinition>();
this.resourceLoader = resourceLoader;
}
public Map<String, BeanDefinition> getRegistry() {
return registry;
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
}
最后我们来看一个通过读取Xml文件的BeanDefinitionReader:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
/**
* 构造函数 传入我们之前分析过的ResourceLoader 这个通过
* location 可以 加载到Resource
*/
public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
super(resourceLoader);
}
/**
* 这个方法其实是BeanDefinitionReader这个接口中的方法
* 作用就是通过location来构造BeanDefinition
*/
@Override
public void loadBeanDefinitions(String location) throws Exception {
//把location传给ResourceLoader拿到Resource,然后获取输入流
InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
//接下来进行输入流的处理
doLoadBeanDefinitions(inputStream);
}
protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {
//因为xml是文档对象,所以下面进行一些处理文档工具的构造
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
//把输入流解析成一个文档,java可以处理的文档
Document doc = docBuilder.parse(inputStream);
// 处理这个文档对象 也就是解析bean
registerBeanDefinitions(doc);
inputStream.close();
}
public void registerBeanDefinitions(Document doc) {
//得到文档的根节点,知道根节点后获取子节点就是通过层级关系处理就行了
Element root = doc.getDocumentElement();
//解析根节点 xml的根节点
parseBeanDefinitions(root);
}
protected void parseBeanDefinitions(Element root) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//element有属性的包装
if (node instanceof Element) {
Element ele = (Element) node;
processBeanDefinition(ele);
}
}
}
protected void processBeanDefinition(Element ele) {
/**
* <bean id="object***" class="com.***.***"/>
*/
//获取element的id
String name = ele.getAttribute("id");
//获取element的class
String className = ele.getAttribute("class");
BeanDefinition beanDefinition = new BeanDefinition();
//处理这个bean的属性
processProperty(ele, beanDefinition);
//设置BeanDefinition的类名称
beanDefinition.setBeanClassName(className);
//registry是一个map,存放所有的beanDefinition
getRegistry().put(name, beanDefinition);
}
private void processProperty(Element ele, BeanDefinition beanDefinition) {
/**
*类似这种:
<bean id="userServiceImpl" class="com.serviceImpl.UserServiceImpl">
<property name="userDao" ref="userDaoImpl"> </property>
</bean>
*/
NodeList propertyNode = ele.getElementsByTagName("property");
for (int i = 0; i < propertyNode.getLength(); i++) {
Node node = propertyNode.item(i);
if (node instanceof Element) {
Element propertyEle = (Element) node;
//获得属性的名称
String name = propertyEle.getAttribute("name");
//获取属性的值
String value = propertyEle.getAttribute("value");
if (value != null && value.length() > 0) {
//设置这个bean对应definition里的属性值
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
} else {
//value是Reference的话 就会进入到这里处理
String ref = propertyEle.getAttribute("ref");
if (ref == null || ref.length() == 0) {
throw new IllegalArgumentException("Configuration problem: <property> element for property ‘"
+ name + "‘ must specify a ref or value");
}
//构造一个BeanReference 然后把这个引用方到属性list里
BeanReference beanReference = new BeanReference(ref);
beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanReference));
}
}
}
}
}
上面用到了BeanReference(如下),其实这个和PropertyValue类似,用不同的类型是为了更好的区分:
public class BeanReference {
private String name;
private Object bean;
}
好了,到现在我们已经分析完了Spring是如何找到Bean并加载进入Spring容器的,这里面最主要的数据结构就是BeanDefinition,ReourceLoader来完成资源的定位,读入,然后获取输入流,进一步的处理,这个过程中有对xml文档的解析和对属性的填充。
以上是关于Spring Ioc 源码分析之Bean的加载和构造的主要内容,如果未能解决你的问题,请参考以下文章
Spring源码分析之ioc容器是如何创建和获取单实例bean的
Spring5源码分析(006)——IoC篇之核心类DefaultListableBeanFactory和XmlBeanDefinitionReader
Spring IOC 容器源码分析 - getBean调用方法解析 -- Bean的初始化之BeanPostProcessor