SpringMVC读取资源文件的几种方式

本文介绍了Spring IoC容器的启动过程,包括加载资源文件、解析资源文件和注册BeanDefinition。重点讲解了Spring中Resource和ResourceLoader接口在加载资源文件时的作用,以及ClassPathResource和DefaultResourceLoader的加载过程。还提到了如何通过自定义协议解析器进行扩展。

前面介绍了Spring的一些基础知识和简单的用法,本篇开始分析Spring的IoC容器。BeanFactory是IoC容器的基础,所以接下来的分析都是基于BeanFactory的。

IoC启动过程:

启动
加载资源文件
解析资源文件
注册BeanDefinition
结束
IoC容器的启动可分为三步,加载资源文件、解析资源文件、注册BeanDefinition。本篇分析资源文件加载过程。

Spring中资源文件加载主要有两个接口,Resource和ResourceLoader,前者提供了对资源文件的定位、是否存在、是否可读、是否打开、是否文件、获取URL,获取File、获取FileName等一系列功能。后者提供了Resource对象获取,自定义资源文件协议解析等功能。加载资源文件的过程会涉及到类加载机制,且不是我们分析IoC容器的重点,所以本篇不会做太多深入的分析。测试类

import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;

import java.io.IOException;

public class MyTest {

    @Test
    public void test1() {
        // 从资源文件夹下加载
        Resource resource = new ClassPathResource("v2/day01.xml");
        print(resource);

    }

    @Test
    public void test2() {
        // 使用类信息加载
        Resource resource = new ClassPathResource("day01.xml", MyTest.class);
        print(resource);

    }

    @Test
    public void test3() {
        // 使用类加载器从资源文件夹下加载
        Resource resource = new ClassPathResource("v2/day01.xml", MyTest.class.getClassLoader());
        print(resource);
    }

    @Test
    public void test4() {
        // 使用DefaultResourceLoader加载
        Resource resource = new DefaultResourceLoader().getResource("v2/day01.xml");
        print(resource);
    }

