文章目录
介绍
上篇文章介绍了基于XML来配置Spring MVC的DispatcherServlet并整合Spring IoC容器,本篇文章继续介绍Spring MVC提供的基于Java的方式来配置DispatcherServlet并整合Spring IoC容器。
我们仍然使用上篇文章的示例工程。
使用基于Java的方式进行配置的基本步骤就是要实现 Spring MVC提供的某个接口或扩展某个抽象类,往往扩展抽象类会更简单更清晰一些;然后覆盖某些方法即可。
这些接口和抽象类的名字都是以Initializer结尾的,我们就叫它们为初始化器吧。
Spring MVC会自动扫描是否有初始化器的具体类,如果有,就会自动实例化它们并执行相应的方法来配置DispatcherServlet并整合Spring IoC容器。
而且,基于XML和基于Java这两种方式可以同时使用。
使用WebApplicationInitializer接口
我们先使用实现WebApplicationInitializer
接口的方式来配置DispatcherServlet并整合Spring IoC容器。
首先,假设我们要为spring-mvc-test工程的appA应用使用基于Java的方式来配置DispatcherServlet并整合Spring IoC容器。
先把web.xml中的关于appA的相关配置注释掉或删掉,我这里选择注释(使用<!--
和-->
):
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
id="WebApp_ID" version="3.0">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<!-- 注释掉appA的相关配置
<servlet>
<servlet-name>appA</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/appA-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appA</servlet-name>
<url-pattern>/appA/*</url-pattern>
</servlet-mapping>
-->
<servlet>
<servlet-name>appB</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/appB-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appB</servlet-name>
<url-pattern>/appB/*</url-pattern>
</servlet-mapping>
</web-app>
新建类AppAInitializer
我把新建的这个初始化器类放到test.config
包下,你也可以放在自己定义的包下,AppAInitializer.java的代码如下:
package test.config;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class AppAInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) throws ServletException {
XmlWebApplicationContext appAContext = new XmlWebApplicationContext();
appAContext.setConfigLocation("/WEB-INF/appA-context.xml");
ServletRegistration.Dynamic registrationA = container.addServlet("appA", new DispatcherServlet(appAContext));
registrationA.setLoadOnStartup(1);
registrationA.addMapping("/appA/*");
}
}
WebApplicationInitializer
接口有唯一的方法onStartup
需要我们实现。那怎么实现呢?
实例化Spring IoC容器
还记得我们在standalone应用中是如何在main方法中实例化Spring IoC容器的吗?没错,就是使用ClassPathXmlApplicationContext
来实例化一个ApplicationContext
对象,参考这篇文章。
同样,在Web应用中也是类似,只不过Spring MVC为我们提供的是WebApplicationContext
这个接口,如果配置元数据是基于XML的,那么我们就可以使用XmlWebApplicationContext
这个具体类,我们实例化一个它的对象,然后设置设置它的配置元数据的位置即可(这个位置默认是从资源的根目录开始)。
XmlWebApplicationContext appAContext = new XmlWebApplicationContext();
appAContext.setConfigLocation("/WEB-INF/appA-context.xml");
动态注册DispatcherServlet
我们直接实例化一个DispatcherServlet
对象,别忘了把我们前面生成的IoC容器传给它,这个IoC容器就是它的Servlet IoC容器了:
new DispatcherServlet(appAContext)
接下来,就应该配置DispatcherServlet
了,这里,主要采用的是Servlet规范中的动态注册Servlet的API,即接口ServletContext
的addServlet
方法,ServletContext
对象container
是onStartup
方法的参数:
ServletRegistration.Dynamic registrationA = container.addServlet("appA", new DispatcherServlet(appAContext));
最后,使用得到的注册对象设置Servlet的其他属性,比如是否容器启动时加载、URL映射模式等:
registrationA.setLoadOnStartup(1);
registrationA.addMapping("/appA/*");
验证
同上篇文章,不再赘述。
结论是整个Web应用可以由Tomcat正常加载并运行,包括Java配置的appA应用和原来web.xml中配置的appB应用。
如果完全不用web.xml配置呢?
那就把web.xml中的内容都注释或删除。
-
然后,可以仍然在
AppAInitializer
的onStartup
方法中添加appB应用以及Root IoC容器的配置代码,不过这样的话代码会显得有些臃肿且不明确,记住,一个类只干一件事。 -
当然,也可以为appB应用创建独立的初始化器
AppBInitializer
,然后实现onStartup
方法;为Root IoC容器的配置创建独立的初始化器MyWebApplicationInitializer
,然后实现onStartup
方法。这里的类名都可以自由命名的。
AppBInitializer
跟AppAInitializer
类似,读者可以自行实现。
Root IoC容器则因为使用的是监听器而非Servlet,而有所不同。我们可以根据上面的动态注册Servlet的代码举一反三,既然有addServlet
方法,是否也会有addListener
方法呢?答案是肯定的,我们可以使用Eclipse的代码补全功能可以很快的找到这个方法(其他IDE都应该有此功能)。于是,我们应该先new
一个Spring MVC提供的ContextLoaderListener
,将Root IoC容器的配置元数据的位置传给这个监听器即可:
XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
rootContext.setConfigLocation("/WEB-INF/root-context.xml");
container.addListener(new ContextLoaderListener(rootContext));
使用AbstractDispatcherServletInitializer抽象类
上面覆盖WebApplicationInitializer
接口的onStartup
方法的方式感觉不够简练,所以Spring MVC为我们提供了更方便的AbstractDispatcherServletInitializer
抽象类来配置DispatcherServlet并整合Spring IoC容器。
实际上,这个抽象类里面也可以配置Root IoC容器,不过这个抽象类也是通过继承AbstractContextLoaderInitializer
抽象类的,因此我们也可以直接使用AbstractContextLoaderInitializer
抽象类来配置Root IoC容器,见下一节。
我为appB应用的DispatcherServlet
创建了一个独立的初始化器,AppBInitializer.java:
package test.config;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer;
public class AppBInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext appBContext = new XmlWebApplicationContext();
appBContext.setConfigLocation("/WEB-INF/appB-context.xml");
return appBContext;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/appB/*" };
}
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected String getServletName() {
return "appB";
}
}
我们必须要覆盖三个方法:
createServletApplicationContext
:创建Servlet IoC容器的实例并传入配置元数据的位置;getServletMappings
:返回Servlet的URL映射模式;createRootApplicationContext
:创建Root IoC容器的实例并传入配置元数据的位置,我这里直接返回null,因为我下一节直接使用AbstractContextLoaderInitializer
抽象类来配置Root IoC容器。
我还覆盖了一个方法:
getServletName
:返回Servlet的名字即可,如果不覆盖此方法,会使用AbstractDispatcherServletInitializer
抽象类默认的名字DEFAULT_SERVLET_NAME = "dispatcher"
。大家可以自行看它的源码(如何查看源码参考这篇文章)。
一开始我并没有覆盖此方法,但是验证的时候我发现Console视图中打印的日志信息是初始化dispatcher这个DispatcherServlet,于是就想该如何使用AbstractDispatcherServletInitializer
抽象类配置Servlet的名字,于是就查看了一下其源码。
使用AbstractContextLoaderInitializer抽象类
AbstractContextLoaderInitializer
抽象类与上一节的AbstractDispatcherServletInitializer
抽象类的配置模式是一样的,都是覆盖某些方法而已,不再赘述,直接上代码。
MyWebApplicationInitializer.java:
package test.config;
import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
public class MyWebApplicationInitializer extends AbstractContextLoaderInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
XmlWebApplicationContext rootContext = new XmlWebApplicationContext();
rootContext.setConfigLocation("/WEB-INF/root-context.xml");
return rootContext;
}
}
总结
- 基于Java和基于XML在一个Web应用中可以混合使用;
- 基于Java的方式又有两种:
WebApplicationInitializer
接口和AbstractDispatcherServletInitializer
抽象类; - 基于Java的整合Spring IoC的思想是一致的:都是先new一个容器对象,设置元数据的所在位置,然后把容器对象传给
DispatcherServlet
或ContextLoaderListener
对象。 - 我们要善于举一反三,触类旁通,推此及彼。
- 我们要多看源代码和Javadoc。