Spring初探随笔(1)

近期希望能够对Spring有一个系统性的学习,由于现在最新版本的Spring代码量已经比较庞大,希望通过使用早期版本Spring作为蓝本来学习和使用的方法,以便快速上手,另外可以更容易的对源码进行分析和解读。

总体上打算从Spring的内核Beans框架开始,学习使用第一个范例。

环境搭建
  • 从Oracle站点上下载j2sdk 1.3.1, 以及j2ee 1.3, 并安装到本地 (很复古的节奏吐舌头,只是为了配合Spring v0.9的版本来使用)
  • 从SourceForge网站上下载Spring v0.9 release
  • 在Eclipse里面新建Java工程,添加build依赖项,Spring v0.9 的所有lib, Java 1.3 JRE Lib, 添加j2ee 1.3 lib目录下的j2ee.jar
代码实现

代码很简单,通过beans XML配置来加载和使用Role这个类

Role.java
package test.beans;

public class Role {
    private String name;
    
    public String getName() {
        return name;
    }
    
    public String setName() {
        this.name = name;
    }
}
Main.java
package test.beans;

import com.interface21.beans.factory.xml.XmlBeanFactory;

public class Main {
    public static void main(String[] args) {
         try {
             XmlBeanFactory factory = new XmlBeanFactory("bin/my_bean.xml");
             Role t = (Role) factory.getBean("teacher");
             System.out.println(t.getName());
         } catch (Throwable e) {
         }
    }
}

my_beans.xml
<?xml version="1.0"?>
<beans>
    <bean id="teacher" class="test.beans.Role">
        <property name="name">
            <value>Teacher</value>
        </property>
    </bean>
</beans>

运行Main.java,结果出现下面的错误信息,

java.lang.NoSuchMethodError
at com.interface21.beans.factory.xml.XmlBeanFactory$BeansErrorHandler.warning(XmlBeanFactory.java:460)
at org.apache.crimson.parser.Parser2.warning(Parser2.java:3153)
at org.apache.crimson.parser.Parser2.parseInternal(Parser2.java:491)
at org.apache.crimson.parser.Parser2.parse(Parser2.java:305)
at org.apache.crimson.parser.XMLReaderImpl.parse(XMLReaderImpl.java:442)
at org.apache.crimson.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:185)
at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:122)
at com.interface21.beans.factory.xml.XmlBeanFactory.loadBeanDefinitions(XmlBeanFactory.java:199)
at com.interface21.beans.factory.xml.XmlBeanFactory.<init>(XmlBeanFactory.java:124)
at com.interface21.beans.factory.xml.XmlBeanFactory.<init>(XmlBeanFactory.java:136)
at test.beans.Main.main(Main.java:10)

原本以为很顺理成章就能运行成功第一个例子,然而还是遇到问题,先试着定位一下,查看XmlBeanFactory.java代码,代码中有以下的片段,

private void loadBeanDefinition(InputStream is) throws BeansException {
    ....
    try {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(true);
        DocumentBuilder db = factory.newDocumentBuilder();
        db.setErrorHandler(new BeansErrorHandler());
        db.setEntityResolver(new BeansDtdResolver());
        Document doc = db.parse(is);
        loadBeanDefinitions(doc);
    } catch (ParserConfigurationException ex) {
    }
    ...
}

这里实现设置了错误处理回调BeansErrorHandler,于是在该类中设置断点来查看错误,以debug方式启动,在BeansErrorHandler中触发断点,

查看错误,显示"valid documents must have a <!DOCTYPE declaration"

因此问题是出在my_beans.xml的定义文件格式上,增加上面所提到的内容如下,

<?xml version="1.0"?>
<!DOCTYPE beans SYSTEM "spring-beans.dtd">
<beans>
    <bean id="teacher" class="test.beans.Role">
        <property name="name">
            <value>Teacher</value>
        </property>
    </bean>