    // 打印资源文件内容
    public void print(Resource resource) {
        byte[] read = new byte[10000];
        try {
            resource.getInputStream().read(read, 0, read.length);
            System.out.println(new String(read));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

运行测试类,将会打印day01.xml的内容。注意资源文件的路径

先分析ClassPathResource的加载过程,再DefaultResourceLoader的加载过程

1. ClassPathResource对象创建过程
创建ClassPathResource对象

public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
    //规范路径
    String pathToUse = StringUtils.cleanPath(path);
    //如果路径以"/"开头,则截取开头"/"以后字符做为路径
    if (pathToUse.startsWith("/")) {
        pathToUse = pathToUse.substring(1);
    }
    //将处理后的路径赋给this.path
    this.path = pathToUse;
    //获取classLoader并赋给this.classLoader
    this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}

该构造函数比较简单,重点来看一下类加载器的获取过程。

获取类加载器

public static ClassLoader getDefaultClassLoader() {
    ClassLoader cl = null;
    try {
        //优先获取线程上下文类加载器
        cl = Thread.currentThread().getContextClassLoader();
    }
    catch (Throwable ex) {
        // Cannot access thread context ClassLoader - falling back...
    }
    if (cl == null) {
        // No thread context class loader -> use class loader of this class.
        // 获取当前类的类加载器
        cl = ClassUtils.class.getClassLoader();
        if (cl == null) {
            // getClassLoader() returning null indicates the bootstrap ClassLoader
            try {
                //获取SystemClassLoader
                cl = ClassLoader.getSystemClassLoader();
            }
            catch (Throwable ex) {
                // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
            }
        }
    }
    return cl;
}

类加载器简介:

bootstrap class loader:主要负责main方法启动的时候,加载JAVA_HOME/lib下的jar包
extension class loader:主要负责加载JAVA_HOME/ext/lib下的jar包
system class loader:主要负责加载classpath下的jar包或者类
2.使用ClassPathResource获取InputStream
创建了ClassPathResource对象实例之后,就可以使用该对象来获取InputStream。

public InputStream getInputStream() throws IOException {
    InputStream is;
    // ①如果类对象不为null,则使用类对象信息的getResourceAsStream获取输入流
    if (this.clazz != null) {
        is = this.clazz.getResourceAsStream(this.path);
    }
    // ②如果类加载器不为null,则使用类加载器的getResourceAsStream获取输入流
    else if (this.classLoader != null) {
        is = this.classLoader.getResourceAsStream(this.path);
    }
    else {
        // ③否则使用ClassLoader类的getSystemResourceAsStream方法获取输入流
        is = ClassLoader.getSystemResourceAsStream(this.path);
    }
    if (is == null) {
        //以上三种方法都无法获取到输入流的话,那么说明文件不存在,抛出异常
        throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
    }
    return is;
}

Spring对InputStream的获取过程进行了分情况处理

①如果类对象不为null,则使用类对象信息的getResourceAsStream获取输入流
②如果类加载器不为null,则使用类加载器的getResourceAsStream获取输入流
③否则使用ClassLoader类的getSystemResourceAsStream方法获取输入流
使用类对象信息的getResourceAsStream获取输入流

public InputStream getResourceAsStream(String name) {
    // 解析资源文件名称
    // 如果名称不是绝对的,则添加包名称前缀。如果名称是绝对的,则删除前面的“/”
    // 例如:相对路径:解析前name=day01.xml;解析后name=com/lyc/cn/v2/day03/day01.xml
    //      绝对路径:解析前name=/day01.xml;解析后name=day01.xml
    name = resolveName(name);
    // 获取类加载器并返回InputStream
    ClassLoader cl = getClassLoader0();
    if (cl==null) {
        // A system class.
        return ClassLoader.getSystemResourceAsStream(name);
    }
    return cl.getResourceAsStream(name);
}

使用类加载器的getResourceAsStream获取输入流

public InputStream getResourceAsStream(String name) {
    // 将资源文件路径转换为URL统一资源定位符
    URL url = getResource(name);
    try {
        if (url == null) {
            return null;
        }
        URLConnection urlc = url.openConnection();
        // 获取InputStream
        InputStream is = urlc.getInputStream();
        // 判断URLConnection类型并根据其类型加入到closeables对象中
        // closeables对象通过WeakHashMap维护了Closeable对象信息
        if (urlc instanceof JarURLConnection) {
            JarURLConnection juc = (JarURLConnection)urlc;
            JarFile jar = juc.getJarFile();
            synchronized (closeables) {
                if (!closeables.containsKey(jar)) {
                    closeables.put(jar, null);
                }
            }
        } else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
            synchronized (closeables) {
                closeables.put(is, null);
            }
        }
        return is;
    } catch (IOException e) {
        return null;
    }
}


使用ClassLoader类的getSystemResourceAsStream方法获取输入流

public static InputStream getSystemResourceAsStream(String name) {
    // 将资源文件路径转换为URL统一资源定位符
    URL url = getSystemResource(name);
    try {
        // 判断URL对象是否为空,并返回InputStream
        return url != null ? url.openStream() : null;
    } catch (IOException e) {
        return null;
    }
}

DefaultResourceLoader的加载过程

3.创建DefaultResourceLoader对象
该过程主要就是获取ClassLoader对象,与上面的分析是相同的。

public DefaultResourceLoader() {
    this.classLoader = ClassUtils.getDefaultClassLoader();
}

public static ClassLoader getDefaultClassLoader() {
    ClassLoader cl = null;
    try {
        //优先获取线程上下文类加载器
        cl = Thread.currentThread().getContextClassLoader();
    }
    catch (Throwable ex) {
        // Cannot access thread context ClassLoader - falling back...
    }
    if (cl == null) {
        // No thread context class loader -> use class loader of this class.
        // 获取当前类的类加载器
        cl = ClassUtils.class.getClassLoader();
        if (cl == null) {
            // getClassLoader() returning null indicates the bootstrap ClassLoader
            try {
                //获取SystemClassLoader
                cl = ClassLoader.getSystemClassLoader();
            }
            catch (Throwable ex) {
                // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
            }
        }
    }
    return cl;
}

4.使用DefaultResourceLoader对象获取Resource

public Resource getResource(String location) {

    // ①优先遍历协议解决器集,如果可以解决,则返回位置相应的资源
    for (ProtocolResolver protocolResolver : this.protocolResolvers) {
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
            return resource;
        }
    }

    // ②如果资源位置以"/"开头,则获取路径资源
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    }
    // ③如果资源位置以"classpath:"开头,创建路径位置的的类路径资源ClassPathResource
    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 url = new URL(location);
            return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
        }
        catch (MalformedURLException ex) {
            // No URL -> resolve as resource path.
            // ⑤没有成功转换为URL资源,则将location视为资源路径并返回对应解析资源
            return getResourceByPath(location);
        }
    }
}

该过程涉及到的步骤比较多,但是资源文件加载并不是我们分析IoC容器的重点,我们只分析自定义解析协议,其他的不再赘述,感兴趣的同学可以自己debug跟踪下代码。

5. 自定义协议解析器

import org.springframework.core.io.ProtocolResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
public class MyProtocolResolver implements ProtocolResolver {
    @Override
    public Resource resolve(String location, ResourceLoader resourceLoader) {
        if (location.startsWith("my")) {
            return resourceLoader.getResource(location.replace("my", "classpath"));
        }
        return null;
    }
}

