SpringMVC学习之04 XML和java配置方式配置DispatcherServlet并整合SpringIoC容器【详细】

基于XML配置DispatcherServlet并整合Spring IoC容器

Spring MVC的核心就是前端控制器DispatcherServlet,它显然是一个Servlet,需要进行配置以便由Servlet容器(比如Tomcat)来加载并管理。

那么如何配置DispatcherServlet让Servlet容器加载呢?显然不能在其源码中加上注解,因此只能使用部署描述符web.xml了。

不过,Spring MVC也提供了一种基于Java来配置DispatcherServlet的方式,实际上底层是基于Servlet API中的动态注册Servlet的接口。

部署描述符web.xml

然后,我们需要为我们的Web应用编写部署描述符web.xml(也可以在建立web工程时自动生成),然后在web.xml中配置Servlet了,DispatcherServlet也是一个Servlet而已。

同样,我先给出完整的web.xml:

<?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>
	
	<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>

可以看到,web.xml跟之前学过的Spring IoC容器的基于XML的配置元数据很像,废话,都是遵循XML规范嘛!

不过这里的根标记是,它的命名空间是Java EE的。关于部署描述符我们以后再详细讨论,我们先关注DispatcherServlet的配置。

大家可能会发现,web.xml中context这个单词出现很多次,除了<context-param>标记里面的context是指Servlet范畴的context,剩下的显然都是指Spring范畴的context。


部署描述符中配置DispatcherServlet

部署描述符中配置DispatcherServlet与配置普通的Servlet没什么区别,主要使用<servlet><servlet-mapping>这两个标记,下面是配置了DispatcherServletweb.xml

  • <servlet>标记:声明一个Servlet,其作用类似于@WebServlet注解,它有两个子标记:
  1. <servlet-name>:指定该Servlet的名字,必须是唯一的,我这指定的是appA,显然希望这个DispatcherServlet是处理appA这个应用的请求,也就是说你还可以再声明一个DispatcherServlet,其名字可能叫做appB,用来处理appB这个应用的请求。
  2. <servlet-class>:指定Servlet的具体类型,当然这里就必须是Spring MVC中的DispatcherServlet了。
  • <servlet-mapping>标记:用来配置一个Servlet与请求的映射模式,它也有两个子标记:
  1. <servlet-name>:指定要配置映射的哪个Servlet,使用其名字。
  2. <url-pattern>:请求的URL映射模式,可以有通配符,比如星号*

