在介绍DispatcherServlet的时候,我们得先了解 ,servlet。那什么时候servlet勒。
servlet是基于http协议的,在服务端(如Tomcate)运行的,是按照servlet规范编写的一个Java类。做Java Web开发的应该都了解它。主要用于处理客户端的请求并将其结果发送至客户端。servlet的生命周期可分三个阶段:初始化、运行和销毁。
1.初始化阶段
(1) servlet容器加载servlet类,把servlet类的.class文件中的数据读至内存中。
(2) servlet容器创建一个ServletConfig对象。
(3) 创建一个servlet对象
(4) servlet容器调用servlet对象的init方法进行初始化。
2.运行阶段
当servlet容器接收到一个请求时,servlet容器会针对该请求创建一个servletRequest和servletResponse对象,然后调用service方法。并把这两个参数传递给service方法。从servletRequest中获取请求信息,并处理请求。再通过servletResponse生成这个请求的响应结果。然后销毁对象。无论请求是get或者post,最终这个请求都会由service方法来处理。
3、销毁阶段
当Web应用被终止时,servlet容器会先调用servlet对象的destrory方法,然后销毁servlet对象。一般我们会在该方法中释放servlet所占用资源。如:数据库连接、关闭文件输入输出流等。
servlet的框架是由两个Java包组成:javax.servlet和javax.servlet.http. HttpServlet是定义的采用Http通信协议的Servlet的特例。也是我们常用的。毕竟大部我们的Web服务都是走的Http协议。HttpServlet针对Http请求的delete、get、post、options、put和trace.定义的相应的方法。
Servlet例子:
(1) 建议Servlet
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
handleTest(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
handleTest(req, resp);
}
@Override
public void init() {
System.err.println("初始化喽");
}
private void handleTest(HttpServletRequest request,HttpServletResponse response) {
System.err.println(" handleTest");
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher("/index.jsp");
try {
rd.forward(request, response);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
虽然只有一个类,但却是一个完整的Web处理过程。SpringMVC 的dispatcherServlet的最终目的也是在做这些事情。init方法在启动时被执行初始化。SpringMVC时该方法内执行了被始化initServletBean(). get/post()方法里处理了跳转逻辑。springMVC中也做了类似的逻辑,当然要比我们复杂的多。
添加配置:
myservlet
com.lhn.web.servlet.MyServlet
1
myservlet
*.action
请求:localhost/a.action
通过上面我们可以了解到在Servlet初始化阶段会调用init方法,so我们先从DispatcherServlet类中的init方法入手,首先看它是否重写了该方法。在其父类HttpServletBean中找到了该方法。
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
try {
//解析web.xml中init-param 并封装至pvs中。
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//将当前的Servlet类转为Spring可操作的Wrapper类
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//该处理为空实现,留给子类,使用模板模式
initBeanWrapper(bw);
//属性的注入
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
//模板模式留给子类扩展
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
DispatcherServlet的初始化过程主要是通过当前的servlet类型实例转换为BeanWrapper类型实例,以便使用Spring提供的注入功能进行对应属性的注入。这些属性如contextAttribute、contextClass、nameSpace、contextConfigLocation等。都可以在web.xml文件中以初始化参数的方式配置在servlet声明中。DipatcherServlet继承自Frameworker,该类中包含了对应的同名属性,Spring会将这些参数注入到对应的值中。属性注入步骤如下
上面代码中的第7行ServletConfigPropertyValues及做了该事情,同时也对属性进行验证。
public ServletConfigPropertyValues(ServletConfig config, Set requiredProperties)
throws ServletException {
Set missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?new HashSet(requiredProperties) : null;
Enumeration en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = en.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
if (missingProps != null && missingProps.size() > 0) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
该方法对初始化进行封装,通过requiredProperties参数验证servlet中配置的必须性。一旦没指定必须值,则异常。
2.将servlet实例转为BeanWrapper实例
3.注册相对于Resource的属性编辑器
如果有Resource类型的属性,就会使用ResouceEditor去解析。
4.属性注入
BeanWrapper自动注入属性。
5.servletBean的初始化
该过程由子类完成。在FrameworkServlet中覆盖了HttpServletBean中的initServletBean方法,如下
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
//关键初始化WebApplicationContext
this.webApplicationContext = initWebApplicationContext();
//子类覆盖
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
initWebApplicationContext方法的主要工作就是创建或者是刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// webApplicationContext 实例在构造函数中被注入
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
//刷新上下文环境
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//根据contextAttribute属性加载
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
本方法中的初始化主要包含几个部分
1、寻找或者创建对应的webApplicationContext实例
webApplicationContext的寻找及创建包含以下几个步骤
(1)通过构造函数的注入进行初始化。
当进入initWebApplicationContext方法后通过判断this.webApplicationContext != null 后,
便可以确定this.webApplicationContext是否是通过构造函数来初始化的。由于DispatcherServlet只可被声明一次,而this.webApplicationContext除了initWebApplicationContext()方法可以创建外,还可以通过构造函数来初始化。
(2)通过contextAttribute进行初始化
通过web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认为:WebApplicationContext.class.getName() + ".ROOT",也就是在ContextLoaderLister加载时会创建webApplicationContext实例,并将实例以WebApplicationContext.class.getName() + ".ROOT"为key放入ServletContext中,当然我们也可以重写初始化逻辑使用自己创建的WebApplicationContext,并在servlet的配置中通过初始化参数contextAttribute指定key.
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
(3) 重新创建WebApplicationContext 实例
通过以上方法都没有拿到实例,则只能重新创建新的实例。
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
//获取servlet的初始化参数,contextClass,如果没有配置,则默认为:XmlWebApplicationContext.class
Class> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
//通过反射方式实例化contextClass
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
//初始化Spring环境加载配置文件
configureAndRefreshWebApplicationContext(wac);
return wac;
}
configureAndRefreshWebApplicationContext
无论是通过构造方法注入还是通过单独创建,都免不了会调用configureAndRefreshWebApplicationContext方法来对已经创建的WebApplicationContext 实例进行刷新,下面看该方法做了哪些工作。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// the wac environment's #initPropertySources will be called in any case when
// the context is refreshed; do it eagerly here to ensure servlet property sources
// are in place for use in any post-processing or initialization that occurs
// below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
//加载配置文件及整合parent至wac
wac.refresh();
}
onRefresh是FrameworkServlet类中提供一个模版方法,在其子类DispatcherServlet中对它进行了重写,用于刷新Spring在Web功能实现中所必须使用的全局变量。我们说一下他的初始化过程以及使用的场景。
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
//(1)初始化MultipartResolver
initMultipartResolver(context);
//(2)初始化LocaleResolver
initLocaleResolver(context);
//(3)初始化ThemeResolver
initThemeResolver(context);
//(4)初始化HandlerMappings
initHandlerMappings(context);
//(5)初始化HandlerAdapters
initHandlerAdapters(context);
//(6)初始化HandlerExceptionResolvers
initHandlerExceptionResolvers(context);
//(7)初始化RequestToViewNameTranslator
initRequestToViewNameTranslator(context);
//(8)初始化ViewResolvers
initViewResolvers(context);
//(9)初始化FlashMapManager
initFlashMapManager(context);
}
(1)初始化MultipartResolver
在Spring中,MultipartResolver主要用于处理文件上传。默认情况下,Spring是没有multipart处理的,因为一些开发者想要自己处理它们。如果想使用Spring的multipart,则需要在Web应用的上下文中添加Multipart解析器。这样每一个请求就会被检查是否包含Multipart。如果包含Multipart,则定义的MultipartResolver就会解析它,这样请求中的MultipartResolver属性就会被处理。配置如下:
100000
CommonsMultipartResolver也提供也其它功能用于帮助用户完成上传功能。有空可以看一下。
MultipartResolver就是在initMultipartResolver方法中被加入至DispatcherServlet中的。
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
"': no multipart request handling provided");
}
}
}
在刷新前已经完成了Spring中配置文件的解析,这里只需要在 ApplicationContext通过getBean方法获取Bean就可。
在Spring中配置国际化配置。
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接口的实现可以看出有3种方式进行国际化。大家可以有兴趣可以看一下。
(3)初始化ThemeResolver
在Web中我们可能会遇到通过主题Theme来控制网页风格,每一个人或者一类人看到不同风格的网页以增加用户体验。
感觉兴趣的同学也可以看一下。
(4)初始化HandlerMappings
这个我们常与我们打交道,当客户端发出Request时DispatcherServlet会将Request提交给HandlerMapping,然后HandlerMapping根据ApplicationContext 的配置来回传给 DispatcherServlet相应的Controller。
在基于SrpingMVC的Web应用程序中,我们可以配置多个HandlerMapping。DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列的HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。如果当前的HandlerMapping能够返回可用的Hander,DispatcherServlet则使用当前返回的Handler进行Web请求处理,而不再继续询问其它的HandlerMapping。否则将继续寻找,直到找到一个可用的Handler为止。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
从代码中可以看出默认情况下,SpringMVC将加载当前系统中所有实现了HandlerMapping接口的bean。如果只期望加载指定的HandlerMapping时,可以修改Web.xml中的DispatcherServlet的初始参数。
detectAllHandlerMappings
false
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
OrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
}
}
}