Spring复杂的BeanFactory继承体系该如何理解?---中下

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring复杂的BeanFactory继承体系该如何理解?---中下相关的知识,希望对你有一定的参考价值。

Spring复杂的BeanFactory继承体系该如何理解?---中下


前文链接:

Spring复杂的BeanFactory继承体系该如何理解? ----上

Spring复杂的BeanFactory继承体系该如何理解? ----中


Spring IoC容器 ApplicationContext

作为Spring提供的较之BeanFactory更为先进的IoC容器实现,ApplicationContext除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessor、BeanPostProcessor以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、国际化的信息支持、容器内事件发布等。真是“青出于蓝而胜于蓝”啊!

Spring为基本的BeanFactory类型容器提供了XmlBeanFactory实现。相应地,它也为ApplicationContext类型容器提供了以下几个常用的实现

  • org.springframework.context.support.FileSystemXmlApplicationContext。在默认情况下,从文件系统加载bean定义以及相关资源的ApplicationContext实现。
  • org.springframework.context.support.ClassPathXmlApplicationContext。在默认情况下,从Classpath加载bean定义以及相关资源的ApplicationContext实现.
  • org.springframework.web.context.support.XmlWebApplicationContext。Spring提供的用于Web应用程序的ApplicationContext实现,我们将在第六部分更多地接触到它。

更多实现可以参照org.springframework.context.ApplicationContext接口定义的Javadoc,这里不再赘述。

前面两节说明了ApplicationContext所支持的大部分功能。下面主要围绕ApplicationContext较之BeanFactory特有的一些特性展开讨论,即国际化(I18n)信息支持、统一资源加载策略以及容器内事件发布等。


统一资源加载策略

要搞清楚Spring为什么提供这么一个功能,还是从Java SE提供的标准类java.net.URL说起比较好。URL全名是Uniform Resource Locator(统一资源定位器),但多少有些名不副实的味道。

首先,说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上只提供了基于HTTP、FTP、File等协议(sun.net.www.protocol包下所支持的协议)的资源定位功能。

虽然也提供了扩展的接口,但从一开始,其自身的“定位”就已经趋于狭隘了。实际上,资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。

其次,从某些程度上来说,该类的功能职责划分不清,资源的查找和资源的表示没有一个清晰的界限。当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情。

所以,在这个前提下 ,Spring提出了一套基于org.springframework.core.io.Resource和org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。


Spring中的Resource

Spring框架内部使用org.springframework.core.io.Resource接口作为所有资源的抽象和访问接口,我们之前在构造BeanFactory的时候已经接触过它,如下代码:

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("...")); 
... 

其中ClassPathResource就是Resource的一个特定类型的实现,代表的是位于Classpath中的资源。

Resource接口可以根据资源的不同类型,或者资源所处的不同场合,给出相应的具体实现。Spring框架在这个理念的基础上,提供了一些实现类(可以在org.springframework.core.io包下找到这些实现类)。

  • ByteArrayResource。将字节(byte)数组提供的数据作为一种资源进行封装,如果通过InputStream形式访问该类型的资源,该实现会根据字节数组的数据,构造相应的ByteArrayInputStream并返回。
  • ClassPathResource。该实现从Java应用程序的ClassPath中加载具体资源并进行封装,可以使用指定的类加载器(ClassLoader)或者给定的类进行资源加载。
  • FileSystemResource。对java.io.File类型的封装,所以,我们可以以文件或者URL的形式对该类型资源进行访问,只要能跟File打的交道,基本上跟FileSystemResource也可以。
  • UrlResource。通过java.net.URL进行的具体资源查找定位的实现类,内部委派URL进行具体的资源操作。
  • InputStreamResource。将给定的InputStream视为一种资源的Resource实现类,较为少用。可能的情况下,以ByteArrayResource以及其他形式资源实现代之。

如果以上这些资源实现还不能满足要求,那么我们还可以根据相应场景给出自己的实现,只需实现org.springframework.core.io.Resource接口就是了

Resource接口定义

public interface Resource extends InputStreamSource 
    boolean exists();

    default boolean isReadable() 
        return this.exists();
    

    default boolean isOpen() 
        return false;
    

    default boolean isFile() 
        return false;
    

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    default ReadableByteChannel readableChannel() throws IOException 
        return Channels.newChannel(this.getInputStream());
    

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String var1) throws IOException;

    @Nullable
    String getFilename();

    String getDescription();

public interface InputStreamSource 
    InputStream getInputStream() throws IOException;

该接口定义了很多方法,可以帮助我们查询资源状态、访问资源内容,甚至根据当前资源创建新的相对资源。

不过,要真想实现自定义的Resource,倒是真没必要直接实现该接口,我们可以继承org.springframework.core.io.AbstractResource抽象类,然后根据当前具体资源特征,覆盖相应的方法就可以了。

