从零刨析spring(二)
XmlBeanDefintionReader
上一篇中已经实现了一个简易的beanFactory。现在我们对这个BeanFactory进行重构。在DefaultBeanFactory中存在了多种职责,创建bean,解析xml等等,现在我们对它进行分离职责。
我们首先把解析xml的职责分离出来,并根据最少知道原则,把原本的BeanFactory中的getBeanDefinition方法提取出来,因为用户不关心bean的定义信息是什么,想要获取的仅仅是genBean。
我们先来改造上一篇的测试用例,具体内容请查看:litespring_02
具体实现,新增加一个接口BeanDefinitionRegistry
/**
* 注册bean的定义信息
* @param id
* @param bd
*/
void registerBeanDefinition(String id, BeanDefinition bd);
/**
* 获取bean的定义
* @param BeanId
* @return
*/
BeanDefinition getBeanDefinition(String BeanId);
这个接口包含注册bean的定义信息,获取bean的定义信息。同时由DefaultBeanFactory去实现它。这个实现只是简单的get、set方法。我们再写一个xml解析的类(XmlBeanDefinitionReader),这个类的构造方法持有BeanDefinitionRegistry这个接口的具体实例,把xml解析后的信息注册到BeanDefinitionRegistry这个接口的具体实例中去。这样我们就把解析xml的职责分离了出来。
ApplicationContext
以下内容具体代码可查看:litespring_03
在日常开发中我们大部分都是直接使用ApplicationContext而不是BeanFactory,两者的区别好比一个是完整的汽车,一个是发动机。
ApplicationContext提供了更多的功能,为了提供复用性,使ApplicationContext继承BeanFactorty接口。
首先我们写一个测试用例:
@Test
public void testGetBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("petstore-v1.xml");
PetStoreService petStoreService = (PetStoreService)applicationContext.getBean( "petStore" );
Assert.assertNotNull( petStoreService );
}
同样是解析xml,并通过applicationContext获取到该bean。
为了让这段测试用例通过,我们首先创建接口ApplicationContext并继承BeanFatory。接下来我们便开始去实现ApplicationContext具体的实现类ClassPathXmlApplicationContext。这个类的实现其实和之前咱们写的测试用例差不多,我把代码贴出来。
public DefaultBeanFactory factory = null;
public AbstractApplicationContext(String configFile) {
//后面提供多种resource,会利用模版方法消除冗余代码。
factory = new DefaultBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader( factory );
reader.loadBeanDefinitions( configFile );
}
@Override
public Object getBean(String beanId) {
return this.factory.getBean( beanId );
}
这个代码实现中,我也写出来提供多种resource,目前我们实现了从Resource目录下读取文件并解析,后面我们也想通过文件的形式传入一个文件并解析。那这样我们可以实现一个Resource类。我们先写一下Resource类的测试用例:
@Test
public void testClassPathResource() throws Exception{
Resource r = new ClassPathResource("petstore-v1.xml");
InputStream is = null ;
try {
is = r.getInputStream();
//这个测试并不充分,应检查具体内容
Assert.assertNotNull(is);
}finally {
if (is != null){
is.close();
}
}
}
@Test
public void testFileSystemResource()throws Exception{
Resource r = new FileSystemResource("src" + File.separator + "test" + File.separator +"resources" + File.separator + "petstore-v1.xml");
InputStream is = null;
try {
is = r.getInputStream();
//这个测试并不充分
Assert.assertNotNull(is);
}finally {
if (is != null){
is.close();
}
}
}
同样我们首先建一个Resource接口,而它属于IO类的方法,所以我们把它放到core.io包下,它里面包含两个方法。
public InputStream getInputStream() throws IOException;
public String getDescription();
然后再创建它的实现类ClassPathResource,实现类的方法也非常简单:
public class ClassPathResource implements Resource {
private String path;
private ClassLoader classLoader;
public ClassPathResource(String path) {
this(path , (ClassLoader)null);
}
public ClassPathResource(String path, ClassLoader classLoader) {
this.path = path;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
@Override
public InputStream getInputStream() throws IOException {
//通过类加载器把文件转换成流
InputStream is = this.classLoader.getResourceAsStream( this.path );
if ( null == is ){
throw new FileNotFoundException( path + " cannot be opened" );
}
return is;
}
@Override
public String getDescription() {
return this.path;
}
}
同样我们再来实现FileSystemResource
public class FileSystemResource implements Resource {
private final String path;
private final File file;
public FileSystemResource(String path){
Assert.notNull( path , " Path must not be null ");
this.file = new File( path );
this.path = path;
}
@Override
public InputStream getInputStream() throws IOException {
//把文件转换成流
return new FileInputStream( this.path );
}
@Override
public String getDescription() {
return "file [" + this.file.getAbsolutePath() + "]";
}
}
刚工作时我还遇到过一个bug,就是因为这里的final File file。然后把spring相关的jar从tomcat的共享包中拿出来就解决了那个bug。
此时我们完成了Resource。测试用例便可以通过了。
这里我们实现了多Resource,增加了一个FileSystemResource,那我们便可以让Application多一个现实类FileSystemXmlApplicationContext了。我们先来写一个测试用例:
@Test
public void testGetBeanFromFileSystemContext(){
ApplicationContext ctx = new FileSystemXmlApplicationContext("src" + File.separator + "test" + File.separator +"resources" + File.separator + "petstore-v1.xml");
PetStoreService petStore = (PetStoreService)ctx.getBean("petStore");
Assert.assertNotNull(petStore);
}
而FileSystemXmlApplicationContext与ClassPathXmlApplication中有很多相似的地方,我们可以通过重构代码,让其相似的代码可以重复使用.
- 首先我们变更XmlBeanDefinitionReader类的loadBeanDefinitions方法,使loadBeanDefinitions方法的接收参数为Resource,这样它就可以把不同的resource解析成BeanDefinition了。
- 其次我们来增加一个抽象类AbstractApplicationContext,让它来实现ClassPathXmlApplicationContext与FileSystemXmlApplicationContext重复的代码。
public DefaultBeanFactory factory = null;
public AbstractApplicationContext(String configFile) {
//这里只是把litespring_02中的测试用例拿过来,同时支持了多种resource;
//利用模版模式消除重复代码。
factory = new DefaultBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader( factory );
Resource resource = this.getResourceByPath( configFile );
reader.loadBeanDefinitions( resource );
}
@Override
public Object getBean(String beanId) {
return this.factory.getBean( beanId );
}
protected abstract Resource getResourceByPath(String path);
这里可以看到有一个抽象方法getResourceByPath,各个Resource只需要重写这个抽象方案返回Resource即可。
此时运行测试用例,便可以全部通过了。
生活要多点不自量力
网友评论