my-mini-spring
项目的Github地址:github仓库
Gitee地址:gitee仓库
个人博客地址:sillybaka的博客
此篇所在的分支为:resource-and-loader 和 xml-define-bean
该篇1、实现了将所有资源抽象成一个Resource接口,补充了URL的不足之处
2、实现了资源加载器ResourceLoader,支持根据类路径和文件系统路径等方式获取Resource
3、实现了读取XML文件中的Bean配置
简单的IOC容器
5、资源的抽象及获取(读取XML配置文件)
5.1、Resource接口(Spring中所有资源的抽象)
**前言:**在Spring中,通常是通过XML文件来配置Bean、定义Bean的信息,同时也用来引入第三方框架的配置。那么Spring要如何为XML配置文件的加载提供支持呢?
Java的标准
java.net.URL
类和各种URL前缀的标准处理程序无法满足所有对low-level
资源的访问.比如:没有标准化的URL实现类用于获取根据
ServletContext
的类路径。并且缺少某些Spring所需要的功能,例如检测某资源是否存在等,java.net.url
只提供了基于标准URL来访问资源的方法**(实际上只针对网络上发布的web资源),而不能基于特定的路径来访问特定的资源(无法针对文件资源、类路径资源)**Java SE对于资源定位提供了URL这一类,即Uniform Resource Locator,虽然号称统一资源定位,实际上只对通过网络发布的资源提供查找和定位功能;
实际上,资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。
其次,该类的功能职责划分不清,资源的查找和表示没有清晰的界限;当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。
Resource接口
是Spring中所有资源的抽象和访问接口,用于解决URL接口的不足
Spring中Resource接口的UML关系图
使用策略模式,在获取资源时先获取当前的上下文,再根据上下文的类型来获取相应类型的Resource类对象
public interface Resource {
/**
* 判断当前资源是否存在
*/
boolean isExist();
/**
* 获取当前资源的二进制流 由具体Resource类实现
*/
InputStream getInputStream() throws FileNotFoundException;
/**
* 获取当前资源的URL
*/
URL getURL() throws FileNotFoundException;
/**
* 获取当前资源的URI
*/
URI getURI();
/**
* 获取该资源文件的名字
*/
String getFileName();
}
1、 FileSystemResource(文件系统资源)
FileSystemResource
指的是基于文件系统获取的资源,一般是服务器本地的文件资源
2、 ClassPathResource(类路径资源)
ClassPathResource
表示从类路径获取的资源,通常使用线程上下文的ClassLoader进行资源加载.
我们的Web项目通常编译后,会将class文件存储在WEB-INF/classes
下,Spring就可以通过ClassPathResource
来访问这些文件.
3、 UrlResource(Url资源)
UrlResource
表示基于URL路径获取的资源,一般是一种web资源。
5.2 ResourceLoader接口(Spring中定位资源策略的抽象)
Spring中ResourceLoader接口的UML关系图
资源是有了,但如何去查找和定位这些资源,则就是ResourceLoader的职责所在了。
ResourceLoader接口是资源查找定位策略的统一抽象
,具体的资源查找定位策略则由相应的ResourceLoader实现类给出
public interface ResourceLoader {
/**
* 根据资源路径加载相应的Resource
* @param location 资源路径
* @return
*/
Resource getResource(String location);
}
1、DefaultResourceLoader
DefaultResourceLoader是**
资源加载策略模式的实现
**,可根据路径的前缀返回不同类型的Resource
代码实现
public class DefaultResourceLoader implements ResourceLoader{
private static final String CLASSPATH_PREFIX = "classpath:";
@Override
public Resource getResource(String location) {
// 如果以classpath开头,则返回ClassPathResource
if(location.startsWith(CLASSPATH_PREFIX)){
return new ClassPathResource(location.substring(CLASSPATH_PREFIX.length()));
}else {
// 否则先尝试转换成url
try {
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException e) {
// url格式错误,再尝试使用file体系
return new FileSystemResource(location);
}
}
}
}
6、更新BeanFactory的继承关系
更新前的继承关系
6.1 HierarchicalBeanFactory(分层的BeanFactory)
HierarchicalBeanFactory
为BeanFactory提供了分层的能力,让实现这个接口的BeanFactory之间拥有层级关系(子级BeanFactory无法获得父级BeanFactory定义的Bean)
,为BeanFactory提供了可以获取父级BeanFactory
的接口
6.2 ListableBeanFactory(可列表化的BeanFactory)
实现了
ListableBeanFactory
接口的BeanFactory可以一次性列出所有需要的Bean的信息
,提供了查询Bean的能力
6.3 ConfigurableBeanFactory(可配置的BeanFactory)
ConfigurableBeanFactory
为BeanFactory提供了多个配置BeanFactory
的接口,允许框架开发者对BeanFactory进行自定义配置
6.4 AutowireCapableBeanFactory(可自动装配的BeanFactory)
AutowireCapableBeanFactory
为BeanFactory提供了自动装配Bean属性
的接口
6.5 ConfigurableListableBeanFactory
ConfigurableListableBeanFactory
接口**整合了BeanFactory所需的所有特性
,同时还提供了分析和修改Bean的工具,也提供了解决循环依赖的方法(预定义Bean实例)**
7、 读取配置文件的Bean定义
有了**
Resource和ResourceLoader
,就可以读取配置文件**,现在可以开始实现使用配置文件定义Bean的功能了
Spring中读取Bean定义的接口BeanDefinitionReader的继承关系
在本项目my-mini-spring中,只实现以XML文件的类型配置Bean的定义
所以继承关系如下:
7.1 BeanDefinitionReader
流程:
- 通过配置文件的locations,获取配置文件的资源Resource
- 读取每一个资源**Resource **
(需要ResourceLoader)
- 解析读取到的内容,转化为BeanDefinition
- 将BeanDefinition 注册到BeanDefinitionRegistry 中
(需要BeanDefinitionRegistry)
BeanDefinitionReader接口定义
public interface BeanDefinitionReader {
ResourceLoader getResourceLoader();
BeanDefinitionRegistry getBeanDefinitionRegistry();
void loadBeanDefinitions(String location);
void loadBeanDefinitions(String[] locations);
void loadBeanDefinitions(Resource resource);
void loadBeanDefinitions(Resource[] resources);
}
7.2 XMLBeanDefinitionReader(实现读取XML文件中bean的逻辑)
XML示例
<bean id="xxx" class="xxx" >
<property name="xxx" value="xxx"></property>
// 第一种 直接注入(级联)
<bean id="xxxx" class="xxxx">
</bean>
// 第二种 使用引用注入
<property name="xxx" ref="refXXX"></property>
</bean>
<bean id="refXXX" class="xxx">
</bean>
代码实现
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader{
public static final String BEAN_ELEMENT_TAG = "bean";
public static final String PROPERTY_ELEMENT_TAG = "property";
public static final String ID_ATTRIBUTE = "id";
public static final String NAME_ATTRIBUTE = "name";
public static final String CLASS_ATTRIBUTE = "class";
public static final String VALUE_ATTRIBUTE = "value";
public static final String REF_ATTRIBUTE = "ref";
public XmlBeanDefinitionReader(ResourceLoader resourceLoader, BeanDefinitionRegistry beanDefinitionRegistry) {
super(resourceLoader, beanDefinitionRegistry);
}
@Override
public void loadBeanDefinitions(Resource resource) {
try {
try (InputStream inputStream = resource.getInputStream()) {
doLoadBeanDefinitions(inputStream);
}
} catch (IOException e) {
throw new BeansException("");
}
}
/**
* 读取XML配置文件中的BeanDefinitions
* @param inputStream xml文件的二进制流
*/
public void doLoadBeanDefinitions(InputStream inputStream){
Document document = XmlUtil.readXML(inputStream);
// 根标签
Element root = document.getDocumentElement();
// 根标签下的子节点
NodeList childNodes = root.getChildNodes();
int length = childNodes.getLength();
for (int i = 0; i < length; i++) {
// 如果子节点是一个标签 则开始解析
if(childNodes.item(i) instanceof Element){
Element element = (Element) childNodes.item(i);
// 如果该标签为bean标签
if(BEAN_ELEMENT_TAG.equals(element.getTagName())){
doLoadBeanDefinition(element);
}
}
}
}
/**
* 加载指定Bean标签的beanDefinition实际逻辑
* @param beanElement bean标签
*/
public BeanDefinition<?> doLoadBeanDefinition(Element beanElement){
//解析bean标签
String id = beanElement.getAttribute(ID_ATTRIBUTE);
String name = beanElement.getAttribute(NAME_ATTRIBUTE);
String className = beanElement.getAttribute(CLASS_ATTRIBUTE);
Class clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new BeansException("不存在该类型: [ "+ className + "] 的类",e);
}
// 存放Bean定义
BeanDefinition<?> beanDefinition = new BeanDefinition<>();
beanDefinition.setType(clazz);
PropertyValues propertyValues = new PropertyValues();
// 如果id不为空 则beanName为id
String beanName = StrUtil.isNotBlank(id) ? id : name;
// 若都为空 则beanName为类名(首字母小写)
if(StrUtil.isBlank(beanName)){
beanName = className.substring(className.lastIndexOf('.'));
beanName = beanName.substring(0,1).toLowerCase(Locale.ROOT) + beanName.substring(1);
}
// 查询注册表中是否有同名的bean
if(getBeanDefinitionRegistry().containsBeanDefinition(beanName)){
throw new BeansException("不能注册重名的Bean,注册失败");
}
// 读取bean标签下的子标签(property,bean等)
NodeList subNodes = beanElement.getChildNodes();
int subLength = subNodes.getLength();
for (int j = 0; j < subLength; j++) {
if(subNodes.item(j) instanceof Element){
Element subElement = (Element) subNodes.item(j);
String subTagName = subElement.getTagName();
// 如果是property标签
if(PROPERTY_ELEMENT_TAG.equals(subTagName)){
String propertyName = subElement.getAttribute(NAME_ATTRIBUTE);
String propertyValue = subElement.getAttribute(VALUE_ATTRIBUTE);
PropertyValue pv;
// 是一个值
if(StrUtil.isNotBlank(propertyValue)){
//todo 这里的value是字符串类型 怎么转换为原类型?String可以转换成任意基本类型,而value只处理基本类型的属性
// 应该需要一个属性类型表 才方便转换
pv = new PropertyValue(propertyName, propertyValue);
PropertyUtils.addPropertyDescriptor(clazz,propertyName,null);
}else {
// 是一个引用 则propertyRef是引用的beanName
String propertyRef = subElement.getAttribute(REF_ATTRIBUTE);
BeanReference beanReference = new BeanReference(propertyRef);
// 引用作为属性值,自动装配时会注入bean实例
pv = new PropertyValue(propertyName,beanReference);
PropertyUtils.addPropertyDescriptor(clazz,propertyName,BeanReference.class);
}
// 添加属性到列表中
propertyValues.addPropertyValue(pv);
}else if(BEAN_ELEMENT_TAG.equals(subTagName)){
//todo 如果是bean标签 则创建一个内置BeanDefinition 先不处理 嵌套太多 代码复杂
// 递归处理嵌套bean
BeanDefinition<?> subBeanDefinition = doLoadBeanDefinition(subElement);
String subBeanName = subElement.getAttribute(ID_ATTRIBUTE);
PropertyValue pv = new PropertyValue(subBeanName,subBeanDefinition);
PropertyUtils.addPropertyDescriptor(clazz,subBeanName,BeanDefinition.class);
propertyValues.addPropertyValue(pv);
}
}
}
beanDefinition.setPropertyValues(propertyValues);
// 将bean定义注册进注册表中
getBeanDefinitionRegistry().registerBeanDefinition(beanName,beanDefinition);
return beanDefinition;
}
}
测试
测试用的XML文件 test1.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"
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 http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="carBean1" class="sillybaka.springframework.entity.Car">
<property name="brand" value="Benci"/>
<property name="carRoll" ref="carRoll1"/>
</bean>
<bean id="carRoll1" class="sillybaka.springframework.entity.CarRoll">
<property name="brand" value="niubiChelun"/>
</bean>
</beans>
测试用的方法
@Test
public void testXmlReader(){
ResourceLoader resourceLoader = new DefaultResourceLoader();
BeanDefinitionRegistry beanDefinitionRegistry = new DefaultListableBeanFactory();
BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(resourceLoader,beanDefinitionRegistry);
// 1、先从xml配置文件中读取Bean定义 并注册进注册表
beanDefinitionReader.loadBeanDefinitions("classpath:test1.xml");
// 2、BeanFactory根据beanName获取bean实例(懒汉式创建)
BeanFactory beanFactory = new DefaultListableBeanFactory();
Object carBean1 = beanFactory.getBean("carBean1");
System.out.println(carBean1);
beanDefinitionReader.loadBeanDefinitions("classpath:test2.xml");
Object carBean2 = beanFactory.getBean("carBean2");
System.out.println(carBean2);
}