什么?让我给个实现的例子?算了吧,目前我还没碰到这样的需求。呵呵!要真的碰上了,你只要知道有这么“一出儿”就行了。


ResourceLoader,“更广义的URL”

资源是有了,但如何去查找和定位这些资源,则应该是ResourceLoader的职责所在了。

org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。我想,把ResourceLoader称作统一资源定位器或许才更恰当一些吧!ResourceLoader定义如下:

public interface ResourceLoader 
    String CLASSPATH_URL_PREFIX = "classpath:";

    Resource getResource(String var1);

    @Nullable
    ClassLoader getClassLoader();

其中最主要的就是Resource getResource(String location);方法,通过它,我们就可以根据指定的资源位置,定位到具体的资源实例。

1. 可用的ResourceLoader

DefaultResourceLoader

ResourceLoader有一个默认的实现类,即org.springframework.core.io.DefaultResourceLoader,该类默认的资源查找处理逻辑如下。

(1) 首先检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类型资源并返回。

(2) 否则:

(a) 尝试通过URL,根据资源路径来定位资源,如果没有抛出MalformedURLException,有则会构造UrlResource类型的资源并返回;

(b)如果还是无法根据资源路径定位指定的资源,则委派getResourceByPath(String) 方法来定位, DefaultResourceLoader 的
getResourceByPath(String)方法默认实现逻辑是,构造ClassPathResource类型的资源并返回。

DefaultResourceLoader源码:

    public Resource getResource(String location) 
        Assert.notNull(location, "Location must not be null");
        Iterator var2 = this.getProtocolResolvers().iterator();

        Resource resource;
        do 
            if (!var2.hasNext()) 
                if (location.startsWith("/")) 
                    return this.getResourceByPath(location);
                

                if (location.startsWith("classpath:")) 
                    return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
                

                try 
                    URL url = new URL(location);
                    return (Resource)(ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
                 catch (MalformedURLException var5) 
                    return this.getResourceByPath(location);
                
            

            ProtocolResolver protocolResolver = (ProtocolResolver)var2.next();
            resource = protocolResolver.resolve(location, this);
         while(resource == null);

        return resource;
    
    
    protected Resource getResourceByPath(String path) 
        return new DefaultResourceLoader.ClassPathContextResource(path, this.getClassLoader());
    

    protected static class ClassPathContextResource extends ClassPathResource implements ContextResource 
        public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) 
            super(path, classLoader);
        

        public String getPathWithinContext() 
            return this.getPath();
        

        public Resource createRelative(String relativePath) 
            String pathToUse = StringUtils.applyRelativePath(this.getPath(), relativePath);
            return new DefaultResourceLoader.ClassPathContextResource(pathToUse, this.getClassLoader());
        
    

在这个基础上,让我们来看一下DefaultResourceLoader的行为是如何反应到程序中的吧!

DefaultResourceLoader使用演示

        ResourceLoader resourceLoader = new DefaultResourceLoader();
        Resource fakeFileResource = resourceLoader.getResource("C:\\\\Users\\\\zdh\\\\Desktop\\\\hh.txt");
        System.out.println(fakeFileResource instanceof  ClassPathResource);
        System.out.println(fakeFileResource.exists());

        System.out.println("---------------------------------------------------------------------");

        Resource fakeFileResource1 = resourceLoader.getResource("file:C:\\\\Users\\\\zdh\\\\Desktop\\\\hh.txt");
        System.out.println(fakeFileResource1 instanceof FileUrlResource);
        System.out.println(fakeFileResource1.exists());
        //获取文件,输出文件内容
        File file = fakeFileResource1.getFile();
        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        String line = reader.readLine();
        System.out.println("文件内容: "+line);
        
        System.out.println("---------------------------------------------------------------------");

        Resource urlResource2 = resourceLoader.getResource("http://www.baidu.cn");
        System.out.println(fakeFileResource1 instanceof  UrlResource);
        System.out.println(fakeFileResource1.exists());
        URL url = urlResource2.getURL();
        InputStream inputStream = url.openStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String readLine = bufferedReader.readLine();
        System.out.println("URI读取到的数据第一行为: "+readLine);
        

        System.out.println("---------------------------------------------------------------------");


尤其注意fakeFileResource资源的类型,并不是我们所预期的FileSystemResource类型,而是ClassPathResource类型,这是由DefaultResourceLoader的资源查找逻辑所决定的。

如果最终没有找到符合条件的相应资源,getResourceByPath(String)方法就会构造一个实际上并不存在的资源并返回。而指定有协议前缀的资源路径,则通过URL能够定位,所以,返回的都是UrlResource类型


FileSystemResourceLoader