继承并实现ProtocolResolver接口的方法,将自定义的协议前缀转换为classpath即可

使用方法

@Test
public void test5() {
    // 使用自定义协议解析器加载
    DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
    resourceLoader.addProtocolResolver(new MyProtocolResolver());
    Resource resource = resourceLoader.getResource("my:/v2/day01.xml");
    print(resource);
}

 

<think>我们正在讨论Spring MVC中读取properties文件的配置方法。根据用户提供的引用内容,我们可以总结几种常见的配置方式。下面将详细介绍这些方法,并给出示例代码和配置。 ### 方法一:使用`<context:property-placeholder>`(推荐用于占位符替换) 这是最常用的方式,特别适合在XML配置中使用`${}`占位符注入属性值。 **配置示例**: ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 加载单个properties文件 --> <context:property-placeholder location="classpath:config.properties" ignore-unresolvable="true" file-encoding="UTF-8"/> <!-- 加载多个properties文件 --> <context:property-placeholder location="classpath:config.properties, classpath:db.properties" ignore-unresolvable="true" file-encoding="UTF-8"/> <!-- 使用占位符注入属性 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans> ``` **参数说明**: - `location`:指定properties文件的位置,多个文件用逗号分隔。 - `ignore-unresolvable`:设置为`true`时,如果找不到对应的占位符不会报错,而是保留原字符串(如`${unknown}`);设置为`false`则会抛出异常。默认为`false`。 - `file-encoding`:指定properties文件的编码,如UTF-8、GBK等。 **注意**:在同一个Spring配置文件中,只能有一个`<context:property-placeholder>`,如果需要加载多个文件,可以在`location`中使用逗号分隔。如果需要多个`PropertyPlaceholderConfigurer`,请使用方法二。 ### 方法二:使用`PropertyPlaceholderConfigurer` Bean(更灵活) 这种方式可以配置多个`PropertyPlaceholderConfigurer`,并且支持更复杂的设置。 **配置示例**: ```xml <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:config.properties</value> <value>classpath:db.properties</value> <!-- 也可以加载外部文件 --> <value>file:/path/to/external/config.properties</value> </list> </property> <property name="ignoreUnresolvablePlaceholders" value="true"/> <property name="fileEncoding" value="UTF-8"/> </bean> ``` **参数说明**: - `locations`:指定properties文件的位置列表,可以是`classpath:`(类路径下)或`file:`(文件系统绝对路径)。 - `ignoreUnresolvablePlaceholders`:同方法一的`ignore-unresolvable`。 - `fileEncoding`:文件编码。 ### 方法三:使用`PropertiesFactoryBean`(获取Properties对象) 如果我们需要将properties文件加载为一个`java.util.Properties`对象,并注入到其他Bean中,可以使用这种方式。 **配置示例**: ```xml <bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath:config.properties</value> </list> </property> <property name="fileEncoding" value="UTF-8"/> </bean> <!-- 使用Properties对象 --> <bean id="someBean" class="com.example.SomeBean"> <property name="properties" ref="configProperties"/> </bean> ``` 在Java代码中,我们可以这样使用: ```java public class SomeBean { private Properties properties; public void setProperties(Properties properties) { this.properties = properties; } public void doSomething() { String value = properties.getProperty("key"); // ... } } ``` ### 方法四:注解方式(结合Spring MVC) 在Spring MVC中,我们通常也会使用注解配置。可以在Java配置类中结合`@PropertySource`和`@Value`。 **配置示例**: ```java @Configuration @PropertySource("classpath:config.properties") public class AppConfig { @Value("${jdbc.url}") private String jdbcUrl; // 如果属性不存在,可以设置默认值 @Value("${jdbc.timeout:3000}") private int timeout; @Bean public DataSource dataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setUrl(jdbcUrl); // ... return dataSource; } } ``` **注意**:使用`@PropertySource`需要配合`PropertySourcesPlaceholderConfigurer`,在Java配置中,我们可以这样配置: ```java @Bean public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } ``` ### 总结 在Spring MVC中读取properties文件,主要有以下几种方式: 1. **XML占位符替换**:使用`<context:property-placeholder>`或`PropertyPlaceholderConfigurer`,适合在XML配置中使用`${}`占位符。 2. **加载为Properties对象**:使用`PropertiesFactoryBean`,适合需要直接操作Properties对象的场景。 3. **注解方式**:使用`@PropertySource`和`@Value`,适合基于Java配置的项目。 **最佳实践建议**: - 对于简单的键值注入,推荐使用`<context:property-placeholder>`。 - 需要加载外部配置文件时,可以使用`file:`路径。 - 注意文件编码,避免中文乱码。 - 使用`ignore-unresolvable="true"`可以避免因某些配置缺失导致整个容器启动失败。 ### 常见问题 **Q1:如何加载多个properties文件?** - 在`<context:property-placeholder>`的`location`属性中用逗号分隔多个文件路径。 - 在`PropertyPlaceholderConfigurer`的`locations`属性中使用`<list>`包含多个`<value>`。 **Q2:如何解决中文乱码问题?** - 确保properties文件保存为UTF-8编码(推荐)。 - 在配置中指定`file-encoding="UTF-8"`(或相应编码)。 **Q3:如何引用其他XML文件中定义的占位符?** - 确保在Spring容器中只有一个`PropertyPlaceholderConfigurer`(或`<context:property-placeholder>`),因为Spring不允许覆盖。 - 或者将多个XML文件的占位符配置合并到一个文件中。 §§相关问题§§ 1. 在Spring MVC中如何实现多环境(如dev, test, prod)的properties文件配置? 2. 如何在Spring中安全地管理数据库密码等敏感配置信息? 3. Spring Boot中读取properties文件与Spring MVC有何不同?</think>### Spring MVC 读取 Properties 文件的配置方法 在 Spring MVC 中读取 properties 文件主要有以下几种配置方式,根据您的需求选择合适的方法: #### 方法一:使用 `<context:property-placeholder>`(最常用) ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 加载单个properties文件 --> <context:property-placeholder location="classpath:config.properties" ignore-unresolvable="true" file-encoding="UTF-8"/> <!-- 加载多个properties文件 --> <context:property-placeholder location="classpath:config.properties, classpath:db.properties" ignore-unresolvable="true" file-encoding="UTF-8"/> <!-- 使用占位符注入属性 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans> ``` **关键参数说明**: - `location`:配置文件路径(`classpath:`表示资源目录) - `ignore-unresolvable`:设为`true`时找不到占位符不报错[^1] - `file-encoding`:指定文件编码(如UTF-8、GBK等)[^1] #### 方法二:使用 `PropertyPlaceholderConfigurer` Bean ```xml <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <!-- 加载类路径下的文件 --> <value>classpath:config.properties</value> <!-- 加载外部绝对路径文件 --> <value>file:/etc/app/config.properties</value> </list> </property> <property name="ignoreUnresolvablePlaceholders" value="true"/> <property name="fileEncoding" value="UTF-8"/> </bean> ``` **优势**:支持同时加载类路径和文件系统路径的配置文件[^4] #### 方法三:使用 `PropertiesFactoryBean`(获取Properties对象) ```xml <bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean"> <property name="locations"> <list> <value>classpath:config.properties</value> </list> </property> <property name="fileEncoding" value="UTF-8"/> </bean> <!-- 注入Properties对象 --> <bean id="serviceBean" class="com.example.MyService"> <property name="config" ref="configProperties"/> </bean> ``` **使用场景**:当需要直接操作Properties对象而非占位符替换时[^3] #### 方法四:注解方式(需开启组件扫描) ```java @Configuration @PropertySource("classpath:config.properties") public class AppConfig { @Value("${app.name}") private String appName; @Autowired private Environment env; // 通过环境对象获取 public String getDbUrl() { return env.getProperty("db.url"); } } ``` **XML配置支持**: ```xml <context:component-scan base-package="com.example.config"/> ``` ### 最佳实践建议 1. **路径规范**: - 开发环境:`classpath:config.properties` - 生产环境:`file:/path/to/external/config.properties`[^4] 2. **编码处理**: ```xml <!-- 明确指定中文字符集 --> <property name="fileEncoding" value="UTF-8"/> ``` 3. **多文件加载**: ```xml <property name="locations"> <list> <value>classpath:base.properties</value> <value>classpath:${spring.profiles.active}.properties</value> </list> </property> ``` 4. **错误处理**: - 设置`ignore-unresolvable="true"`避免因缺失属性导致启动失败[^1] - 使用默认值:`@Value("${timeout:30}")` ### 配置优先级总结 | 方法 | 适用场景 | 优势 | |------|----------|------| | `<context:property-placeholder>` | 占位符替换 | 简洁易用 | | `PropertyPlaceholderConfigurer` | 复杂路径需求 | 支持外部文件 | | `PropertiesFactoryBean` | 需Properties对象 | 直接操作配置 | | `@PropertySource` | 注解驱动项目 | 类型安全 | > 提示:Spring Boot中推荐使用`@ConfigurationProperties`,但在传统Spring MVC项目中以上XML配置仍是主流方案。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值