这一篇让我想起来学习 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 赞注完成
本文完 | 求赞求关注求转发 !