- 背景
Spring的配置文件有两种,分别是Spring和Spring MVC的配置文件,一般放在classpath下或者WEB-INF下,加载的方式一般在web.xml中声明,如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
... ...
<!-- 指定配置文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 初始化Spring -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 初始化Spring MVC -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定配置文件路径 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
... ...
</web-app>
若未指定contextConfigLocation参数,ContextLoaderListener会默认使用/WEB-INF/applicationContext.xml文件,DispatcherServlet会默认使用${servlet-name}-servlet.xml。
显然,这种方式的话Spring的配置文件会被打到war包里,有时会修改的需要,但下次更新又会被覆盖掉。
- 解决
跟踪源码看一下Spring的初始化,找到指定配置文件的地方:
ContextLoaderListener.java
// Listener的上下文初始化方法,在此处初始化Spring上下文
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
AbstractApplicationContext.java
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
... ...
// 初始化BeanFactory,找到配置文件,加载Bean信息
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
... ...
}
}
XmlWebApplicationContext.java
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
// 此处获取contextConfigLocation参数
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
AbstractBeanDefinitionReader.java
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
... ...
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// 使用ResourcePatternResolver,将location也就是contextConfigLocation参数,封装成Spring的Resource
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
......
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
... ...
}
这里我们看到,Spring会利用配置的路径参数加载成一个Resource,供后续解析。通过查阅官方文档,Spring提供了几种内建Resource,有UrlResource、ClassPathResource、FileSystemResource、InputStreamResource、ByteArrayResource,我们一开始给出web.xml示例的配置路径最后封装成了ClassPathResource。
要想引用外部文件,其实可以使用UrlResource这种类型,UrlResource包装了一个java.net.URL
,被用来访问能通过URL访问到的对象,比如文件、HTTP资源、FTP资源,具体如何使用呢?
很简单,将contextConfigLocation参数配置成file:${配置文件路径},如file:D:/IdeaProjects/spring-framework-demo/config/spring-mvc.xml。然后又有一个问题,我们不想在web.xml写死路径,而是想在代码中注入路径(在Spring初始化前)。
ContextLoaderListener和DispatcherServlet初始化不相同,因此分开处理:
ContextLoaderListener:写一个CustomContextLoaderListener类继承ContextLoaderListener,复写ContextLoaderListener的contextInitialized(ServletContextEvent event)方法,在调用super.contextInitialized(event)之前利用得到的ServletContext写入contextConfigLocation参数,具体实现如下:
import org.springframework.web.context.ContextLoaderListener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
public class CustomContextLoaderListener extends ContextLoaderListener {
@Override
public void contextInitialized(ServletContextEvent event) {
initContextConfigLocation(event.getServletContext());
super.contextInitialized(event);
}
private void initContextConfigLocation(ServletContext context) {
context.setInitParameter(CONFIG_LOCATION_PARAM, "file:D:/IdeaProjects/spring-framework-demo/config/applicationContext.xml");
}
}
DispatcherServlet:写一个CustomDispatcherServlet类继承DispatcherServlet,在无参构造器中,调用setContextConfigLocation()方法,注入contextConfigLocation参数,具体实现如下:
import org.springframework.web.servlet.DispatcherServlet;
public class CustomDispatcherServlet extends DispatcherServlet {
private static final long serialVersionUID = 4556688394661662171L;
public CustomDispatcherServlet() {
super();
initContextConfigLocation();
}
private void initContextConfigLocation() {
super.setContextConfigLocation("file:D:/IdeaProjects/spring-framework-demo/config/spring-mvc.xml");
}
}
最后,在web.xml声明自定义的ContextLoaderListener和DispatcherServlet,如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
... ...
<listener>
<listener-class>com.yjy.listener.CustomContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>com.yjy.servlet.CustomDispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
... ...
</web-app>