Spring资源加载模块,原来XML就这,活该被注解踩在脚下 手写Spring第六篇了
这一篇让我想起来学习 Spring 的时,被 XML 支配的恐惧。明明是写Java,为啥要搞个XML呢?大佬们永远不知道,我认为最难的是 XML 头,但凡 Spring 用 JSON来做配置文件,Java 界都有可能再诞生一个扛把子。
<?xml version="1.0" encoding="UTF-8"?>
那今天我就要来盘一下,突破自己的心里障碍。把 Spring XML 的底裤都给扒掉,最后会发现,原来每个人的身上都有毛毛。
设计一下子
首先想想应该怎么设计这个模块?BeanDefinitionRegistry
这个伙计是大门的保安,把守着资源加载的大门。他的口头禅就是:穿内裤者 或 不打领带者不得入内。
作为一个专业的前端后端运维测试攻城狮,高内聚低耦合必须手到擒来,面向接口编程更是基本操作。BeanDefinitionReader
接口就是我们的协议,只要符合这个接口协议都能进入我们的大门。
这可是比武招亲严格多了,你再有本事,不按照规矩来,那也是白搭。
BeanDefinitionReader
接口一放出去,有两个年轻人,三十多岁,一个叫 XmlBeanDefinitionReader
,一个叫 ResouceLoader
,他们说要试试。这两个年轻人不知道天高地厚,以为我的类图只有这么点。实际上我只是按照传统功夫的点到为止截图而已。
不仅如此,我还有流程图。
上次我太将武德了,右眼睛被人蹭了一下。今天我 18 岁老码农是乱打的,类图,流程图,还有一个不知道什么图,训练有素。现在我就是这么不讲武德,来骗,来偷袭年轻人。你们年轻人自己耗子尾汁。
实现一下子
首先,我们来看看门面担当BeanDefinitionReader
:
public interface BeanDefinitionReader {
void loadBeanDefinitions(String location) throws IOException;
}
这家伙就一个方法,简单得很!但是别小看它,这可是整个系统的总指挥!
然后是我们的主角 XmlBeanDefinitionReader
:
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
private final BeanDefinitionRegistry beanDefinitionRegistry;
public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
this.beanDefinitionRegistry = beanDefinitionRegistry;
}
@Override
public void loadBeanDefinitions(String location) throws IOException {
ResourceLoader resourceLoader = new DefaultResourceLoader();
loadBeanDefinitions(resourceLoader.getResource(location));
}
private void loadBeanDefinitions(Resource resource) throws IOException {
InputStream inputSteam = resource.getInputSteam();
doLoadBeanDefinitions(inputSteam);
}
private void loadBeanDefinitions(Resource... resources) throws IOException {
for (Resource resource : resources) {
loadBeanDefinitions(resource);
}
}
private void doLoadBeanDefinitions(InputStream inputStream) {
Document document = XmlUtil.readXML(inputStream);
Element root = document.getDocumentElement();
NodeList childNodes = root.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node item = childNodes.item(i);
if (!(item instanceof Element)) continue;
if (!"bean".equals(item.getNodeName())) continue;
// bean 信息
Element element = (Element) item;
String id = element.getAttribute("id");
String name = element.getAttribute("name");
String className = element.getAttribute("class");
String beanName = StrUtil.isNotBlank(id) ? id : name;
Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new BeanException(e.getMessage());
}
PropertyValues propertyValues = new PropertyValues();
BeanDefinition beanDefinition = new BeanDefinition(clazz, propertyValues);
// properties 信息
NodeList propertyNodes = element.getChildNodes();
for (int j = 0; j < propertyNodes.getLength(); j++) {
Node property = propertyNodes.item(j);
if (!(property instanceof Element)) continue;
if (!"property".equals(property.getNodeName())) continue;
Element propertyElement = (Element) property;
String propertyName = propertyElement.getAttribute("name");
String value = propertyElement.getAttribute("value");
String ref = propertyElement.getAttribute("ref");
PropertyValue propertyValue;
if (StrUtil.isNotBlank(ref)) {
BeanReference beanReference = new BeanReference(ref);
propertyValue = new PropertyValue(propertyName, beanReference);
} else {
propertyValue = new PropertyValue(propertyName, value);
}
propertyValues.addPropertyValues(propertyValue);
}
// 注册
beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition);
}
}
}
资源加载皮条哥,就看你选的哪个小弟给你干活。目前他能 hold 住资源内容读取三兄弟。
public interface ResourceLoader {
Resource getResource(String location);
}
public class DefaultResourceLoader implements ResourceLoader {
private final String CLASS_PATH_PREFIX = "classpath:";
@Override
public Resource getResource(String location) {
Objects.requireNonNull(location);
if (location.startsWith(CLASS_PATH_PREFIX)) {
String name = location.substring(CLASS_PATH_PREFIX.length());
return new ClassPathResource(name, getClassLoader());
}
try {
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException e) {
return new FileSystemResource(location);
}
}
private ClassLoader getClassLoader() {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
return contextClassLoader;
}
return ClassUtil.class.getClassLoader();
}
}
资源内容读取三兄弟:
ClassPathResource
:专门找项目里的文件FileSystemResource
:负责找电脑里的文件UrlResource
:负责找网上的资源文件
public interface Resource {
InputStream getInputSteam() throws IOException;
}
public class ClassPathResource implements Resource {
private final String name;
private final ClassLoader classLoader;
public ClassPathResource(String name, ClassLoader classLoader) {
Objects.requireNonNull(name);
this.name = name;
this.classLoader = classLoader;
}
@Override
public InputStream getInputSteam() throws IOException {
InputStream inputStream = classLoader.getResourceAsStream(name);
if (Objects.isNull(inputStream)){
throw new FileNotFoundException("Not found this file: "+ name);
}
return inputStream;
}
}
public class FileSystemResource implements Resource {
private final String path;
private final File file;
public FileSystemResource(String path) {
this.path = path;
this.file = new File(path);
}
@Override
public InputStream getInputSteam() throws IOException {
return Files.newInputStream(file.toPath());
}
public String getPath() {
return path;
}
}
public class UrlResource implements Resource {
private final URL url;
public UrlResource(URL url) {
this.url = url;
}
@Override
public InputStream getInputSteam() throws IOException {
URLConnection connection = url.openConnection();
try {
return connection.getInputStream();
} catch (IOException e) {
if (connection instanceof HttpURLConnection) {
((HttpURLConnection) connection).disconnect();
}
throw e;
}
}
}
测试一下子
首先准备一张菜单吗?告诉Spring:
- 老板,来一个testDao
- 再来一个testService,要加karl调料,顺便把刚才的testDao也放进去
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="testDao" class="pri.hongweihao.smallspring.bean.TestDao"/>
<bean id="testService" class="pri.hongweihao.smallspring.bean.TestService">
<property name="name" value="karl"/>
<property name="testDao" ref="testDao"/>
</bean>
</beans>
// 模拟dao对象
public class TestDao {
public void test() {
System.out.print("testDao");
}
}
// 模拟service对象
public class TestService {
private final String name;
private final TestDao testDao;
public TestService(String name, TestDao testDao) {
this.name = name;
this.testDao = testDao;
}
public void test() {
System.out.println("testService.name: " + this.name);
this.testDao.test();
}
}
开干,我玩的就是真实
public class BeanFactoryTest {
@Test
public void test() throws IOException {
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
// 读取配置文件并自动注册
BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(defaultListableBeanFactory);
beanDefinitionReader.loadBeanDefinitions("classpath:spring.xml");
// 从工厂中获取bean对象
TestService service = (TestService) defaultListableBeanFactory.getBean("testService", "", null);
service.test();
/*
打印结果
testService.name: karl
testDao
*/
}
}
搞定!是不是感觉XML也没那么可怕了?
总结
XML配置其实就是一张"菜单":
- Spring通过【资源】和【资源加载】帮我们找到这些配置文件
- 然后通过大厨
XmlBeanDefinitionReader
解析配置 - 最后把"菜"(Bean)放到"厨房"(容器)里
本文由 https://github.com/hongweihao/small-spring/tree/6_resource_load 赞注完成
本文完 | 求赞求关注求转发 !