嗯,配置很简单,一目了然,只要是属于应用appA即URL是/appA/*模式的请求,就Servlet容器就会交给名字是appA的DispatcherServlet实例来处理。

同理,如果再配置一个应用appB的DispatcherServlet,也是类似,我就配置了两个DispatcherServlet独立处理appA和appB这两个web应用的请求。


部署描述符中整合Spring IoC容器

DispatcherServlet配置好之后,就应该 整合 Spring IoC容器了。整合这个词听起来有点太大了,其实就是要生成一个实现ApplicationContext接口(更底层的是BeanFactory接口)的具体类的实例而已,该实例会读取配置元数据并生成和管理Bean。不过,在Web应用(部署到Servlet容器中运行)中,Spring框架为我们提供的是WebApplicationContext接口。

之前standalone应用(可以独立部署并运行的),当然可以在应用的运行入口main方法中生成一个Spring IoC容器;而Web应用是由Servlet容器加载并执行的,当然就应该在Servlet容器加载Web应用的时候生成Spring IoC容器了。

不过Spring MVC不需要我们显式的像standalone应用那样生成Spring IoC容器,我们只需要指定配置元数据的位置即可,当然也提供了显式的基于Java的方式。

我们首先来看看Spring MVC中的Spring IoC容器的层次问题。

Spring IoC容器的层次

可能会有也应该是这么一种场景,我们肯定尽量把相关的多个应用部署到同一个Servlet容器中,这样,多个应用可以共享各个层次(模型层、控制器层、视图层)某些代码,而其他组件可能是不一样的。

于是,每个应用需要配置自己的非共享的控制层、视图层以及业务模型组件;而那些共享的业务模型组件只需要配置一次,每个应用就都可以使用。

因此,每个应用都需要一个独立的Spring IoC容器(我们把它叫做Servlet IoC)来配置和管理自己的组件,还需要一个整个Servlet容器级别的Spring IoC容器(我们把它叫做Root IoC)来配置和管理所有应用共享的组件。

很自然的,如果一个DispatcherServlet在自己的Servlet IoC容器中没有找到需要的组件,那么它就应该去Root IoC容器找需要的组件;如果找到了,就不需要再去Root IoC容器找了。

也就是说,一个DispatcherServlet应该指向两个Spring IoC容器,比如上面的web.xml中就配置了两个DispatcherServlet,每个DispatcherServlet都指向了一个自己的Servlet IoC容器以及Root IoC容器:

在这里插入图片描述


配置Servlet IoC容器

自己的IoC容器当然是配置在自己里面了,于是只需要在标记下添加一个子标记:

  • <init-param>标记:用来向Servlet传送初始化参数的,由此可以推测,Servlet IoC容器是在初始化Servlet的阶段生成的。它也有两个子标记:
  1. <param-name>:这是参数名字,这里就必须是contextConfigLocation,意味着是IoC容器的配置文件的位置,当然这是Spring MVC规定的;
  2. <param-value>:这是参数的值,当然就是IoC配置文件的位置了,通常都是放在WEB-INF目录下的。不过,这里可以填写多个位置以加载多个IoC配置文件,以逗号分开即可。

<load-on-startup>这个标记其实跟配置IoC容器没有关系,主要是用来表示是否在Servlet容器启动时就加载该Servlet,非零即表示启动时就加载。

实际上,可以完全不配置Servlet IoC容器,或者<param-value>标记的内容为空。

我们的web.xml中appA和appB都配置了自己的Servlet IoC容器。


配置Root IoC容器

Root IoC容器的配置要稍微麻烦一点,首先要配置一个监听器(属于Servlet规范):

  • <listener>标记:声明一个监听器对象,再配置一个子标记:
  1. <listener-class>标记:指定监听器类,其值为Spring MVC为我们提供的org.springframework.web.context.ContextLoaderListener从名字即可看出使用来加载IoC容器的。

然后要配置Root IoC容器的配置元数据的位置:

  • <context-param>标记:类似<init-param>标记,用来传递参数的,子标记也相同,不再赘述,不过这里应该传Root IoC容器的配置文件路径,我们的web.xml配置的是/WEB-INF/root-context.xml

IoC容器的配置元数据

现在,我们要为IoC容器提供配置元数据,我们在web.xml中配置了三个IoC容器,所以需要提供三份配置元数据,默认是使用基于XML的方式。
先来看root-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		https://www.springframework.org/schema/beans/spring-beans.xsd 
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">
	
	<context:component-scan base-package="test.common"/>

</beans>
123456789101112

可以看到,里面开启了组件扫描,故采用的是基于注解的Bean生成和自动装配,而Root IoC容器扫描的范围是test.common这个包。

appA-context.xml和appB-context.xml也类似,不过appA应用的Servlet IoC容器扫描的范围是test.appa这个包,appB应用的是test.appb这个包。

总结
  • Spring MVC的核心配置是在Servlet容器中配置DispatcherServlet;
  • 可以配置多个DispatcherServlet;
  • Spring MVC中的Spring IoC容器分为两层:每个DispatcherServlet可以配置自己的IoC容器,然后它们都有一个针对整个Web应用的Root IoC容器;
  • Servlet IoC容器在DispatcherServlet的内部使用<init-param>标记传入参数contextConfigLocation来配置;
  • Root IoC容器使用Spring MVC的ContextLoaderListener监听器和<context-param>标记来配置。

基于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,即接口ServletContextaddServlet方法,ServletContext对象containeronStartup方法的参数:

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中的内容都注释或删除。

  • 然后,可以仍然在AppAInitializeronStartup方法中添加appB应用以及Root IoC容器的配置代码,不过这样的话代码会显得有些臃肿且不明确,记住,一个类只干一件事。
  • 当然,也可以为appB应用创建独立的初始化器AppBInitializer,然后实现onStartup方法;为Root IoC容器的配置创建独立的初始化器MyWebApplicationInitializer,然后实现onStartup方法。这里的类名都可以自由命名的。

AppBInitializerAppAInitializer类似,读者可以自行实现。

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一个容器对象,设置元数据的所在位置,然后把容器对象传给DispatcherServletContextLoaderListener对象。
  • 我们要善于举一反三,触类旁通,推此及彼。
  • 我们要多看源代码和Javadoc。

基于Java配置DispatcherServlet

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值