</beans>

再次运行,结果出现了另外的异常,Relative URI "spring-beans.dtd"; can not be resolved without a base URI.

com.interface21.beans.factory.BeanDefinitionStoreException: XML document is invalid; nested exception is: 
org.xml.sax.SAXParseException: Relative URI "spring-beans.dtd"; can not be resolved without a base URI.
at com.interface21.beans.factory.xml.XmlBeanFactory.loadBeanDefinitions(XmlBeanFactory.java:206)
at com.interface21.beans.factory.xml.XmlBeanFactory.<init>(XmlBeanFactory.java:124)
at com.interface21.beans.factory.xml.XmlBeanFactory.<init>(XmlBeanFactory.java:136)
at test.beans.Main.main(Main.java:10)

在网上搜索类似的异常及解决方案,找到了几个相关联的链接在讨论类似的问题,提到在DOCTYPE标签中使用了spring官方站点的DTD文件链接,提出问题如何改造为本地DTD位置,这样可以避免不必要的联网下载。

另外,还有一个链接给出了两个解决方案,http://www.jdom.org/pipermail/jdom-interest/2002-November/011086.html

首先,确保DTD文件在相对于XML文件的相对路径上能够找到,设置SAX InputSource的System ID以使其解析器能够定位到XML文件,以此路径为base URI来定位DTD文件的位置。

或者,更普遍的方案,实现一个org.xml.sax.EntityResolver并将其注册到SAXBuilder,通过这样的方式,定位DTD文件位置这件事情就delegate给应用程序自身,所以DTD文件可以放置在任意方便的位置,例如打包在程序的Jar文件中。

其实,仔细阅读前面的代码,就会发现Spring就是采用的第二种方式,

        db.setEntityResolver(new BeansDtdResolver());

这里BeansDtdResolver就是负责定位DTD文件,并将文件stream返回给XML解析器。代码如下:

public class BeansDtdResolver implements EntityResolver {


	private static final String DTD_NAME = "spring-beans";


	private static final String SEARCH_PACKAGE = "/com/interface21/beans/factory/xml/";


	private Log log = LogFactory.getLog(getClass());


	public InputSource resolveEntity(String publicId, String systemId) {
		if (systemId != null && systemId.indexOf(DTD_NAME) > systemId.lastIndexOf("/")) {
			String dtdFile = systemId.substring(systemId.indexOf(DTD_NAME));
			// Search for DTD
			log.debug("Trying to locate [" + dtdFile + "] under " + SEARCH_PACKAGE);
			InputStream in = getClass().getResourceAsStream(SEARCH_PACKAGE + dtdFile);
			if (in != null) {
				log.debug("Found DTD [" + systemId + "] in classpath");
				InputSource source = new InputSource(in);
				source.setPublicId(publicId);
				source.setSystemId(systemId);
				return source;
			}
			else {
				log.debug("Could not resolve DTD [" + systemId + "]: not found in classpath");
			}
		}
		else {
			log.debug("Ignoring DTD [" + systemId + "]");
		}
		// use the default behaviour -> download from website
		return null;
	}


}

但是,尽管如此,在这个方法中设置断点,却发现程序没有跑进去,运行结果依然出现“can not be resolved without a base URI.”的问题,后来发现需要使用spring-beans.dtd的网络URL地址才能正常工作,更新my_beans.xml文件如下,

<?xml version="1.0"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="teacher" class="test.beans.Role">
        <property name="name">
            <value>Teacher</value>
        </property>
    </bean>
</beans>

这个时候再次调试,程序就可以跑进上述的resolveEntity()方法中,通过调试,发现会在systemId参数中传入DOCTYPE标签最后一个属性值,http://www.springframework.org/dtd/spring-beans.dtd,这样在resolveEntity()方法中,它先提取出文件名sping-beans.dtd,然后和jar中打包的该文件的相对路径做拼接,再直接通过资源加载的方式去加载该dtd文件。

