Spring Core 官方文档阅读笔记(十四)
1.DispatcherServlet
DispatcherServlet是Spring MVC的中心Servlet,提供了请求处理的共享算法,而实际的工作则由可配置的委托组件来执行。DispatcherServlet与其他servlet一样,都需要使用java配置或者web.xml配置来进行声明和映射。反过来,DispatcherServlet使用Spring配置来发现请求映射、视图解析、异常处理等所需的委托组件。
先来看下怎么初始化一个DispatcherServlet,直接看官方例子吧:
/**
* @Author: kuromaru
* @Date: Created in 16:25 2019/6/20
* @Description:
* @modified:
*/
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext acwac = new AnnotationConfigWebApplicationContext();
acwac.register(AppConfig.class);
acwac.refresh();
DispatcherServlet ds = new DispatcherServlet(acwac);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", ds);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
或者使用最原始的方法,配置web.xml:
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
</web-app>
2.上下文层次结构
上面在初始化DispatcherServlet的时候,首先构建了一个 AnnotationConfigWebApplicationContext,并且将其作为参数传给了DispatcherServlet构造器。AnnotationConfigWebApplicationContext继承了AbstractRefreshableWebApplicationContext,而AbstractRefreshableWebApplicationContext又是ConfigurableWebApplicationContext的子接口,ConfigurableWebApplicationContext又继承了WebApplicationContext,在WebApplicationContext接口中,只有一个接口方法:
/**
* Return the standard Servlet API ServletContext for this application.
* <p>Also available for a Portlet application, in addition to the PortletContext.
*/
ServletContext getServletContext();
AbstractRefreshableWebApplicationContext通过实现这个方法来与ServletContext进行关联。因此,当我们在需要获取WebApplicationContext的时候,就可以使用RequestContextUtils来获取:
RequestContextUtils.findWebApplicationContext(request, servletContext)
也可以通过WebApplicationContextUtils来获取,如下:
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
实际来看一下,假设我们需要在某个请求里完成Spring容器的刷新操作,看代码:
/**
* @Author: kuromaru
* @Date: Created in 10:50 2019/6/21
* @Description:
* @modified:
*/
@Controller
public class ContextRefreshAction extends WebApplicationObjectSupport {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@RequestMapping(value = "/context/refresh", method = RequestMethod.POST)
@ResponseBody
public BaseResponse refreshContext(HttpServletRequest request, HttpServletResponse response) {
logger.info("开始刷新Spring容器上下文");
XmlWebApplicationContext webApplicationContext = (XmlWebApplicationContext)WebApplicationContextUtils.getWebApplicationContext(getServletContext());
webApplicationContext.refresh();
return new BaseResponse(ResponseCode.SUCCESS.getCode(), "刷新成功");
}
}
代码中我们通过WebApplicationContextUtils.getWebApplicationContext来获取了WebApplicationContext,然后强转为XmlWebApplicationContext(通过xml文件来完成容器的配置,如果通过java bean的方式进行配置,可以强转为AnnotationConfigWebApplicationContext)。这里注意一下getServlet()方法,这个方法是WebApplicationObjectSupport中的,这个类里有一个setServletContext方法,通过这个方法会在实例化Bean的时候将ServletContext与WebApplicationObjectSupport绑定起来。具体是什么时候设置的呢?
setServletContext方法会在ServletContextAwareProcessor的postProcessBeforeInitialization方法中调用。看代码:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (getServletContext() != null && bean instanceof ServletContextAware) {
((ServletContextAware) bean).setServletContext(getServletContext());
}
if (getServletConfig() != null && bean instanceof ServletConfigAware) {
((ServletConfigAware) bean).setServletConfig(getServletConfig());
}
return bean;
}
ServletContextAwareProcessor是BeanPostProcessor的实现类,前面整理过Spring的扩展点,BeanPostProcessor可以在Bean的生成前后做一些个性化处理。再回头看代码,postProcessBeforeInitialization是在Bean初始化之前做的处理,先去判断当前Bean是不是ServletContextAware类型,如果是,则设置ServletContext到bean里。这里获取ServletContext的方法还是getServletContext(),阴魂不散啊。。。接着看这个方法的实现:
/**
* Returns the {@link ServletContext} to be injected or {@code null}. This method
* can be overridden by subclasses when a context is obtained after the post-processor
* has been registered.
*/
protected ServletContext getServletContext() {
if (this.servletContext == null && getServletConfig() != null) {
return getServletConfig().getServletContext();
}
return this.servletContext;
}
可以看到,这个方法会直接返回servletContext属性,或者从servletConfig属性中获取servletContext。好,那来看下servletContext和servletConfig属性是什么时候设置的。查看ServletContextAwareProcessor的代码可以看到,有一个构造器直接传入了servletContext和ServletConfig,而这个构造器会在AbstractRefreshableWebApplicationContext的postProcessBeanFactory里调用:
/**
* Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
*/
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}
而这个postProcessBeanFactory方法,会在容器的刷新处理refresh中被调用,查看类关系图,AbstractRefreshableWebApplicationContext是AbstractApplicationContext的子抽象类,继承了AbstractApplicationContext的refresh()方法。而这个refresh方法,则会在初始化web容器的时候被调用。
看到这可能还有个疑问,postProcessBeforeInitialization是在哪个地方调用的呢?好,这个方法会在AbstractAutowireCapableBeanFactory中的applyBeanPostProcessorsBeforeInitialization方法里被调用,看代码:
@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
result = beanProcessor.postProcessBeforeInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}
这个处理里,获取了所有的BeanPostProcessors,逐个执行postProcessBeforeInitialization方法。而这个applyBeanPostProcessorsBeforeInitialization方法,看方法名的字面意思,"在初始化之前应用BeanPostProcessor",再来看这个方法的调用:
/**
* Initialize the given bean instance, applying factory callbacks
* as well as init methods and bean post processors.
* <p>Called from {@link #createBean} for traditionally defined beans,
* and from {@link #initializeBean} for existing bean instances.
* @param beanName the bean name in the factory (for debugging purposes)
* @param bean the new bean instance we may need to initialize
* @param mbd the bean definition that the bean was created with
* (can also be {@code null}, if given an existing bean instance)
* @return the initialized bean instance (potentially wrapped)
* @see BeanNameAware
* @see BeanClassLoaderAware
* @see BeanFactoryAware
* @see #applyBeanPostProcessorsBeforeInitialization
* @see #invokeInitMethods
* @see #applyBeanPostProcessorsAfterInitialization
*/
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareMethods(beanName, bean);
return null;
}
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
嗯,在AbstractAutowireCapableBeanFactory类的initializeBean方法被调用了。继续往下看,这个initializeBean方法会被doCreateBean方法调用,而doCreateBean又会被createBean方法调用,createBean又会在AbstractBeanFactory的getBean方法里被调用。看到这个getBean方法,就开心了,终于找到头了。我们在从spring容器获取bean的时候,都是通过BeanFactory的getBean来获取的,那么串起来看,也就是如果一个类继承了WebApplicationObjectSupport类,那么在spring容器获取这个类的实例的时候,会把初始化web容器时获取到的servletContext与这个类绑定。奈斯~
终于一层一层的把这个玩意剥开了。。。
在Spring MVC中,WebApplicationContext可以分为root WebApplicationContext和servlet-specific WebApplicationContext,root WebApplicationContext中包含了基础框架Bean,如数据库存储、业务服务等,而servlet-specific WebApplicationContext则对应了各个servlet实例,这些servlet-specific都继承了root来获取基础能力。
那么对于一个DispatcherServlet来说,它的层次结构应该是下面这样:
可以通过继承AbstractAnnotationConfigDispatcherServletInitializer来配置Servlet上下文。
/**
* @Author: kuromaru
* @Date: Created in 15:03 2019/6/25
* @Description:
* @modified:
*/
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{AppConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/app/*"};
}
}
如代码所示,RootConfig中的配置就会被做为root WebApplicationContext来使用,而AppConfig则对应了特定的Servlet。
简单说一下AbstractAnnotationConfigDispatcherServletInitializer,这个抽象类继承了AbstractDispatcherServletInitializer,看代码:
public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
public AbstractAnnotationConfigDispatcherServletInitializer() {
}
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = this.getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
} else {
return null;
}
}
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = this.getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}
protected abstract Class<?>[] getRootConfigClasses();
protected abstract Class<?>[] getServletConfigClasses();
}
AbstractDispatcherServletInitializer是WebApplicationInitializer的基础实现类,它在ServletContext里注册了一个DispathcerServlet。他是怎么注册的呢?首先我们看一下WebApplicationInitializer是干什么用的,看代码:
public interface WebApplicationInitializer {
/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initializing this web application. See
* examples {@linkplain WebApplicationInitializer above}.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;
}
WebApplicationInitializer是一个接口,这个接口有三个抽象实现类,分别是AbstractContextLoaderInitializer、AbstractDispatcherServletInitializer和AbstractAnnotationConfigDispatcherServletInitializer。而这三个都是抽象类,那么就可以推定,一定是通过WebApplicationInitializer来使这三个类发挥作用的。那么我们看一下哪里使用了WebApplicationInitializer。查询调用情况,发现在SpringServletContainerInitializer中有用到,如下:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
* implementations present on the application classpath.
* <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
* Servlet 3.0+ containers will automatically scan the classpath for implementations
* of Spring's {@code WebApplicationInitializer} interface and provide the set of all
* such types to the {@code webAppInitializerClasses} parameter of this method.
* <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
* this method is effectively a no-op. An INFO-level log message will be issued notifying
* the user that the {@code ServletContainerInitializer} has indeed been invoked but that
* no {@code WebApplicationInitializer} implementations were found.
* <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
* they will be instantiated (and <em>sorted</em> if the @{@link
* org.springframework.core.annotation.Order @Order} annotation is present or
* the {@link org.springframework.core.Ordered Ordered} interface has been
* implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
* method will be invoked on each instance, delegating the {@code ServletContext} such
* that each instance may register and configure servlets such as Spring's
* {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
* or any other Servlet API componentry such as filters.
* @param webAppInitializerClasses all implementations of
* {@link WebApplicationInitializer} found on the application classpath
* @param servletContext the servlet context to be initialized
* @see WebApplicationInitializer#onStartup(ServletContext)
* @see AnnotationAwareOrderComparator
*/
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
我们看到在SpringServletContainerInitializer的onStartUp方法里,遍历了一个WebApplicationInitializers的List,这个List又是通过参数Set<Class<?>> webAppInitializerClasses获得的,那么就要看一下这个参数的值是哪里来的。我们看到这个SpringServletContainerInitializer实现了ServletContainerInitializer,是Servlet3.0的一个接口,用于在Web应用启动的时候触发一些处理,这个接口方法会收集实现类中@HandlesTypes注解的中指定的类型做为onStartUp的第一个参数,这个后面会单独在Servlet专题中详细介绍。那么我们看一下SpringServletContainerInitializer类的HandlesType注解,它的value指定了WebApplicationInitializer类型,所以,onStartUp方法的第一个参数就是所有实现了WebApplicationInitializer的类的集合,也就是我们前面指出来的那三个。好了,我们已经看到了AbstractAnnotationConfigDispatcherServletInitializer类提供了两个方法,用户创建rootApplicationContext和ServletApplicationContext,那么他的父类AbstractDispatcherServletInitializer做了哪些事情呢?看代码:
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
/**
* The default servlet name. Can be customized by overriding {@link #getServletName}.
*/
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
/**
* Register a {@link DispatcherServlet} against the given servlet context.
* <p>This method will create a {@code DispatcherServlet} with the name returned by
* {@link #getServletName()}, initializing it with the application context returned
* from {@link #createServletApplicationContext()}, and mapping it to the patterns
* returned from {@link #getServletMappings()}.
* <p>Further customization can be achieved by overriding {@link
* #customizeRegistration(ServletRegistration.Dynamic)} or
* {@link #createDispatcherServlet(WebApplicationContext)}.
* @param servletContext the context to register the servlet against
*/
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
/**
* Return the name under which the {@link DispatcherServlet} will be registered.
* Defaults to {@link #DEFAULT_SERVLET_NAME}.
* @see #registerDispatcherServlet(ServletContext)
*/
protected String getServletName() {
return DEFAULT_SERVLET_NAME;
}
/**
* Create a servlet application context to be provided to the {@code DispatcherServlet}.
* <p>The returned context is delegated to Spring's
* {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)}. As such,
* it typically contains controllers, view resolvers, locale resolvers, and other
* web-related beans.
* @see #registerDispatcherServlet(ServletContext)
*/
protected abstract WebApplicationContext createServletApplicationContext();
/**
* Create a {@link DispatcherServlet} (or other kind of {@link FrameworkServlet}-derived
* dispatcher) with the specified {@link WebApplicationContext}.
* <p>Note: This allows for any {@link FrameworkServlet} subclass as of 4.2.3.
* Previously, it insisted on returning a {@link DispatcherServlet} or subclass thereof.
*/
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}
/**
* Specify application context initializers to be applied to the servlet-specific
* application context that the {@code DispatcherServlet} is being created with.
* @since 4.2
* @see #createServletApplicationContext()
* @see DispatcherServlet#setContextInitializers
* @see #getRootApplicationContextInitializers()
*/
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
}
/**
* Specify the servlet mapping(s) for the {@code DispatcherServlet} —
* for example {@code "/"}, {@code "/app"}, etc.
* @see #registerDispatcherServlet(ServletContext)
*/
protected abstract String[] getServletMappings();
/**
* Specify filters to add and map to the {@code DispatcherServlet}.
* @return an array of filters or {@code null}
* @see #registerServletFilter(ServletContext, Filter)
*/
protected Filter[] getServletFilters() {
return null;
}
/**
* Add the given filter to the ServletContext and map it to the
* {@code DispatcherServlet} as follows:
* <ul>
* <li>a default filter name is chosen based on its concrete type
* <li>the {@code asyncSupported} flag is set depending on the
* return value of {@link #isAsyncSupported() asyncSupported}
* <li>a filter mapping is created with dispatcher types {@code REQUEST},
* {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending
* on the return value of {@link #isAsyncSupported() asyncSupported}
* </ul>
* <p>If the above defaults are not suitable or insufficient, override this
* method and register filters directly with the {@code ServletContext}.
* @param servletContext the servlet context to register filters with
* @param filter the filter to be registered
* @return the filter registration
*/
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
int counter = -1;
while (counter == -1 || registration == null) {
counter++;
registration = servletContext.addFilter(filterName + "#" + counter, filter);
Assert.isTrue(counter < 100,
"Failed to register filter '" + filter + "'." +
"Could the same Filter instance have been registered already?");
}
}
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}
/**
* A single place to control the {@code asyncSupported} flag for the
* {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}.
* <p>The default value is "true".
*/
protected boolean isAsyncSupported() {
return true;
}
/**
* Optionally perform further registration customization once
* {@link #registerDispatcherServlet(ServletContext)} has completed.
* @param registration the {@code DispatcherServlet} registration to be customized
* @see #registerDispatcherServlet(ServletContext)
*/
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
}
}
嗯,在onStartUp做了两件事情,一个是调用父类的onStartUp方法,另一个就是注册DispatchServlet了。在registerDispatcherServlet方法中,可以看到调用了createServletApplicationContext方法,这个方法的实现就是在我们一开始介绍的AbstractAnnotationConfigDispatcherServletInitializer中实现的。接着往下看,创建完ServletApplicationContext以后,紧接着就创建DispatcherServlet,然后将DispatcherServlet注册到了ServletContext上,并且设置了Mapping、Filters等。另一个createRootApplicationContext方法在哪里调用呢?我们看一下父类的onStartUp方法:
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
/**
* Register a {@link ContextLoaderListener} against the given servlet context. The
* {@code ContextLoaderListener} is initialized with the application context returned
* from the {@link #createRootApplicationContext()} template method.
* @param servletContext the servlet context to register the listener against
*/
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
/**
* Create the "<strong>root</strong>" application context to be provided to the
* {@code ContextLoaderListener}.
* <p>The returned context is delegated to
* {@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)} and will
* be established as the parent context for any {@code DispatcherServlet} application
* contexts. As such, it typically contains middle-tier services, data sources, etc.
* @return the root application context, or {@code null} if a root context is not
* desired
* @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
*/
protected abstract WebApplicationContext createRootApplicationContext();
/**
* Specify application context initializers to be applied to the root application
* context that the {@code ContextLoaderListener} is being created with.
* @since 4.2
* @see #createRootApplicationContext()
* @see ContextLoaderListener#setContextInitializers
*/
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
父类AbstractContextLoaderInitializer的onStartUp方法中,只做了一件事,就是调用registerContextLoaderListener方法,而这个方法中,第一步就是调用createRootApplicationContext,然后为ServletContext注册一些ContextLoaderListener。
好了,至此为止,AbstractAnnotationConfigDispatcherServletInitializer类的作用已经非常清晰了。整体串联一下,Web容器启动的时候,会扫描所有实现了WebApplicationInitializer接口的类,并调用它们的onStartUp方法,在onStartUp方法中,会注册ContextLoaderListener和DispatcherServlet到ServletContext中。而在创建ContextLoaderListner的时候,会先创建RootApplicationContext,并且调用自定义的getRootConfigClasses方法来获取相关配置;同样的在创建DispatcherServlet的时候,会先创建ServletApplicationContext,并调用自定义的额getServletConfigClasses来获取相关配置。我们可以通过自定义的getRootConfigClasses和getServletConfigClasses来指定需要加载哪些配置,以及为配置分级。
根据前面贴出来的Servlet WebApplicationContext和Root WebApplicationContext的关系图来看,当Spring容器去获取一个Bean的时候,会先从Servlet级别的上下文中查找,如果找不到再去Root级别的上下文中查找。
3.DispatcherServlet中特殊的Bean
直接看表格:
Bean | 说明 |
---|---|
HandlerMapping | 将请求映射到一个含有预处理和后置处理的拦截器列表的处理。有两个比较重要的HandlerMapping实现,分别是RequestMappingHandlerMapping和SimpleUrlHandlerMapping,其中RequestMappingHandlerMapping即对应了@RequestMapping注解中的值,而SimpleUrlHandlerMapping支持显示的注册URI路径到处理中。 |
HandlerAdapter | 帮助DispatcherServlet调用映射到请求的处理,并且避免被这些处理细节影响 |
HandlerExceptionResolver | 异常的处理策略 |
ViewResolver | 将处理程序中返回的字符串视图名解析为实际视图 |
LocaleResolver, LocaleContextResolver | 解析客户端正在使用的区域设置,可能还有他们的时区,以便能够提供国际化试图 |
ThemeResolver | 解析可以使用的主题 |
MultipartResolver | 在multi-part库的支持下,解析multi-part请求 |
FlashMapManager | 在重定向的时候提供存储和获取输入输出的FlashMap的能力,可以将属性从一个请求传递到另一个请求 |
后面会详细介绍。
应用程序可以声明上述特殊Bean类型中列出的处理请求所需的基础结构Bean。DispatcherServlet在WebApplicationContext中检查每个特殊的Bean,如果没有匹配的Bean,它将使用DispatcherServlet.properties中配置的默认类型。
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
4. Servlet Config
根据上面的介绍,已经了解了DispatcherServlet是怎么创建并注册到ServletContext上的,我们来总结一下:
/**
* 通过继承AbstractAnnotationConfigDispatcherServletInitializer创建DispatcherServlet
* @Author: kuromaru
* @Date: Created in 15:03 2019/6/25
* @Description:
* @modified:
*/
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{AppConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/app/*"};
}
}
/**
* 通过继承AbstractDispatcherServletInitializer创建DispatcherServlet
* @Author: kuromaru
* @Date: Created in 15:03 2019/6/25
* @Description:
* @modified:
*/
public class MyWebAppInitializerByXML extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext xwac = new XmlWebApplicationContext();
xwac.setConfigLocation("dispatcher-servlet.xml");
return xwac;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected Filter[] getServletFilters() {
return new Filter[]{
new CharacterEncodingFilter()
};
}
}
/**
* 实现WebApplicationInitializer来创建DispatcherServlet
* @Author: kuromaru
* @Date: Created in 15:03 2019/6/25
* @Description:
* @modified:
*/
public class MyWebAppInitializerDefault implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
XmlWebApplicationContext xwac = new XmlWebApplicationContext();
xwac.setConfigLocation("dispatcher-servlet.xml");
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", new DispatcherServlet(xwac));
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
5. DisptacherServlet处理请求的流程
我们来一起理一下请求处理的流程:
- 搜索WebApplicationContext,并绑定到request上,做为Controller和其他元素在处理可以使用的属性。如何搜索呢?当收到一个请求时,我们可以获取到请求的URI,然后根据所有注册的servlet的servletMapping来判断对应哪一个servlet。当适用于我们创建的DispatcherServlet时,我们就可以从中获取到相应的WebApplicationContext(创建DispatcherServlet时会做为参数传进来)。
- 当处理请求时,区域解析器会被绑定到请求,使处理中用到的元素被区域解析器所解析,以供使用,如视图展示、数据准备等等。如果不设置区域解析,则不需要区域解析程序。
- 主题解析器被绑定到请求,让视图等元素决定使用哪个主题。如果不使用主题,可以忽略。
- 如果指定了multipart文件解析器,会搜索请求中的multipart部分。如果找到了,则将请求包装进MultipartHttpServletRequest中,以供流程中其他的元素做进一步的处理。
- 搜索相关的Handler。如果Handler被找到,则执行与该Handler相关的处理链(如前置处理、后置处理、Controller等),以便获取model去渲染视图。或者,对于带注释的Controller,可以直接渲染response(通过HandlerAdpter),而不是返回视图。
- 如果返回了model,则渲染视图,否则,不会渲染。
看着不够直观。。。嗯,来结合源码看一下。
先来考虑一下,假设一个Post请求过来,根据Servlet Map,由DispatcherServlet处理。那么,查了DispatcherServlet的源码,没有对应Post的处理逻辑。。。不慌,DispatcherServlet继承了FrameworkServlet,它里面没有,父类里一定有,看一下FrameworkServlet的相关处理逻辑,有个叫doPost的挺靠谱:
/**
* Delegate POST requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
看注释,上面写着委托Post请求给processRequest,嗯,继续看processRequest的逻辑:
/**
* Process this request, publishing an event regardless of the outcome.
* <p>The actual event handling is performed by the abstract
* {@link #doService} template method.
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException ex) {
failureCause = ex;
throw ex;
}
catch (IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
有点长。。。往下看,有一个doService方法,靠谱,进去看看,是一个抽象方法,实例方法在子类DispatcherServlet中:
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<String, Object>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
嗯,看到了吗,注意那一堆request.setAttribute操作:
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
开心吧?找到组织了~这不正是对应了我们整理的DispatcherServlet处理请求的步骤的前4步吗。我们先来看一下这四步:
- 首先是将WebApplicationContext绑定在请求上,request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()),是通过一个getWebApplicationContext方法来获取WebApplicationContext。点进去看一下,这个方法属于FrameServlet类,这个是DispatcherServlet的父类,看代码:
/**
* Return this servlet's WebApplicationContext.
*/
public final WebApplicationContext getWebApplicationContext() {
return this.webApplicationContext;
}
通过代码,我们看到直接返回了一个webApplicationContext属性,那这个属性是在哪里设值的呢?去代码里找一下:
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
在构造器的里完成了复制操作,再去看一下这个构造器在什么时候被调用:
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
看到木有~在我们构建DisptacherServlet的时候,会将参数WebApplicationContext传给父类构造器,也就是FrameServlet的构造器,然后再父类构造里完成赋值。
也就是说,当我们通过一个WebApplicationContext创建DispatcherServlet以后,需要把DispatcherServlet注册到Servlet中,并设置servlet Map,那么在DispatcherServlet收到请求以后,就可以获取到已经配置的WebApplicationContext,并绑定到请求上。
- 然后是设置LocaleResolver,同样的,是获取this.localeResolver的值。来看这个localeResolver是哪里来的。查看代码,很容易找到这个localeResolver的赋值的位置,容器启动的时候会调用onRefresh方法,这个方法里调用了initLocaleResolver方法,就是在这里做的初始化。
/**
* Initialize the LocaleResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* we default to AcceptHeaderLocaleResolver.
*/
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver + "]");
}
}
}
如果我们配置了localeResolver,则使用我们自己的配置,如果没有配置的,就去通过getDefaultStrategy方法获取一个默认的LocaleResolver,这个默认的是什么呢?看代码:
/**
* Create a List of default strategy objects for the given strategy interface.
* <p>The default implementation uses the "DispatcherServlet.properties" file (in the same
* package as the DispatcherServlet class) to determine the class names. It instantiates
* the strategy objects through the context's BeanFactory.
* @param context the current WebApplicationContext
* @param strategyInterface the strategy interface
* @return the List of corresponding strategy objects
*/
@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<T>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Error loading DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]: problem with class file or dependent class", err);
}
}
return strategies;
}
else {
return new LinkedList<T>();
}
}
通过方法注释,就可以很清楚的看到,是从DispatchcerServlet.properties去获取的。
- themeResolver的获取与localeResolver相似,这里就不再赘述。有意思的一点是,Spring会把WebApplicationContext做为默认的ThemeSource来绑定到request上。
前面的三步都已经很清晰了,回到doService方法继续往下看,有个doDispatch方法,点进去:
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
- 对照着我们上面整理的步骤来看,这个方法里有一个checkMultipart方法的调用,看方法名好像跟步骤里的第四部有关,点进去看一下:
/**
* Convert the request into a multipart request, and make multipart resolver available.
* <p>If no multipart resolver is set, simply use the existing request.
* @param request current HTTP request
* @return the processed request (multipart wrapper if necessary)
* @see MultipartResolver#resolveMultipart
*/
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
"this typically results from an additional MultipartFilter in web.xml");
}
else if (hasMultipartException(request) ) {
logger.debug("Multipart resolution failed for current request before - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
看到方法注释就开心了,Convert the request into a multipart request,好了,找到第四步了~这个方法里,会通过multipartResolver(initStrategies里初始化的)来把请求转化成Multipart请求。
- 然后就是去找对应的handler了。继续看DispatcherServlet的代码,在检查完multipart后,有一个getHandler的操作。点进去看一下:
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
这个方法遍历了一个叫handlerMappings的属性,然后再分别对每一个HandlerMapping做getHandler操作。看一下这个getHandler的定义:
/**
* Return a handler and any interceptors for this request. The choice may be made
* on request URL, session state, or any factor the implementing class chooses.
* <p>The returned HandlerExecutionChain contains a handler Object, rather than
* even a tag interface, so that handlers are not constrained in any way.
* For example, a HandlerAdapter could be written to allow another framework's
* handler objects to be used.
* <p>Returns {@code null} if no match was found. This is not an error.
* The DispatcherServlet will query all registered HandlerMapping beans to find
* a match, and only decide there is an error if none can find a handler.
* @param request current HTTP request
* @return a HandlerExecutionChain instance containing handler object and
* any interceptors, or {@code null} if no mapping found
* @throws Exception if there is an internal error
*/
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
还是先看注释,上面说这个方法返回请求的一个handler和他的拦截器,那么我们就可以初步认识到,这个HandlerExecutionChain至少含有一个handler和一个拦截器的list。继续看实现类:
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
源码里的第一步,就通过getHandlerInternal获取了handler,然后再通过handler构建EHandlerExecutionChain。再进去看一下怎么获取的handler,这个getHandlerInternal方法是一个接口方法,我们找到位于AbstractUrlHandlerMapping的实例方法:
/**
* Look up a handler for the URL path of the given request.
* @param request current HTTP request
* @return the handler instance, or {@code null} if none found
*/
@Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
rawHandler = getRootHandler();
}
if (rawHandler == null) {
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}
这里就比较清晰了,通过URI来获取响应的handler。关于Handler的整理暂时先止于此,由于内容比较多,包括handlerMapping、handlerMethodMapping和handlerAdapter等,我们后面单独梳理。
- 接下来通过获取到的handler获取对应的handlerAdapter,然后通过handlerAdapter来执行所请求的处理。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
在执行请求处理的前后,会先执行HandlerInterceptor中的方法。其中,mv即是ModelAndView。这块的分析跟handler一起做。FLAG~~~
以上,就是DispatcherServlet接收到请求以后的大致处理流程。可以看到,上面的流程都是一些正常流程,那么,如果处理中,出现了错误怎么办呢?可以使用HandlerExceptionResolver来做自定义处理,后面会详细介绍。
Spring的DispatcherServlet支持返回last-modification-date,由Servlet API指定。过程也很简单,DispatcherServlet查找合适的HandlerMapping,并检测找到的Handler是否实现了LastModified接口。如果是这样,LastModified接口的getLastModified方法的返回值将返回给客户端。
可以通过想web.xml的servlet声明中添加初始化参数来自定义DispatcherServlet,支持的参数如下:
参数名 | 说明 |
---|---|
contextClass | 实现了ConfigurableWebApplicationContext的类,将被Servlet实例化和配置。默认使用XmlWebApplicationContext。 |
contextConfigLocation | 用于对Context指明上下文配置文件的路径,支持配置多个,如果配置了两个相同的,则以最后配置的为准 |
namespace | WebApplicationContext的命名空间,默认是[servlet-name]-servlet |
throwExceptionIfNoHandlerFound | 用于指定是否在找不对请求对应的handler的时候抛出NoHandlerFoundException,该异常可以被HandlerExceptionResolver处理,如被@ExceptionHandler的Controller。默认是false,在这种情况下,就会提示404。需要注意,如果配置了默认的servlet,则未匹配的请求会被转发到默认的servlet中,不会引发404. |
6. Interception
所有的HandlerMapping都支持HandlerInterceptor,当你想要将特定的功能用于某些请求的时候,这些拦截器就会非常有用。
自定义的拦截器要实现HandlerInterceptor接口,接口里有三个抽象方法必须实现,分别是:
- preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
- postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
- afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
/**
* @Author: kuromaru
* @Date: Created in 17:12 2019/7/10
* @Description:
* @modified:
*/
public class MyHandlerInteceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
留意一下preHandler的返回值,这个方法返回一个boolean类型的值,如果返回true,则会继续执行处理链上的逻辑,而当返回false的时候,DispatcherServlet会认为已经处理了请求,不会执行链上的其他拦截器和实际处理程序。
也可以通过实现HandlerMapping来注册Interceptor。看到这,应该就想起来我们上面整理的HandlerMapping的作用,即将请求映射到一个包含前置处理和后置处理的拦截器列表的处理。因为HandlerMapping本身就是为了把请求映射到含有拦截器的处理上,所以,我们就可以通过HandlerMapping来注册拦截器。后面我们再详细介绍。
需要注意一点,postHandler对于@ResponseBody和ResponseEntity方法的作用比较小,这些response的提交和写入是在HandlerAdapter内进行的,也就是在postHandler之前。为什么在这个之前呢?想一下,postHandler是在HandlerInterceptor中的,而HandlerInterceptor的执行
7. HandlerMapping和HandlerAdapter
一直有个疑问,HandlerMapping是为了将请求映射到含有拦截器的处理上,而HandlerAdapter则是为了帮助DispatcherServlet调用映射到请求的处理。感觉差不多,仔细读一下这两个的定义,就能发现,HandlerMapping是为了找到请求映射的处理,而HandlerAdapter是为了执行这个处理。就这么简单。
8. 异常处理
当请求过程中出现了异常,DispatchServlet委托HandlerExceptionResolver处理链来进行异常处理。
可用的HandlerExceptionResolver如下:
HandlerExceptionResolver实现类 | 说明 |
---|---|
SimpleMappingExceptionResolver | 异常类名和视图的映射解析,用于在浏览器中显示错误页面 |
DefaultHandlerExceptionResolver | 解析Spring MVC的异常,并映射为HTTP状态码 |
ResponseStatusExceptionResolver | 解析带有@ResponseStatus注解的异常,并基于注解中的value值,映射成HTTP状态码 |
ExceptionHandlerExceptionResolver | 通过调用被@Controller或@ControllerAdvice注解的类中带有@ExceptionHandler的方法来解析异常 |
上面的DefaultHandlerExcepiton,具体的HTTP状态码映射关系如下:
Exception | Http Status code |
---|---|
HttpRequestMethodNotSupportedException | 405 (SC_METHOD_NOT_ALLOWED) |
HttpMediaTypeNotSupportedException | 415 (SC_UNSUPPORTED_MEDIA_TYPE) |
HttpMediaTypeNotAcceptableException | 406 (SC_NOT_ACCEPTABLE) |
MissingPathVariableException | 500 (SC_INTERNAL_SERVER_ERROR) |
MissingServletRequestParameterException | 400 (SC_BAD_REQUEST) |
ServletRequestBindingException | 400 (SC_BAD_REQUEST) |
ConversionNotSupportedException | 500 (SC_INTERNAL_SERVER_ERROR) |
TypeMismatchException | 400 (SC_BAD_REQUEST) |
HttpMessageNotReadableException | 400 (SC_BAD_REQUEST) |
HttpMessageNotWritableException | 500 (SC_INTERNAL_SERVER_ERROR) |
MethodArgumentNotValidException | 400 (SC_BAD_REQUEST) |
MissingServletRequestPartException | 400 (SC_BAD_REQUEST) |
BindException | 400 (SC_BAD_REQUEST) |
NoHandlerFoundException | 404 (SC_NOT_FOUND) |
AsyncRequestTimeoutException | 503 (SC_SERVICE_UNAVAILABLE) |
根据HandlerExceptionResolver的约定,对于异常的处理可以有以下几个结果:
- 返回一个指向错误视图的ModelAndView
- 如果异常在resolver中被处理,允许返回一个空的ModelAndView
- 如果异常未被处理,允许向上抛出到Servlet
9. 视图解析
Spring MVC定义了ViewResolver和View接口,允许在浏览器中渲染Model,ViewResolver提供视图名称和实际视图之间的映射,View解决了在将数据移交给特定视图之前的数据准备工作。
具体的ViewResolver如下:
ViewResolver | 描述 |
---|---|
AbstractCachingViewResolver | 解析AbstractCachingViewResolver的子类,缓存提高了某些视图的性能,可以通过将缓存禁用来关闭缓存。如果必须在某个时间刷新视图,可以调用removeFromCache来使缓存失效。 |
XmlViewResolver | 允许接受一个与Spring XML Bean工厂相同DTD的xml配置文件,默认的配置文件是/WEB-INF/views.xml |
ResourceBundleViewResolver | 在ResourceBundle中使用Bean定义,通过bundle的名字来指定ViewResolver。对每一个视图,使用 视图名.(class) 做为视图的class,视图名.url做为视图的url。 |
UrlBasedViewResolver | 支持视图名到URL的直接解析,而不需要指定映射关系。 |
InternalResourceViewResolver | UrlBasedViewResolver的子类,支持InternalResourceView及子类,例如JSP、JstlView、TilesView等。可以通过setViewClass方法为所有视图指定视图类 |
FreeMarkerViewResolver | UrlBasedViewResolver的子类,支持FreeMarkerView及子类 |
ContentNegotiatingViewResolver | 基于请求中的文件名或者Accept请求头来解析视图 |
第三个flag...后面挨个整理这些视图解析器。。。不能再立了...(┬_┬)
我们可以声明多个解析器,作为一个视图解析器链。ViewResolver约定返回null来标识找不到视图。但是在JSP和InternalResourceViewResolver的情况下,判断JSP是否存在的唯一标准是通过RequestDispatcher来执行分派。因此,必须将InternalResourceViewResolver作为视图解析器链的最后一个环节。
可以通过在视图名前面指定redirect:来执行重定向。UrlBasedViewResolver将此识别为需要重定向的指令。效果与使用RedirectView的效果相同,不同的是可以直接在控制器里控制重定向。逻辑视图名,如 redirect:/myapp/ome/resource,相当于对当前Servlet上下文进行重定向,而如 redirect:http://myhost.com/some/arbitrary/path 这样的则重定向到一个决定路径。
需要注意的是,如果一个Controller的方法被@ResponseStatus注解,则注解的优先级要高于RedirectView。也就是说,以@ResponseStatus的响应状态为准。
还可以通过指定forward:前缀来指定一个转发。它会被UrlBasedViewResolver解析,并创建一个InternalResourceView来执行RequestDispatcher.forward()。但是,这个前缀不适用与InternalResourceViewResolver和JSP的InternalResourceView。
ContentNegotiatingViewResolver本身不解析视图,而是委托给其他的解析器,并且选择与请求表现的视图相类似的视图。如可以从Accept表头或者查询参数来确定表示哪一个视图。
ContentNegotiatingViewResolver通过将请求的Content-Type与每个ViewResolver关联的View所支持的Content-Type进行比较,来选择合适的视图。如果找不到合适的视图,则通过DefaultView属性来指定视图。
10. Locale
Spring MVC支持国际化,DispatcherServlet允许通过LocaleResolver设置自动解析消息。
当Request进来的时候,DispatcherServlet就去找Locale解析器,如果找到了,就会尝试用它去设置Locale。可以使用RequestContext.getLocale()方法来获取当前正在使用的Locale设置。也可以在特定情况下使用拦截器来更改Locale设置。
Locale解析器和拦截器在org.springframework.web.servlet.i18n包中。
- TimeZone
除了获取Client的Locale设置以外,了解时区通常也很有用。LocaleContextResolver接口提供了对LocaleResolver的扩展,允许解析器提供更丰富的LocaleContext,其中就包括时区信息。
通常可以使用RequestContext.getTimeZone()来获取时区信息。获取到的时区信息由注册到Spring的ConversionService的任何Date/Time转换器和格式化程序自动使用。 - Header Resolver
这个区域解析器检查请求的accept-language头部信息。通常,这个头部信息包含客户端操作系统的区域设置。需要注意,该解析器并不支持Time Zone信息。 - Cookie Resolver
该区域解析器检查客户端中的Cookie设置,看有没有区域设置或者时区信息被设置。如果存在,则使用指定的详细信息。通过使用此区域解析器的属性,我们可以指定cookie的名称和最大期限。
可以通过配置CookieLocaleResolver来设置cookie信息,如下:
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="cookieName" value="clientLanguage" />
<property name="cookieMaxAge" value="100000" />
</bean>
再来看一下CoolieLocaleResolver的属性:
Property | Default | Description |
---|---|---|
cookieName | className + LOCALE | cookie的名称 |
cookieMaxAge | Servlet容器的默认值 | cookie在客户端存活的最大时间。如果设为-1,则不会被持久化。仅在客户端关闭之前可用。 |
cookiePath | / | 将cookie的可见性限制在某个路径下。当指定cookiePath时,cookie仅在指定的路径及其子路径下可见。 |
- Session Resovler
SessionLocaleResolver允许从用户请求的会话Session中检索区域设置和时区设置。与CookieLocaleResolver不同的是,此解析器将本地的区域设置存储在Servlet的HttpSession中。因此,这些设置的生命周期与会话的生命周期相同。
注意,此设置与Spring Session项目无关。 - Locale Interceptor
可以通过将LocaleChangeInterceptor添加到HandlerMapping定义来启用对区域设置的更改。它检测请求中的参数,并相应的更改区域设置,在Dispatcher的应用上下文中调用LocaleResolver上的setLocale方法。看下面的例子:
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="siteLanguage" />
</bean>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="localeChangeInterceptor" />
</list>
</property>
<property name="mappings">
<value>/**/*.view=someController</value>
</property>
</bean>
上面的例子定义了一个LocaleChangeInterceptor,用于拦截请求请求参数中有siteLanguage的请求。比如,对于请求http://www.sf.net/home.view?siteLanguage=nl,就会将view的表示语言改为荷兰语。
11. Themes
可以通过设置Spring MVC框架的主题来设置应用程序的整体外观。
如果要在Web应用中使用主题,则必须设置org.springframework.ui.context.ThemeSource接口。WebApplicationContext接口扩展了ThemeSource接口,但是将职责委托给了专门的实现。默认情况下,委托类是从类路径的根目录加载属性文件的org.springframe.ui.context.support.ResourceBundleThemeSource实现。如果要使用自定的ThemeSource,可以在spring容器中使用保留名themeSource进行配置。
当我们使用ResourceBundleThemeSource的时候,theme通常会被定义在配置文件中,如下:
styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg
<html>
<head>
<link rel="stylesheet" href="<spring:theme code='styleSheet' />" type="text/css" />
</head>
<body style="background=<spring:theme code='background' />">
...
</body>
<html>
上面在配置文件中定义了两个属性:styleSheet和background,然后在html中,通过link标签,使用spring:theme code='styleSheet'将css引入到页面中,同样的方法,在body标签中设置了背景图片。
定义完主题以后,就需要对主题进行解析,spring会查找名为themeSource的bean做为主题解析器。支持的主题解析器有:
Class | Description |
---|---|
FixedThemeResolver | 选择固定主题,使用defaultThemeName属性进行设置 |
SessionThemeResolver | 主题在用户的HTTP会话中维护,需要在每个会话中设置一次,但是不会在会话之间持久化 |
CookieThemeResolver | 所选主题被存储在客户端的cookie中 |
同样的,spring也提供了一个ThemeChangeInterceptor来根据请求参数修改主题。
12. Multipart Resolver
来自org.springframework.web.multipart包的MultipartResolver是解析包括文件上传在内的多部分请求的策略。一种是基于Commons FileUpload的实现,另一种是基于Servlet3.0的multipart请求解析的实现。
如果要启用多部分处理,需要在DispatcherServlet Spring配置中声明一个名为multipartResolver的MultipartResolver Bean。DispatcherServlet检测到它并将其应用于传入的请求,当接收到的内容类型为multipart/form-data的Post请求时,解析器解析内容并将当前HttpServletRequest包装为MultipartHttpServletRequest,以提供对已解析部分的访问,并将它们作为请求参数公开。
若要使用Commons FileUpload,我们可以配置一个名为multipartResolver的CommonsMultipartResolver类型的bean,同时我们需要依赖commons-fileupload。
而Servlet3.0可以通过配置Servlet容器来使用,看代码:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
}
}
也可以通过在web.xml中配置
13. Logging
可以通过DispatcherServlet上的enableLoggingRequestDetails属性来设置是否在日志中显示完整的请求数据。
public class MyInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return ... ;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return ... ;
}
@Override
protected String[] getServletMappings() {
return ... ;
}
@Override
protected void customizeRegistration(Dynamic registration) {
registration.setInitParameter("enableLoggingRequestDetails", "true");
}
}
至此,DispatcherServlet告一段落,下面。。。开始填坑(┬_┬)