美文网首页
spring(二):职责分离、最少知道原则。

spring(二):职责分离、最少知道原则。

作者: 一个_人鸭 | 来源:发表于2019-10-07 15:25 被阅读0次

    从零刨析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即可。
    此时运行测试用例,便可以全部通过了。


                                                                                                    生活要多点不自量力

    相关文章

      网友评论

          本文标题:spring(二):职责分离、最少知道原则。

          本文链接:https://www.haomeiwen.com/subject/nzzeyctx.html