再试着运行程序,结果能够正常运行。

在尝试完上述基于JDK1.3的环境之后,我又试着在JDK8下面运行同样的工程(在JDK8下面,只需要加入Spring 0.9 release jar包以及JDK8 runtime lib就可以),发现直接采用下面本地路径的方式也能正常工作。

<!DOCTYPE beans SYSTEM "spring-beans.dtd">

对比解析XML的调用栈发现,JDK1.3下的XML解析和JDK8下的XML解析的调用栈有比较大的差异,也许原因就在于对XML的解析流程的差异造成了最终的运行时表现的不同。

JDK1.3下的XML解析调用栈


JDK8下的XML解析调用栈


小结

本文记录了在基于Spring v0.9版本运行的第一个程序过程中遇到的问题及对应的解决方法,主要是先做一个记录,也同时分享给遇到类似错误的同学,希望这些小的发现能够对解决类似问题提供些许帮助。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package cn.javass.spring.chapter4; import java.io.File; import java.io.IOException; import junit.framework.Assert; import org.jboss.vfs.VFS; import org.jboss.vfs.VirtualFile; import org.jboss.vfs.spi.RealFileSystem; import org.junit.Test; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; @SuppressWarnings("all") public class ResourcePatternTest { @Test public void testClasspathPrefix() throws IOException { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); //只加载一个绝对匹配Resource,且通过ResourceLoader.getResource进行加载 Resource[] resources = resolver.getResources("classpath:META-INF/INDEX.LIST"); Assert.assertEquals(1, resources.length); //只加载一个匹配的Resource,且通过ResourceLoader.getResource进行加载 resources = resolver.getResources("classpath:META-INF/*.LIST"); Assert.assertTrue(resources.length == 1); //只加载一个绝对匹配Resource,且通过ResourceLoader.getResource进行加载 resources = resolver.getResources("classpath:META-INF/MANIFEST.MF"); Assert.assertEquals(1, resources.length); } @Test public void testClasspathAsteriskPrefix() throws IOException { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); //将加载多个绝对匹配的所有Resource //将首先通过ClassLoader.getResources("META-INF")加载非模式路径部分 //然后进行遍历模式匹配 Resource[] resources = resolver.getResources("classpath*:META-INF/INDEX.LIST"); Assert.assertTrue(resources.length > 1); //将加载多个模式匹配的Resource resources = resolver.getResources("classpath*:META-INF/*.LIST"); Assert.assertTrue(resources.length > 1); } @Test public void testClasspathAsteriskPrefixLimit() throws IOException { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); //将首先通过ClassLoader.getResources("")加载目录, //将只返回文件系统的类路径不返回jar的跟路径 //然后进行遍历模式匹配 Resource[] resources = resolver.getResources("classpath*:asm-*.txt"); Assert.assertTrue(resources.length == 0); //将通过ClassLoader.getResources("asm-license.txt")加载 //asm-license.txt存在于com.springsource.net.sf.cglib-2.2.0.jar resources = resolver.getResources("classpath*:asm-license.txt"); Assert.assertTrue(resources.length > 0); //将只加载文件系统类路径匹配的Resource resources = resolver.getResources("classpath*:LICENS*"); Assert.assertTrue(resources.length == 1); } @Test public void testFilekPrefix() throws IOException { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("file:D:/*.txt"); Assert.assertTrue(resources.length > 0); } @Test public void testVfsPrefix() throws IOException { //1.创建一个虚拟的文件目录 VirtualFile home = VFS.getChild("/home"); //2.将虚拟目录映射到物理的目录 VFS.mount(home, new RealFileSystem(new File("d:"))); //3.通过虚拟目录获取文件资源 VirtualFile testFile = home.getChild("test.txt"); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("/home/test.txt"); Assert.assertTrue(resources.length > 0); System.out.println(resources[0].getClass()); } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值