Spring IoC 学习(4)

前言



前面的三篇文章,主要用BeanFactory介绍了Spring中IoC容器的两个阶段:容器启动阶段和实例化阶段。接下来的这篇文章主要说的是Spring的统一资源定位策略。

Spring为什么要整这个



写下这篇文章之前的绝大部分时间,我都在思考,为什么要整这个功能。任何一个功能、实现肯定有其道理。那道理是什么呢?有人是这么解释的:

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

首先,说是统一资源定位,但基本实现却只限于网络形式发布的资源的查找和定位工作,基本上只提供了基于HTTP、FTP、File等协议(sun.net.www.protocol 包下所支持的协议)的资源定位功能。虽然也提供了扩展的接口,但从一开始,其自身的“定位”就已经趋于狭隘了。实际上,资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。

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

以上的这段文字来自《Spring揭秘》,写的非常正确。但是我第一遍看的时候,我大致还是没看懂。

可能是我底子确实太差了。也可能是因为写书,总归要写的正式一点,而我对于正式语言的理解能力着实不足。这三大段文字的主要意思就是:

URL能力不足,不能定位所有类型的资源,开发Spring的这群人肯定觉得URL不好使,整了一个比它功能强的家伙

看看Spring是怎么整的

一开始肯定是要先定义一个借口Resource

Resource


    package org.springframework.core.io;

    import java.io.File;
    import java.io.IOException;
    import java.net.URI;
    import java.net.URL;

    public interface InputStreamSource{
        //每次调用都将返回一个新的资源对应的java.io.InputStream字节流,调用者在使用完毕后必须关闭该资源
        InputStream getInputStream() throws IOException;
    }
        public interface Resource extends InputStreamSource{
        //返回当前Resouce代表的底层资源是否存在,true表示存在
        boolean exists();
        //返回当前Resouce代表的底层资源是否可读,true表示可读
        boolean isReadable();
        //返回当前Resouce代表的底层资源是否已经打开,如果返回true,则只能被读取一次然后关闭以避免内存泄露;常见的Resource实现一般返回false;
        boolean isOpen();
        //如果当前Resouce代表的底层资源能由java.util.URL代表,则返回该URL,否则抛出IOException
        URL getURL() throws IOException;
        //如果当前Resouce代表的底层资源能由java.util.URI代表,则返回该URI,否则抛出IOException
        URI getURI() throws IOException;
        //如果当前Resouce代表的底层资源能由java.io.File代表,则返回该File,否则抛出IOException
        File getFile() throws IOException;
        //返回当前Resouce代表的底层文件资源的长度,一般的值代表的文件资源的长度
        long contentLength() throws IOException;
        //返回当前Resouce代表的底层文件资源的最后修改时间
        long lastModified() throws IOException;
        //用于创建相对于当前Resource代表的底层资源的资源,比如当前Resource代表文件资源“D:/test/”则createRelative("test.txt")将返回代表文件资源“D:/test/test.txt”Resource资源。
        Resource createRelative(String relativePath) throws IOException;
        //返回当前Resource代表的底层文件资源的文件路径,比如File资源:file://d:/test.txt 将返回d:/test.txt,而URL资源http://www.javass.cn将返回“”,因为只返回文件路径
        String getFilename();
        //返回当前Resource代表的底层资源的描述符,通常就是资源的全路径(实际文件名或实际URL地址)
        String getDescription();
    }

接口提供这些方法,基本上是足够我们日常使用的,除了这个接口,当然也提供了这些接口的sh实现类。

可以看到,有16个实现类,当然,我们常用的并没有那么多,主要有以下几个

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

ResourceLoader

其实啦,上面说了半天,都是在说Resource,有了资源,怎么去查找,怎么去定位呢,用ResourceLoader。

这必然,我猜它就是个接口,这么简洁的名字。

ResourceLoader


    package org.springframework.core.io;

    import org.springframework.util.ResourceUtils;

    public interface ResourceLoader {

        // CLASSPATH_URL_PREFIX = classpath
        String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

        // 拿到Resource
        Resource getResource(String location);

        // 拿到ClassLoader
        ClassLoader getClassLoader();

    }