为了避免DefaultResourceLoader在最后getResourceByPath(String)方法上的不恰当处理,我们可以使用org.springframework.core.io.FileSystemResourceLoader,它继承自DefaultResourceLoader,但覆写了getResourceByPath(String)方法,使之从文件系统加载资源并以FileSystemResource类型返回。这样,我们就可以取得预想的资源类型。

public class FileSystemResourceLoader extends DefaultResourceLoader 
    public FileSystemResourceLoader() 
    

    protected Resource getResourceByPath(String path) 
        if (path.startsWith("/")) 
            path = path.substring(1);
        

        return new FileSystemResourceLoader.FileSystemContextResource(path);
    

    private static class FileSystemContextResource extends FileSystemResource implements ContextResource 
        public FileSystemContextResource(String path) 
            super(path);
        

        public String getPathWithinContext() 
            return this.getPath();
        
    

使用FileSystemResourceLoader

        ResourceLoader resourceLoader = new FileSystemResourceLoader();
        Resource fakeFileResource = resourceLoader.getResource("C:\\\\Users\\\\zdh\\\\Desktop\\\\hh.txt");
        System.out.println(fakeFileResource instanceof  ClassPathResource);
        System.out.println(fakeFileResource.exists());
        //获取文件,输出文件内容
        File file1 = fakeFileResource.getFile();
        BufferedReader reader1 = new BufferedReader(new InputStreamReader(new FileInputStream(file1)));
        String line1 = reader1.readLine();
        System.out.println("文件内容: "+line1);


        System.out.println("---------------------------------------------------------------------");

        Resource fakeFileResource1 = resourceLoader.getResource("file:C:\\\\Users\\\\zdh\\\\Desktop\\\\hh.txt");
        System.out.println(fakeFileResource1 instanceof FileUrlResource);
        System.out.println(fakeFileResource1.exists());
        //获取文件,输出文件内容
        File file = fakeFileResource1.getFile();
        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        String line = reader.readLine();
        System.out.println("文件内容: "+line);

        System.out.println("---------------------------------------------------------------------");


FileSystemResourceLoader在ResourceLoader家族中的兄弟FileSystemXmlApplicationContext,也是覆写了getResourceByPath(String)方法的逻辑,以改变DefaultResourceLoader的默认资源加载行为,最终从文件系统中加载并返回FileSystemResource类型的资源。


2. ResourcePatternResolver ——批量查找的ResourceLoader

ResourcePatternResolver是ResourceLoader的扩展,ResourceLoader每次只能根据资源路径返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式,每次返回多个Resource实例。

接口org.springframework.core.io.support.ResourcePatternResolver定义如下:

public interface ResourcePatternResolver extends ResourceLoader 
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    Resource[] getResources(String var1) throws IOException;

ResourcePatternResolver在继承ResourceLoader原有定义的基础上,又引入了Resource[] getResources(String)方法定义,以支持根据路径匹配模式返回多个Resources的功能。

它同时还引入了一种新的协议前缀classpath*:,针对这一点的支持,将由相应的子类实现给出。

ResourcePatternResolver最常用的一个实现是org.springframework.core.io.support. PathMatchingResourcePatternResolver,该实现类支持ResourceLoader级别的资源加载,支持基于Ant风格的路径匹配模式(类似于**/.suffix之类的路径形式),支ResourcePatternResolver新增加的classpath:前缀等,基本上集所有技能于一身。

在构造PathMatchingResourcePatternResolver实例的时候,可以指定一个ResourceLoader,如果不指定的话,则PathMatchingResourcePatternResolver内部会默认构造一个DefaultResourceLoader实例。PathMatchingResourcePatternResolver内部会将匹配后确定的资源路径,委派给它的ResourceLoader来查找和定位资源。

这样,如果不指定任何ResourceLoader的话,PathMatchingResourcePatternResolver在加载资源的行为上会与DefaultResourceLoader基本相同,只存在返回的Resource数量上的差异。如下代码表明了二者在资源加载行为上的一致性:

ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); 
Resource fileResource = resourceResolver.getResource("D:/spring21site/README"); 
assertTrue(fileResource instanceof ClassPathResource); 
assertFalse(fileResource.exists()); 

不过,可以通过传入其他类型的ResourceLoader来替换PathMatchingResourcePatternResolver内部默认使用的DefaultResourceLoader,从而改变其默认行为。

比如使用FileSystemResourceLoader替换默认的DefaultResourceLoader,从而使得PathMatchingResourcePatternResolver的行为跟使用FileSystemResourceLoader一样。

替换DefaultResourceLoader后的PathMatchingResourcePatternResolver

public void testResourceTypesWithPathMatchingResourcePatternResolver() 
 
 ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); 
 Resource fileResource = resourceResolver.getResource("D:/spring21site/README"); 
 assertTrue(fileResource instanceof ClassPathResource); 
 assertFalse(fileResource.exists()); 
 
