读懂Spring核心系列4(XML文件配置)

来源:互联网 发布:仓库平面图绘图软件 编辑:程序博客网 时间:2024/05/21 09:12

回顾上一篇的内容,经过3个系列的累积,我们列出的代码已经能够自动装配bean。但是美中不足的是,这些bean的类路径以及属性都是手动编写代码才能添加到容器中的。在Spring的实现中,会使用XML文档来配置我们需要的信息。所以这一次,我们结合上一篇给出的代码,将要实现使用XML来进行信息的配置。


在实现的整个过程中,大致分为3个步骤:1、找到资源,2、读取资源,3、将读取的数据注入容器。


首先需要定义资源,在一个使用Spring的程序中,配置的资源可以包括XML,properties等配置文件。所以,首先我们定义一个面向资源的接口,统一管理资源。

/** * Resource是spring内部定位资源的接口。 * @author yihua.huang@dianping.com */public interface Resource {    InputStream getInputStream() throws IOException;}

接下来就需要实现上面这个接口了,实现接口的子类需要标识资源,并且实现接口中的方法,得到输入流对象。

/** * @author yihua.huang@dianping.com */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();    }}
在这个类中,我们使用URL这个统一资源定位符来标识我们需要读取的资源。通过URL对象打开连接,我们就能读取到输入流啦。


那么上面提及的这个URL又是怎么获取到的呢?当然,我们可以指定XML文件的绝对路径来作为URL,但是这样就得依赖具体的操作系统平台啦。很明显这样的设想是不够理想的,所以我们需要一个资源加载类来专门获取XML并标识为URL。

/** * @author yihua.huang@dianping.com */public class ResourceLoader {    public Resource getResource(String location){        URL resource = this.getClass().getClassLoader().getResource(location);        return new UrlResource(resource);    }}
这个资源加载类能够返回一个UrlResource对象,并且已经实例化了UrlResource对象里面的URL。看一下它是怎么得到URL的:通过获取自己的类加载器来加载资源。location只需要指定XML文件的名称就可以了。类加载器将会在classpath指定的路径下查找名称为location的文件,并且加载它。那么,我们为Spring添加的XML配置文件就必须位于classpath指定的路径下了,不然类加载器是找不到XML文件的。

下面是资源加载的测试代码

/** * @author yihua.huang@dianping.com */public class ResourceLoaderTest {@Testpublic void test() throws IOException {ResourceLoader resourceLoader = new ResourceLoader();        Resource resource = resourceLoader.getResource("tinyioc.xml");        InputStream inputStream = resource.getInputStream();        Assert.assertNotNull(inputStream);    }}
测试通过则说明文件已经加载成功了,我们已经取到它的输入流对象。


到这一步,“找到资源”已经完成了,下面就需要“读取资源”了。同样,我们用一个接口来抽象读取资源的操作,这个接口以后会被多个子类实现,因为读取不同的文件当然要不同的读取方式,不同的子类将具体的实现不同的方式。

/** * 从配置中读取BeanDefinitionReader * @author yihua.huang@dianping.com */public interface BeanDefinitionReader {    void loadBeanDefinitions(String location) throws Exception;}

下面是实现上面接口的子类,但是注意,在下面这个类中仍然没有实现接口中的方法。第一,它只是增加了一个Map成员变量,这个Map将负责存储读取到的资源数据,hash的key作为读取到bean的id,hash的值则保存具体的数据,包括bean的类路径,名称,class对象,属性值等。BeanDefinition这个对象在前几篇就出现了,可以在前面找到它的定义。第二,增加ResourceLoader,这个对象可以取到输入流,为读取做准备。最后,新增的几个方法相信都一目了然啦。

说到底,下面这个类这是一个过渡类,它为具体的读取做一些准备而已。你也可以看做实现不同读取方式的子类抽出来的公有方法了。

/** * 从配置中读取BeanDefinitionReader *  * @author yihua.huang@dianping.com */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;    }}


接下来的类继承上面的类,它终于要具体的实现读取的方法了,也是本篇中最长的类了。先贴代码,代码后面将作简短的说明。

/** * @author yihua.huang@dianping.com */public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {super(resourceLoader);}@Overridepublic void loadBeanDefinitions(String location) throws Exception {InputStream inputStream = getResourceLoader().getResource(location).getInputStream();doLoadBeanDefinitions(inputStream);}protected void doLoadBeanDefinitions(InputStream inputStream) throws Exception {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();DocumentBuilder docBuilder = factory.newDocumentBuilder();Document doc = docBuilder.parse(inputStream);// 解析beanregisterBeanDefinitions(doc);inputStream.close();}public void registerBeanDefinitions(Document doc) {Element root = doc.getDocumentElement();parseBeanDefinitions(root);}protected void parseBeanDefinitions(Element root) {NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;processBeanDefinition(ele);}}}protected void processBeanDefinition(Element ele) {String name = ele.getAttribute("name");String className = ele.getAttribute("class");        BeanDefinition beanDefinition = new BeanDefinition();        processProperty(ele,beanDefinition);        beanDefinition.setBeanClassName(className);getRegistry().put(name, beanDefinition);}    private void processProperty(Element ele,BeanDefinition beanDefinition) {        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");                beanDefinition.getPropertyValues().addPropertyValue(new PropertyValue(name,value));            }        }    }}

方法的调用从上到下,其实非常清晰了。这里简短描述下过程。先贴一个XML文件示例。

<beans>
    <bean name="helloWorldService" class="us.codecraft.tinyioc.HelloWorldService">
        <property name="text" value="Hello World!"></property>
    </bean>
</beans>

我把文件的命名空间之类的删除了,保持简洁会容易看一些。

1、loadBeanDefinitions方法取到了输入流,调用doLoadBeanDefinitions。

2、doLoadBeanDefinitions构建出DOM对象,关闭输入流,调用registerBeanDefinitions。

3、registerBeanDefinitions取到根节点,这个对应上面XML的beans节点,然后调用parseBeanDefinitions。

4、parseBeanDefinitions遍历子节点,找到bean节点,调用processBeanDefinition。

5、processBeanDefinition取到bean节点的name属性作为id,class属性作为类全名,调用processProperty构建BeanDefinition。

6、processProperty取到bean节点的子节点,也就是property节点啦,然后读取到属性名称与值。


到此为止,“读取资源”已经完成了,测试的代码如下:

/** * @author yihua.huang@dianping.com */public class XmlBeanDefinitionReaderTest {@Testpublic void test() throws Exception {XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");Map<String, BeanDefinition> registry = xmlBeanDefinitionReader.getRegistry();Assert.assertTrue(registry.size() > 0);}}


最后“将读取的数据注入容器”已经非常简单了,因为上几篇文章里面已经完成了注入容器的过程了。看一看测试的代码就明白了。

/** * @author yihua.huang@dianping.com */public class BeanFactoryTest {@Testpublic void test() throws Exception {// 1.读取配置XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");// 2.初始化BeanFactory并注册beanBeanFactory beanFactory = new AutowireCapableBeanFactory();for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());}// 3.获取beanHelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");helloWorldService.helloWorld();}}
测试代码中第2步的循环操作就注入容器了。


到此为止,本篇需要完成的目标就实现了。这下子有了XML配置文件,终于有点像正经的Spring了。老惯例在最后给出UML相关代码的UML。因为上一篇给出的UML已经包括了之前的代码,今天的UML就只包括本篇中新出现的代码啦。








0 0