事实证明,我想的还是没错的,但是这个接口也真是够简洁的。

看看实现类吧~

18个,但还是先看画圈的几个吧。

DefaultResourceLoader


public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");

        for (ProtocolResolver protocolResolver : this.protocolResolvers) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
                return resource;
            }
        }

        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                // Try to parse the location as a URL...
                URL url = new URL(location);
                return new UrlResource(url);
            }
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }

这是DefaultResourceLoader返回Resource的方法,可以看到:

  1. 看看location是不是以"/"开头,如果是返回ClassPathContextResource类型的资源
  2. 检查资源路径是否以classpath:前缀打头,如果是,则尝试构造ClassPathResource类型资源并返回。
  3. 尝试通过URL,根据资源路径来定位资源,如果没有抛出MalformedURLException,有则会构造UrlResource类型的资源并返回;

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

FileSystemResourceLoader虽然是DefaultResourceLoader的继承类,但是在getResourceByPaht方法上就不是返回ClassPathResource,而是返回FileSystemResource

@Override
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemContextResource(path);
    }

ApplicationContext 与资源



前面一直就只是在讲资源,资源定位器,但是,在我们Spring的体现又在哪里?

先看这个图,很明显,ApplicationContext也是ResourceLoader。如果再看两个重要的实现类,你就会懂了。原来如此

ClassPathXmlApplicationContext

FileSystemXmlApplicationContext

剩下的,你说一个bean里面,如果要注入ResourceLoader,或者要注入Resource怎么办呢?

可以这么做

public class FooBar {
    private ResourceLoader resourceLoader;