resourceResolver = new PathMatchingResourcePatternResolver(newFileSystemResourceLoader()); 
 fileResource = resourceResolver.getResource("D:/spring21site/README"); 
 assertTrue(fileResource instanceof FileSystemResource); 
 assertTrue(fileResource.exists()); 
 

3. 回顾与展望

现在我们应该对Spring的统一资源加载策略有了一个整体上的认识


虽然现在看来比较“单薄”,不过,稍后,我们就会发现情况并非如此了。


ApplicationContext与ResourceLoader

说是讲 ApplicationContext的统一资源加载策略,到目前为止却一直没有涉及任何ApplicationContext相关的内容,不知道你是否开始奇怪了呢?

实际上,我是有意为之,就是不想让各位因为过多关注ApplicationContext,却忽略了事情的本质。

如果回头看一下此图,

就会发现,ApplicationContext继承了ResourcePatternResolver,当然就间接实现了ResourceLoader接口。所以,任何的ApplicationContext实现都可以看作是一个ResourceLoader甚至ResourcePatternResolver。而这就是ApplicationContext支持Spring内统一
资源加载策略的真相。

通常,所有的ApplicationContext实现类会直接或者间接地继承org.springframework.context.support.AbstractApplicationContext,从这个类上,我们就可以看到ApplicationContext与ResourceLoader之间的所有关系。

AbstractApplicationContext继承了DefaultResourceLoader,那么,它的getResource(String)当然就直接用DefaultResourceLoader的了。

剩下需要它“效劳”的,就是ResourcePatternResolver的Resource[] getResources (String),当然,AbstractApplicationContext也不负众望,当即拿下。

AbstractApplicationContext类的内部声明有一个resourcePatternResolver,类型是ResourcePatternResolver,对应的实例类型为
PathMatchingResourcePatternResolver 。

之前我们说过 PathMatchingResourcePatternResolver构造的时候会接受一个ResourceLoader,而AbstractApplicationContext本身又继承自DefaultResourceLoader,当然就直接把自身给“贡献”了。

这样,整个ApplicationContext的实现类就完全可以支持ResourceLoader或者ResourcePatternResolver接口,你能说ApplicationContext不支持Spring的统一资源加载吗?

说白了,ApplicationContext的实现类在作为ResourceLoader或者ResourcePatternResolver时候的行为,完全就是委派给了PathMatchingResourcePatternResolver和DefaultResourceLoader来做。

有了这些做前提,让我们看看作为ResourceLoader或者ResourcePatternResolver的ApplicationContext,到底因此拥有了何等神通吧!


1. 扮演ResourceLoader的角色

既然ApplicationContext可以作为ResourceLoader或者ResourcePatternResolver来使用,那么,很显然,我们可以通过ApplicationContext来加载任何Spring支持的Resource类型。

与直接使用ResourceLoader来做这些事情相比,很明显,ApplicationContext的表现过于“谦虚”了。

下面演示的正是“大材小用”后的ApplicationContext

以ResourceLoader身份登场的ApplicationContext

ResourceLoader resourceLoader = new ClassPathXmlApplicationContext("配置文件路径"); 
// 或者
// ResourceLoader resourceLoader = new FileSystemXmlApplicationContext("配置文件路径"); 
Resource fileResource = resourceLoader.getResource("D:/spring21site/README"); 
assertTrue(fileResource instanceof ClassPathResource); 
assertFalse(fileResource.exists()); 
Resource urlResource2 = resourceLoader.getResource("http://www.spring21.cn"); 
assertTrue(urlResource2 instanceof UrlResource); 

我想这样的使用场景,你一定比我先猜到,不是吗?


2. ResourceLoader类型的注入

在大部分情况下,如果某个bean需要依赖于ResourceLoader来查找定位资源,我们可以为其注入容器中声明的某个具体的ResourceLoader实现,该bean也无需实现任何接口,直接通过构造方法注入或者setter方法注入规则声明依赖即可,这样处理是比较合理的。不过,如果你不介意你的bean定义依赖于Spring的API,那不妨考虑用一下Spring提供的便利。

前面两节中曾经提到几个对ApplicationContext特定的Aware接口,这其中就包括ResourceLoaderAware和ApplicationContextAware接口。

依赖于ResourceLoader的实例类

public class FooBar  
private ResourceLoader resourceLoader; 
 
 public void foo(String location) 
  
 System.out.println(getResourceLoader().getResource(location).getClass(以上是关于Spring复杂的BeanFactory继承体系该如何理解?---中下的主要内容,如果未能解决你的问题,请参考以下文章

Spring复杂的BeanFactory继承体系该如何理解? ----中

Spring复杂的BeanFactory继承体系该如何理解?---下

spring的beanFactory继承体系

Spring源码学习之BeanFactory体系结构

Spring源码解析 - BeanFactory接口体系解读

Spring BeanFactory源码学习