    public void foo(String location) {
        System.out.println(getResourceLoader().getResource(location).getClass());
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

然后配置一下bens.xml

<bean id="resourceLoader" class="org.springframework.core.io.DefaultResourceLoader">
</bean>
<bean id="fooBar" class="...FooBar">
<property name="resourceLoader">
<ref bean="resourceLoader"/> </property>
</bean>

有点复杂诶,你说,ApplicationContext不就是资源定位器吗?为什么还要再弄一个新的出来呢?

前一小节的两个接口有用了,ResourceLoaderAware和ApplicationContextAware,他们都能拿到IoC容器本身呀。

public class FooBar implements ApplicationContextAware {
    private ResourceLoader resourceLoader;

    public void foo(String location) {
        System.out.println(getResourceLoader().getResource(location).getClass());
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }

    public void setApplicationContext(ApplicationContext ctx)
            throws BeansException

    {
        this.resourceLoader = ctx;
    }
}

看这配置文件不就只有一行了,方便。

<bean id="fooBar" class="...FooBar">
</bean>

后记



写完这篇,基本算是把资源一部分搞懂了一点,但是最近一直在反思,这种总结的办法,可以我是确实不会用Markdown吧,每次花在编辑上的时间就非常多了。还是要想想办法解决这个问题。

时间: 2024-11-03 21:49:37

Spring IoC 学习(4)的相关文章

Spring IoC 学习(2)

前言 知道了IoC的好处和优势之后,本来应该有的一步是,搞清楚怎么用.因为前面我写的顺序是:是什么,为什么?下一个part肯定的就是怎么办或者怎么用?但是,按照Spring的官方的Guide,我觉得应该大家是可以写个Hello World.网上这类的教程也很多,加上其实我这次学习Spring是想更加深入的学习,因此,重点就不放在这个部分了.主要放在学习背后的故事. 这小节的内容就是学习IoC的容器. 两种容器 概述 整个IoC容器可以分为两个阶段,容器启动阶段和实例化阶段. 容器启动阶段 ①就是

Spring IoC 学习(1)

基本概念 IoC是什么? 如果这个问题要是面试的问题,那么我会这么回答. IoC(Inversion of Control 控制反转),当然它还有另一个名字,DI(Dependency Injection 依赖注入).这两个名称其实实质上指的都是同一样的东西.只不过看问题的角度是不一样的.IoC指的是,原来我们需要获得一个对象(Object)的时候,我们的第一想法就是用new.搭配下图,效果更好. 现在我们不用new了,是别人给我们的.既然是别人给的,那么,别人可以给,也可以不给.主动权这个时候

Spring IoC 学习(3)

前言 前面因为总结的累了,把IoC的两个步骤,只写了一半,就仅仅把容器启动的方面说了说,对于实例化的阶段,我前面并没有说,在这节中,准备讲一讲,实例化阶段. 生命周期 基础生命周期简图 这个部分,其实实例化,一般都是用反射或者cglib,底层封装的也比较深,我随着代码debug的过程中,也没有接触到这个部分.但是在实例化bean的过程中,还是看到了挺多东西. 生命周期的图,基本上有可能是以下这种 从图中可以看到,在这个阶段,最重要的不是实例化本身,而是实例化前后会做的一些操作.实例化有些不同的,

谈谈对Spring IOC的理解

学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring框架的IOC的理解以及谈谈我对Spring Ioc的理解. 一.分享Iteye的开涛对Ioc的精彩讲解 首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂,以下内容全部来自原文,原文地址:http://jinniansh

spring IOC容器实现探讨

   spring IOC容器的实现,一开始我被复杂的接口和类所掩埋,看不清整体的思路和设计,踟蹰于代码丛林中,摸不清前进的方向.一开始我就决定只研读以xml文件做配置文件的XmlFactoryBean的具体实现为主要目标,渐渐地有了点感觉,用UML把spring中的bean工厂体系展现出来之后就更清晰了,让你不得不感叹设计的精巧和复杂.本文只是我个人对spring IOC实现的理解,如有错误,请不吝赐教,谢谢.     首先,需要理解的是spring容器中bean的生命周期,<spring i

利用Spring IOC技术实现用户登录验证机制_java

利用 Spring IOC 技术实现用户登录的验证机制,对用户进行登录验证. 首先利用 Spring 的自动装配模式将 User 对象注入到控制器中,然后将用户输入的用户名和密码与系统中限定的合法用户的用户名和密码进行匹配. 当用户名与密码匹配成功时,跳转到登录成功页面:当用户名与密码不匹配时,跳转到登录失败的页面. 1.创建 User 对象,定义用户名和密码属性,代码如下: package com.importnew; public class User { private String us

《Spring攻略(第2版)》——第1章 Spring简介 1.1实例化Spring IoC容器

第1章 Spring简介 在本章中,你将参加关于Spring.核心容器以及容器所提供的一些全局可用设施的一个速成班(或者一次复习),你还将了解Spring XML配置格式,以及注释驱动的支持.本章将带给你应付本书余下部分中引入的概念所需要的知识.你将学习Spring IoC容器中的基本组件配置.在Spring框架的核心部分,IoC容器的设计具有高度的适应性和可配置性,提供了使你的组件配置尽可能简单的一组工具.你能够很简单地设置运行于Spring IoC容器中的组件. 在Spring中,组件也被称

Spring IoC[控制反转]

近段时间正在学习spring.对于spring IOC发表一下自己的见解 1 spring IoC 1.1 什么是IoC 控制反转(Inversion of Control,英文缩写为IoC).主要是用来降低程序之间耦合度的一种方式. 1.2 IoC主要形式 ◇依赖查找:容器提供回调接口和上下文条件给组件.组件就必须使用容器提供的API来查找资源和协作对象,容器将调用这些回调方法,从而让应用代码获得相关资源. ◇依赖注入:组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系.容器全权负

spring ioc

spring ioc是spring的核心之一,也是spring体系的基础,那么spring ioc所依赖的底层技术是什么的?反射,以前我们开发程序的时候对象之间的相互调用需要用new来实现,现在所有的bean都是通过spring容器来管理.这样做有什么好处呢?解耦!以前程序直接的调用用new直接给写死了,现在我们可以通过注入不同的接口实现类来完成对象直接的调用.   首先来聊聊Java的反射机制 1.反射机制的作用:   反编译:.class-->.java   通过反射机制访问java对象的属