当前位置: 首页 > news >正文

加微信群网站怎么做的wordpress apache nginx

加微信群网站怎么做的,wordpress apache nginx,wordpress安装显示英文,深圳沙头角网站建设本篇重点在于分析Spring MVC与Servlet标准的整合#xff0c;下节将详细讨论Spring MVC的启动/加载流程、处理请求的具体流程。 一、介绍 Spring框架提供了构建Web应用程序的全功能MVC模块。通过策略接口 #xff0c;Spring框架是高度可配置的#xff0c;而且支持多种视图技…本篇重点在于分析Spring MVC与Servlet标准的整合下节将详细讨论Spring MVC的启动/加载流程、处理请求的具体流程。 一、介绍 Spring框架提供了构建Web应用程序的全功能MVC模块。通过策略接口 Spring框架是高度可配置的而且支持多种视图技术。Spring MVC框架并不知道使用的视图所以不会强迫你只使用JSP或特定某一种技术。Spring MVC分离了控制器、模型对象、分派器以及处理程序对象的角色这种分离让它们更容易进行定制。 Spring的MVC是基于Servlet功能实现的通过实现Servlet接口的DispatcherServlet来封装其核心功能的实现通过将请求分派给处理程序同时带有可配置的处理程序映射、视图解析、本地语言、主题解析以及上传文件支持。默认的处理程序是非常简单的Controller接口只有一个方法ModelAndView handleRequest(request, response)。 Spring MVC或者其他比较成熟的MVC框架而言解决的问题无外乎一下几点。 将Web页面的请求传给服务器。根据不同的请求处理不同的逻辑单元。返回处理结果数据并跳转至响应页面。 这里假设读者具有一定的使用经验下文将直接从代码出发。 二、ContextLoaderListener web.xml对于Spring MVC项目是必不可少的下面是一个简单的例子 ?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaeexmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsdversion4.0context-paramparam-namecontextConfigLocation/param-nameparam-value/WEB-INF/applicationContext.xml/param-value/context-paramlistenerlistener-classorg.springframework.web.context.ContextLoaderListener/listener-class/listenerservletservlet-namespring/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classload-on-startup1/load-on-startup/servletservlet-mappingservlet-namespring/servlet-nameurl-pattern*.do/url-pattern/servlet-mappingwelcome-file-listwelcome-file/index.do/welcome-file/welcome-file-list /web-app当使用编程方式的时候我们可以直接将Spring配置信息作为参数传入Spring容器中例如 ApplicationContext ac new ClassPathXmlApplicationContext(applicationContext.xml);但是在Web下我们需要更多的是与Web环境相互结合通常的办法是将路径以context-param的方式注册并使用ContextLoaderListener进行监听读取。 ContextLoaderListener的作用就是启动Web容器时自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口在web.xml配置这个监听器启动容器时就会默认执行它实现的方法。 使用 ServletContextListener 接口开发者能够在为客户端请求提供服务之前向ServletContext中添加任意的对象。这个对象在ServletContext启动的时候被初始化然后在ServletContext整个运行期间都是可见的。 每一个Web应用都有一个ServletContext与之相关联。ServletContext对象在应用启动时被创建在应用关闭的时候被销毁。ServletContext在全局范围内有效类似于应用中的一个全局变量。 在ServletContextListener中的核心逻辑便是初始化 WebApplicationContext 实例并存放至ServletContext中。 1、ServletContextListener的使用 正式分析代码前我们同样还是首先了解ServletContextListener的使用。 创建自定义的ServletContextListener 首先我们创建ServletContextListener目标是在系统启动时添加自定义的属性以便于在全局范围内可以随时使用。系统启动的时候会调用ServletContextListener实现类的contextInitialized方法所以需要在这个方法中实现我们的初始化逻辑。 package javax.servlet;public interface ServletContextListener extends EventListener {public void contextInitialized ( ServletContextEvent sce );public void contextDestroyed ( ServletContextEvent sce ); }public class MyDataContextListener implements ServletContextListener { private ServletContext context null;Overridepublic void contextInitialized(ServletContextEvent sce) {this.context sce.getServletContext();// 添加自定义属性context.setAttribute(myData, this is myData);}Overridepublic void contextDestroyed(ServletContextEvent sce) {this.context null;} }注册监听器 在web.xml文件中需要注册自定义的监听器。 listenerlistener-classsite.xiaokui.test.MyDataContextListener/listener-class /listener测试 一旦Web应用启动的时候我们就能在任意的Servlet或者JSP中通过下面的方法获取我们初始化的参数如下 String myData (String) getServletContext().getAttribute(myData);2、ContextLoaderListener** 分析了ServletContextListener的使用方式后再来分析Spring中的ContextLoaderListener的实现就容易理解的多虽然ContextLoaderListener实现的逻辑要复杂的多但是大致的套路还是万变不离其宗。 ServletContext启动之后会调用ServletContextListener的contextInitialized方法那么我们就从这个方法开始分析。 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {private ContextLoader contextLoader;public ContextLoaderListener() {}public ContextLoaderListener(WebApplicationContext context) {super(context);}Overridepublic void contextInitialized(ServletContextEvent event) {// 进入这一行initWebApplicationContext(event.getServletContext());}Overridepublic void contextDestroyed(ServletContextEvent event) {closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());} }这里涉及到一个常用类WebApplicationContext在Web应用中我们会用到WebApplicationContextWebApplicationContext继承自ApplicationContext在ApplicationContext的基础上又追加了一些特定于Web的操作及属性非常类似与我们通过编程方式使用Spring时的ClassPathXmlApplication类提供的功能。继续跟踪代码 // 来自类 ContextLoaderListener extends ContextLoader public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) ! null) {// web.xml中存在多次ContextLoader定义throw new IllegalStateException(Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!);}Log logger LogFactory.getLog(ContextLoader.class);servletContext.log(Initializing Spring root WebApplicationContext);if (logger.isInfoEnabled()) {logger.info(Root WebApplicationContext: initialization started);}long startTime System.currentTimeMillis();try {// Store context in local instance variable, to guarantee that// it is available on ServletContext shutdown.if (this.context null) {// 初始化Contextthis.context createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {// The context has not yet been refreshed - provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() null) {// The context instance was injected without an explicit parent -// determine parent for root web application context, if any.ApplicationContext parent loadParentContext(servletContext);cwac.setParent(parent);}// 加载xml配置并刷新WebApplicationContext// 这一步很关键configureAndRefreshWebApplicationContext(cwac, servletContext);}}servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl Thread.currentThread().getContextClassLoader();if (ccl ContextLoader.class.getClassLoader()) {currentContext this.context;}else if (ccl ! null) {currentContextPerThread.put(ccl, this.context);}if (logger.isDebugEnabled()) {logger.debug(Published root WebApplicationContext as ServletContext attribute with name [ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE ]);}if (logger.isInfoEnabled()) {long elapsedTime System.currentTimeMillis() - startTime;logger.info(Root WebApplicationContext: initialization completed in elapsedTime ms);}return this.context;}catch (RuntimeException ex) {logger.error(Context initialization failed, ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}catch (Error err) {logger.error(Context initialization failed, err);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);throw err;} }initWebApplicationContext方法主要是体现了创建WebApplicationContext实例的一个功能架构从方法中我们看到了初始化的大致步骤。 1、initWebApplicationContext 这个标题没什么特别的含义只是为了区分DispatchServlet中的initWebApplicationContext。 2、WebApplicationContext存在性的验证 在配置中只允许声明一次ServletContextListener多次声明会扰乱Spring的执行逻辑所以这里首先要做的就是对此验证在Spring中如果创建WebApplicationContext实例会记录在ServletContext中以方便全局调用而使用的key就是WebApplication.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE所以验证的方式就是查看ServletContext实例中是否存在对应key的属性 3、createWebApplicationContext 如果通过验证则Spring将创建WebApplicationContext实例的工作委托给了createWebApplication方法。 protected WebApplicationContext createWebApplicationContext(ServletContext sc) {Class? contextClass determineContextClass(sc);// 父类.class.isAssignableFrom(子类)时返回true否则返回falseif (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException(Custom context class [ contextClass.getName() ] is not of type [ ConfigurableWebApplicationContext.class.getName() ]);}return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }// public static final String CONTEXT_CLASS_PARAM contextClass; protected Class? determineContextClass(ServletContext servletContext) {String contextClassName servletContext.getInitParameter(CONTEXT_CLASS_PARAM);// 兼容自定义 WebApplicationContext 实现类if (contextClassName ! null) {try {return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException(Failed to load custom context class [ contextClassName ], ex);}}else {// 走默认策略默认就是 XmlWebApplicationContextcontextClassName defaultStrategies.getProperty(WebApplicationContext.class.getName());try {return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException(Failed to load default context class [ contextClassName ], ex);}} }其中在 ContextLoader 类中有这样的静态代码块 static {// Load default strategy implementations from properties file.// This is currently strictly internal and not meant to be customized// by application developers.try {ClassPathResource resource new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);defaultStrategies PropertiesLoaderUtils.loadProperties(resource);}catch (IOException ex) {throw new IllegalStateException(Could not load ContextLoader.properties: ex.getMessage());} }根据以上静态代码块的内容我们推断在当前类ContextLoader同样目录下必定会存在属性文件 ContextLoader.properties查看后果然存在内容如下 # Default WebApplicationContext implementation class for ContextLoader. # Used as fallback when no explicit context implementation has been specified as context-param. # Not meant to be customized by application developers. org.springframework.web.context.WebApplicationContextorg.springframework.web.context.support.XmlWebApplicationContext综合以上代码分析在初始化的过程中程序首先会读取ContextLoader类的同目录下的属性文件ContextLoader.properties并根据其中的配置提取将要实现WebApplicationApplicationContext接口的实现类并根据这个实现类通过反射方式进行实例的创建。 3、加载xml配置并刷新WebApplicationContext然后实例记录在servletContext中。 4、映射当前的类加载器与创建的WebApplicationContext实例到全局变量currentContextPerThread中。 三、configureAndRefreshWebApplicationContext 跟踪代码如下所示 // 来源于类 ContextLoader protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {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 informationString idParam sc.getInitParameter(CONTEXT_ID_PARAM);if (idParam ! null) {wac.setId(idParam);}else {// Generate default id...wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX ObjectUtils.getDisplayString(sc.getContextPath()));}}// 互相塞进去wac.setServletContext(sc);// 我们重点关注下这一句这里值为contextConfigLocationString configLocationParam sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam ! null) {// 这一步中已经初始化了 StandardServletEnvironmentwac.setConfigLocation(configLocationParam);}// The wac environments #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// 获取系统环境默认为 StandardServletEnvironmentConfigurableEnvironment env wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {// 将WebApplicationContex中的属性配置到 StandardServletEnvironment// 主要是servletContextInitParams、servletConfigInitParams两个属性分别保存着对象ServletContext servletContext, ServletConfig servletConfig后者这里为null((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}customizeContext(sc, wac);// XmlWebApplicationContextwac.refresh(); }1、setConfigLocation // 来源于类 AbstractRefreshableConfigApplicationContext public void setConfigLocation(String location) {setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS)); }public void setConfigLocations(Nullable String... locations) {if (locations ! null) {Assert.noNullElements(locations, Config locations must not be null);this.configLocations new String[locations.length];for (int i 0; i locations.length; i) {// 解析${}占位符如果有的话this.configLocations[i] resolvePath(locations[i]).trim();}}else {this.configLocations null;} }protected String resolvePath(String path) {return getEnvironment().resolveRequiredPlaceholders(path); }Override public ConfigurableEnvironment getEnvironment() {// 第一次进来为null后面就不为null默认实现为StandardServletEnvironmentif (this.environment null) {this.environment createEnvironment();}return this.environment; }Override protected ConfigurableEnvironment createEnvironment() {return new StandardServletEnvironment(); }2、getEnvironment // 来自类 AbstractApplicationContext Override public ConfigurableEnvironment getEnvironment() {// 第一次进来为null后面就不为null默认实现为StandardServletEnvironmentif (this.environment null) {this.environment createEnvironment();}return this.environment; }protected ConfigurableEnvironment createEnvironment() {return new StandardEnvironment(); }public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {/** Servlet context init parameters property source name: {value}. */public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME servletContextInitParams;/** Servlet config init parameters property source name: {value}. */public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME servletConfigInitParams;/** JNDI property source name: {value}. */public static final String JNDI_PROPERTY_SOURCE_NAME jndiProperties;Overrideprotected void customizePropertySources(MutablePropertySources propertySources) {propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));}super.customizePropertySources(propertySources);}Overridepublic void initPropertySources(Nullable ServletContext servletContext, Nullable ServletConfig servletConfig) {WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);}}// 替换占位符保证加载配置的优先级 public static void initServletPropertySources(MutablePropertySources sources,Nullable ServletContext servletContext, Nullable ServletConfig servletConfig) {Assert.notNull(sources, propertySources must not be null);String name StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;if (servletContext ! null sources.contains(name) sources.get(name) instanceof StubPropertySource) {sources.replace(name, new ServletContextPropertySource(name, servletContext));}name StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;if (servletConfig ! null sources.contains(name) sources.get(name) instanceof StubPropertySource) {sources.replace(name, new ServletConfigPropertySource(name, servletConfig));} }其中在类型为MutablePropertySources的propertySources中大致存储着以下5个PropertySource优先级依次降低如下 1、StubPropertySourceservletConfigInitParams 这是一个只有名称标识没有任何值的属性源只用作占位符当实际的属性源对象不能在ApplicationContext应用上下文创建的时候被立即初始化则会使用它来占位以保证属性源集合的搜索顺序。 举个例子在Web环境中创建StandardServletEnvironment对象时会先调用customPropertySources追加ServletConfig和ServletContext两个属性源对象但是此时并没有这两个对象的引用两个对象的引用通过initPropertySources初始化时传入因此会创建StubPropertySource对象占位当初始化时再用实际属性源替换掉根据name匹配占位对象。 StubPropertySource795553795 {name‘servletConfigInitParams’, propertiesjava.lang.Object12ed85a3} 2、StubPropertySourceservletContextInitParams 大致同上区别是一个对Servlet、一个对Context。 StubPropertySource476472887 {name‘servletContextInitParams’, propertiesjava.lang.Objectdae2226} 3、JndiPropertySourcejndiProperties JNDIJava Naming and Directory Interface Java命名和目录接口比较典型的例子就是Class.forName(“com.mysql.jdbc.Driver”) JndiPropertySource207182756 {name‘jndiProperties’, propertiesorg.springframework.jndi.JndiLocatorDelegate2d062814} 4、PropertiesPropertySourcesystemProperties 存放所有系统属性形如 java.vm.nameOpenJDK 64-Bit Server VM, ignore.endorsed.dirs, file.encodingUTF-8, com.sun.management.jmxremote.password.file/home/hk/.IntelliJIdea2019.2/system/tomcat/Tomcat_8_5_471_spring-web-demo/jmxremote.password, java.specification.version1.8, intellij.debug.agenttrue5、PropertiesPropertySourcesystemEnvironment 存放所有环境变量形如 PATH/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/sbin:/usr/sbin, XAUTHORITY/home/hk/.Xauthority, XMODIFIERSimfcitx, GDMSESSIONdeepin, USERhk, CLASSPATH/usr/dev/apache-tomcat-8.5.47/bin/bootstrap.jar:/usr/dev/apache-tomcat-8.5.47/bin/tomcat-juli.jarHOME/home/hk, SHLVL03、customizeContext 当我们设置了CONTEXT_INITIALIZER_CLASSES_PARAM参数指定初始化WebApplicationContext的类这一步会保证我们自定义的Context会执行相应初始化方法否则这一步为空逻辑实现。 // public static final String CONTEXT_CLASS_PARAM contextClass; protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {ListClassApplicationContextInitializerConfigurableApplicationContext initializerClasses determineContextInitializerClasses(sc);for (ClassApplicationContextInitializerConfigurableApplicationContext initializerClass : initializerClasses) {Class? initializerContextClass GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);if (initializerContextClass ! null !initializerContextClass.isInstance(wac)) {throw new ApplicationContextException(String.format(Could not apply context initializer [%s] since its generic parameter [%s] is not assignable from the type of application context used by this context loader: [%s], initializerClass.getName(), initializerContextClass.getName(),wac.getClass().getName()));}this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));}AnnotationAwareOrderComparator.sort(this.contextInitializers);for (ApplicationContextInitializerConfigurableApplicationContext initializer : this.contextInitializers) {initializer.initialize(wac);} }4、wac.refresh** 继续跟踪如下 // XmlWebApplicationContext wac.refresh();// 来自父类 AbstractApplicationContext Override public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.prepareRefresh();// Tell the subclass to refresh the internal bean factory.ConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn(Exception encountered during context initialization - cancelling refresh attempt: ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset active flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Springs core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}} }熟悉的代码请跳转到上一篇继续阅读谢谢。 … 假设读者已经看完了上一篇那么此时下面代码已经执行完成。 /*** Initialize the root web application context.*/ Override public void contextInitialized(ServletContextEvent event) {// 进入这一行initWebApplicationContext(event.getServletContext()); }此时Tomcat已经得到了一个完整的ServletContext接下来我们把断点打在DispatcherServlet的构造方法上再经历过27次F8后逻辑终于从org.apache.catalina包回到了熟悉的Spring源码中。 特别的此时有以下日志输出 20:25:07.002 [0.1][INFO] web.context.ContextLoader 271: Root WebApplicationContext: initialization started # 中间省略几百行 20:25:22.030 [0.1][INFO] web.context.ContextLoader 307: Root WebApplicationContext initialized in 15021 ms注意DispatcherServlet的构造方法并不是Servlet的入口构造方法中没有包含任何逻辑实现真正的逻辑实现是交给Servlet标准中的init方法。 四、初始化DispatcherServlet 在Spring中ContextLoaderListener只是辅助功能用于创建WebApplicationContext类型实例而真正的逻辑实现其实是在DispatchServlet中进行的DispatchServlet是实现Servlet接口的实现类。 1、Servlet概述 Servlet是一个Java编写的程序基于HTTP协议在服务器端运行主要处理客户端的请求并将处理结果发送到客户端。Servlet的生命周期是由Servlet容器来控制的分为3个阶段初始化、运行和销毁。 1、初始化阶段 Servlet容器加载Servlet类把Servlet类.class文件的数据读到内存中。 Servlet容器创建一个ServletConfig对象ServletConfig对象包含了Servlet的初始化配置信息。 Servlet容器创建一个Servlet对象。 Servlet容器调用Servlet对象的init方法进行初始化。 2、运行阶段 当Servlet容器收到一个请求时Servlet容器会针对这个请求创建ServletRequest和ServletResponse对象然后调用service方法并把这两个参数传递个service方法service方法通过ServletRequest对象获得请求的信息并处理请求。再通过ServletResponse对象生成这个请求的响应结果最后销毁ServletRequest和ServletResponse对象。我们不管这个请求是post提交的还是get提交最终这个请求都会由service方法来处理。 // 来自javax.servlet.http.HttpServlet protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method req.getMethod(); // 请求方法名都是大写的 GET if (method.equals(METHOD_GET)) {long lastModified getLastModified(req);if (lastModified -1) {// servlet doesnt support if-modified-since, no reason// to go through further expensive logicdoGet(req, resp);} else {long ifModifiedSince req.getDateHeader(HEADER_IFMODSINCE);if (ifModifiedSince (lastModified / 1000 * 1000)) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);} } else if (method.equals(METHOD_HEAD)) {long lastModified getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) {doPost(req, resp);} else if (method.equals(METHOD_PUT)) {doPut(req, resp); } // 省略其他3、销毁阶段 当Web应用被终止时Servlet容器会先调用Servlet对象的destroy方法然后再销毁Servlet对象同时也会销毁与Servlet对象相关联的ServletConfig对象。我们可以在destroy方法的实现中释放Servlet所占用的资源如关闭数据库连接关闭文件输入输出流等。 Servlet框架是由两个Java包组成分别为javax.servlet和java.servlet.http。在java.servlet包中定义了所有Servlet类都必须实现或扩展的通用接口和类在javax.servlet.http包中定义了采用HTTP通信协议的HttpServlet类。 Servlet被设计成请求驱动Servlet的请求可能包含多个数据项当Web容器接受到某个Servlet请求时Servlet把请求封装成一个HttpServletRequest对象然后把对象传给Servlet的对应的服务方法。 Http的请求方式包括delete、get、options、post、put、trace在HttpServlet类中分别提供了相应的服务方法它们分别是doDelete、doGet、doOptions、doPost、doTrace方法。 2、静态代码块 在类DispatcherServlet中有这么一段静态代码文件名为DispatcherServlet.properties注意区分之前的 ContextLoader.properties如下 // 在初始化相应的父类属性之后 static {// Spring Web内置不能由用户自定义// Load default strategy implementations from properties file.// This is currently strictly internal and not meant to be customized// by application developers.try {// DEFAULT_STRATEGIES_PATH DispatcherServlet.properties;ClassPathResource resource new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);defaultStrategies PropertiesLoaderUtils.loadProperties(resource);}catch (IOException ex) {throw new IllegalStateException(Could not load DEFAULT_STRATEGIES_PATH : ex.getMessage());} }加载properties文件内容如下 # Default implementation classes for DispatcherServlets 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.LocaleResolverorg.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver # 主题适配 org.springframework.web.servlet.ThemeResolverorg.springframework.web.servlet.theme.FixedThemeResolver # HandlerMapping处理url映射 org.springframework.web.servlet.HandlerMappingorg.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMapping # Handler适配器 org.springframework.web.servlet.HandlerAdapterorg.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.HandlerExceptionResolverorg.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.RequestToViewNameTranslatororg.springframework.web.servlet.view.DefaultRequestToViewNameTranslator # 视图解析 org.springframework.web.servlet.ViewResolverorg.springframework.web.servlet.view.InternalResourceViewResolver # 适配Flash org.springframework.web.servlet.FlashMapManagerorg.springframework.web.servlet.support.SessionFlashMapManager这段代码有什么用呢主要是提供Spring MVC中一些默认组件当用户没有自定义组件时Spring就会使用这些默认组件下篇将会详细展开讨论。 3、init方法** 这里有两个概念需要着重区分一个是ServletContext的入口一个是Servlet的入口。ServletContextListener是ServletContext的入口而init方法就是Servlet的入口。 在Servlet初始化阶段会调用其init方法所以我们首先要查看在DispatcherServlet中是否重写了init方法我们在其父类HttpServletBean中找到了该方法。 // DispatcherServlet 继承 FrameworkServlet 继承 HttpServletBean // 来自于类 HttpServletBean Override public final void init() throws ServletException {// Set bean properties from init parameters.PropertyValues pvs new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);// 一般情况下空所以可以直接跳过这个ifif (!pvs.isEmpty()) {try {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) {if (logger.isErrorEnabled()) {logger.error(Failed to set bean properties on servlet getServletName() , ex);}throw ex;}}// Let subclasses do whatever initialization they like.initServletBean(); }DispatcherServlet的初始化过程主要是通过将当前的Servlet类型实例转换为BeanWrapper类型实例以便使用Spring中提供的注入功能进行对应属性的注入。这些属性如contextAttribute、contextClass、nameSpace、contextConfigLacation等都可以在web.xml文件中以初始化参数的方式配置在Servlet的声明中。 DispatcherServlet继承自FrameworkServletFrameworkServlet类上包含对应的同名属性Spring会保证这些参数被注入到对应的值上。属性注入主要包含一下几个步骤。 1、封装及验证初始化参数 ServletConfigPropertyValues除了封装属性外还有对属性验证的功能。 private static class ServletConfigPropertyValues extends MutablePropertyValues {public ServletConfigPropertyValues(ServletConfig config, SetString requiredProperties)throws ServletException {SetString missingProps (requiredProperties ! null !requiredProperties.isEmpty()) ?new HashSetString(requiredProperties) : null;Enumeration en config.getInitParameterNames();while (en.hasMoreElements()) {String property (String) 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, , ));}} }从代码中得知封装属性主要是对初始化的参数进行封装也就是Servlet中配置的init-param中配置的封装。当然用户可以通过对requiredProperties参数的初始化来强制验证某些属性的必要性这样在属性封装的过程中一旦检测到requiredProperties中的属性没有指定初始值就会抛出异常。 2、将当前Servlet实例转换为BeanWrapper实例 PropertyAccessorFactory.forBeanPropertyAccess是Spring提供的工具类方法主要是将指定实例转换为Spring中可以处理的BeanWrapper类型的实例。 public abstract class PropertyAccessorFactory {public static BeanWrapper forBeanPropertyAccess(Object target) {return new BeanWrapperImpl(target);}public static ConfigurablePropertyAccessor forDirectFieldAccess(Object target) {return new DirectFieldAccessor(target);} }3、注册相对于Resource的属性编辑器 属性编辑器我们在上文已经介绍并且分析过其原理这里使用属性编辑器的目的就是在对当前实例DispatcherServlet属性注入过程中一旦遇到Resource类型的属性就会使用ResourceEditor去解析。 4、属性注入 BeanWrapper为Spring中的方法支持Spring的自动注入。其实我们最常用的属性注入无非是contextAttribute、contextClass、nameSpace、contextConfigLocation等属性。 4、initServletBean** 在ContextLoaderListener加载的时候已经创建了WebApplication实例而在这个函数中最重要的就是对这个实例进行进一步的补充初始化。 继续查看iniServletBean方法父类FrameworkServlet覆盖了HttpServletBean中的iniServletBean方法如下 // 来源于类 FrameworkServlet /*** Overridden method of {link HttpServletBean}, invoked after any bean properties* have been set. Creates this servlets WebApplicationContext.*/ Override 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 {// 注意这一步有点熟悉啊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);} }上面的方法设计了计时器来统计初始化的执行时间而且提供了一个扩展方法initFrameworkServlet用于子类的覆盖操作而作为关键的初始化逻辑实现则委托给了initWebApplicationContext方法。 五、initWebApplicationContext 为了避免混淆这里列下上文提到的initWebApplicationContext方法如下 // 来源于类 FrameworkServlet public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {// 列几句关键代码if (this.context null) {this.context createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac (ConfigurableWebApplicationContext) this.context;if (!cwac.isActive()) {// The context has not yet been refreshed - provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() null) {// The context instance was injected without an explicit parent -// determine parent for root web application context, if any.ApplicationContext parent loadParentContext(servletContext);cwac.setParent(parent);}configureAndRefreshWebApplicationContext(cwac, servletContext);}} }回到正题initWebApplicationContext方法的主要工作就是创建或刷新WebApplicationContext实例并对Servlet功能所使用的变量进行初始化。 // 来源于类 FrameworkServlet // 注意区分类ContextLoaderListener中initWebApplicationContext(event.getServletContext()) protected WebApplicationContext initWebApplicationContext() {// 根rootContext就是之前创建的WebApplicationContext需要注意的是如果上一步发生了异常/错误那么这里或抛出对应的异常/错误WebApplicationContext rootContext WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac null;// 第一个判断为nullif (this.webApplicationContext ! null) {// A context instance was injected at construction time - use itwac 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, etcif (cwac.getParent() null) {// The context instance was injected without an explicit parent - set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}// 刷新上下文环境configureAndRefreshWebApplicationContext(cwac);}}}// 第二个初始化判断为nullif (wac null) {// No context instance was injected at construction time - see if one// has been registered in the servlet context. If one exists, it is assumed// that the parent context (if any) has already been set and that the// user has performed any initialization such as setting the context idwac findWebApplicationContext();}// 第三个自己创建自己的WebApplicationContext注意这个WebApplicationContext是rootContext的子Contextif (wac null) {// No context instance is defined for this servlet - create a local onewac createWebApplicationContext(rootContext);}// 子WebApplicationContext初始化完成刷新自己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);}// 注册contextif (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、两个WebApplicationContex 首先要注意这里的两个WebApplicationContexrootContext和wac 简而言之 rootContext是全局Contextwac是它的子Context它们担任的角色不一样。rootContext定义的是Spring中所需要的bean或配置wac定义的是ServletMVC环境中所需要的bean或配置。rootContext不能引用wac中的bean或配置但wac却能引用rootContext中的bean或配置。大部分情况下rootContext是没有什么用的因为在Spring中一般只有一个Servlet。 下面还是继续跟踪对于本方法中的初始化主要包含以下几个部分熟悉的场景需要注意的是虽然方法名和大体逻辑是一样的但代码上还是有些小小上的区别 2、createWebApplicationContext 一般都是直接创建但不排除之前已经存在的可能WebApplicationContext的寻找及创建包括以下几个步骤。 1、通过构造方法的注入进行初始化 当进入initWebApplicationContext方法后通过判断this.webApplicationContext ! null后便可以确定是否通过构造方法来初始化的没有进行过初始化则进行下一步。 2、通过contextAttribute进行初始化 通过在web.xml文件中配置的Servlet参数contextAttribute来查找ServletContext中对应的属性默认为WebApplicationContext.class.getName() “.ROOT”也就是在ContextLoaderListener加载时会创建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.classClass? 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);}// 通过反射方式实例化contextClassConfigurableWebApplicationContext wac (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);// 这里又是new StandardServletEnvironment()wac.setEnvironment(getEnvironment());// 会合并父WebApplicationContext Environment,这里有对profile的处理wac.setParent(parent);// 获取contextConfigLocation属性配置在Servlet初始化参数中一般为nullwac.setConfigLocation(getContextConfigLocation());// 初始化Spring环境configureAndRefreshWebApplicationContext(wac);return wac; }1、setParent // 来自于类 AbstractApplicationContext Override public void setParent(Nullable ApplicationContext parent) {this.parent parent;if (parent ! null) {// 父Context内容其实也是 new StandardServletEnvironmentEnvironment parentEnvironment parent.getEnvironment();if (parentEnvironment instanceof ConfigurableEnvironment) {// 关键在于这一步里面有对profile的处理getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);}} }// 来自于类 AbstractEnvironment Override public void merge(ConfigurableEnvironment parent) {// 两者一样可跳过for (PropertySource? ps : parent.getPropertySources()) {if (!this.propertySources.contains(ps.getName())) {this.propertySources.addLast(ps);}}// 寻找激活的profile这一步是我们关注的// Spring会遍历多个环境遍历资源以寻找激活的profileString[] parentActiveProfiles parent.getActiveProfiles();if (!ObjectUtils.isEmpty(parentActiveProfiles)) {synchronized (this.activeProfiles) {Collections.addAll(this.activeProfiles, parentActiveProfiles);}}// 寻找默认名为default的profileString[] parentDefaultProfiles parent.getDefaultProfiles();if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {synchronized (this.defaultProfiles) {this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);Collections.addAll(this.defaultProfiles, parentDefaultProfiles);}} }Override public String[] getActiveProfiles() {return StringUtils.toStringArray(doGetActiveProfiles()); }protected SetString doGetActiveProfiles() {synchronized (this.activeProfiles) {if (this.activeProfiles.isEmpty()) {// spring.profiles.activeString profiles getProperty(ACTIVE_PROFILES_PROPERTY_NAME);if (StringUtils.hasText(profiles)) {setActiveProfiles(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(profiles)));}}return this.activeProfiles;} }public String getProperty(String key) {return getProperty(key, String.class, true); }Nullable protected T T getProperty(String key, ClassT targetValueType, boolean resolveNestedPlaceholders) {if (this.propertySources ! null) {// 遍历PropertySourcefor (PropertySource? propertySource : this.propertySources) {if (logger.isTraceEnabled()) {logger.trace(Searching for key key in PropertySource propertySource.getName() );}Object value propertySource.getProperty(key);if (value ! null) {if (resolveNestedPlaceholders value instanceof String) {value resolveNestedPlaceholders((String) value);}logKeyFound(key, propertySource, value);return convertValueIfNecessary(value, targetValueType);}}}if (logger.isTraceEnabled()) {logger.trace(Could not find key key in any property source);}return null; }public String[] getDefaultProfiles() {return StringUtils.toStringArray(doGetDefaultProfiles()); }// 默认内置了default profile protected SetString doGetDefaultProfiles() {synchronized (this.defaultProfiles) {if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {String profiles getProperty(DEFAULT_PROFILES_PROPERTY_NAME);if (StringUtils.hasText(profiles)) {setDefaultProfiles(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(profiles)));}}return this.defaultProfiles;} }2、configureAndRefreshWebApplicationContext** 无论是通过构造方法注入还是单独创建都免不了会调用configureAndRefreshWebApplicationContext方法来对已经创建WebApplicationContext实例进行配置及刷新那么这个步骤又做了那些工作呢 // 来自于类 FrameworkServlet 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 informationif (this.contextId ! null) {wac.setId(this.contextId);}else {// Generate default id...ServletContext sc getServletContext();if (sc.getMajorVersion() 2 sc.getMinorVersion() 5) {// Servlet 2.4: resort to name specified in web.xml, if any.String servletContextName sc.getServletContextName();if (servletContextName ! null) {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX servletContextName . getServletName());}else {wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX getServletName());}}else {// Servlet 2.5s getContextPath available!wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX ObjectUtils.getDisplayString(sc.getContextPath()) / getServletName());}}}// 又是互相塞进去wac.setServletContext(getServletContext());wac.setServletConfig(getServletConfig());// 这一行代码跟之前的有点不一样值为dispatcher-servletwac.setNamespace(getNamespace());// 注意这一行后面会来回来的wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));// The wac environments #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 #refreshConfigurableEnvironment env wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());}// 空逻辑留给子类实现postProcessWebApplicationContext(wac);// 如果使用了ApplicationContextInitializer自定义逻辑那么在这里执行// 如果使用的不是boot的话你别说想要加进去自己的逻辑还有点稍稍有点啰嗦下面给个例子applyInitializers(wac);// 熟悉的方法wac.refresh(); }例子如下 public class TestServlet extends DispatcherServlet {public TestServlet() {super();this.setContextInitializers(new TestApplicationContextInitializer());}private static class TestApplicationContextInitializer implements ApplicationContextInitializer {Overridepublic void initialize(ConfigurableApplicationContext applicationContext) {System.out.println(applicationContext 还没执行refresh之前调用了我);}} }无论调用方式如何变化只要是使用ApplicationContext所提供的功能最后都免不了使用公共父类AbstractApplicationContext提供的refresh()进行配置文件加载。 3、wac.refresh // 来源于类 AbstractApplicationContext跟父Context是同一个方法 // 熟悉的代码但是注意细节上的逻辑还是跟父WebApplicationContext有些区别的 // 大致走一下 Override public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// Prepare this context for refreshing.// 跟之前逻辑是一样的prepareRefresh();// Tell the subclass to refresh the internal bean factory.// 这里的区别在于getConfigLocations返回的是/WEB-INF/dispatcher-servlet.xmlConfigurableListableBeanFactory beanFactory obtainFreshBeanFactory();// Prepare the bean factory for use in this context.prepareBeanFactory(beanFactory);try {// Allows post-processing of the bean factory in context subclasses.// 以下代码逻辑跟之前都是一样的postProcessBeanFactory(beanFactory);// Invoke factory processors registered as beans in the context.invokeBeanFactoryPostProcessors(beanFactory);// Register bean processors that intercept bean creation.registerBeanPostProcessors(beanFactory);// Initialize message source for this context.initMessageSource();// Initialize event multicaster for this context.initApplicationEventMulticaster();// Initialize other special beans in specific context subclasses.onRefresh();// Check for listener beans and register them.registerListeners();// Instantiate all remaining (non-lazy-init) singletons.finishBeanFactoryInitialization(beanFactory);// Last step: publish corresponding event.// 区别在于这一步finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn(Exception encountered during context initialization - cancelling refresh attempt: ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset active flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Springs core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}} }4、finishRefresh protected void finishRefresh() {// Clear context-level resource caches (such as ASM metadata from scanning).clearResourceCaches();// Initialize lifecycle processor for this context.// new DefaultLifecycleProcessorinitLifecycleProcessor();// Propagate refresh to lifecycle processor first.// new DefaultLifecycleProcessor().onRefresh()getLifecycleProcessor().onRefresh();// Publish the final event.// 关键在于这一步发布最后一个事件这个刷新事件会触发DispatchServlet中的onFresh方法publishEvent(new ContextRefreshedEvent(this));// Participate in LiveBeansView MBean, if active.LiveBeansView.registerApplicationContext(this); }5、onRefresh调用的时机 // 我们还是回到最开始的initWebApplicationContext方法来自于类 FrameworkServlet // 这里想强调是 onFresh 方法不是在这里被调用的 protected WebApplicationContext initWebApplicationContext() {WebApplicationContext rootContext WebApplicationContextUtils.getWebApplicationContext(getServletContext());WebApplicationContext wac null;if (this.webApplicationContext ! null) {// A context instance was injected at construction time - use itwac 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, etcif (cwac.getParent() null) {// The context instance was injected without an explicit parent - set// the root application context (if any; may be null) as the parentcwac.setParent(rootContext);}configureAndRefreshWebApplicationContext(cwac);}}}if (wac null) {// No context instance was injected at construction time - see if one// has been registered in the servlet context. If one exists, it is assumed// that the parent context (if any) has already been set and that the// user has performed any initialization such as setting the context idwac findWebApplicationContext();}if (wac null) {// No context instance is defined for this servlet - create a local onewac 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.synchronized (this.onRefreshMonitor) {onRefresh(wac);}}if (this.publishContext) {// Publish the context as a servlet context attribute.String attrName getServletContextAttributeName();getServletContext().setAttribute(attrName, wac);}return wac; }// 而是在这里来自于类FrameworkServlet的内部类 ContextRefreshListener // 注入是在这个方法 configureAndRefreshWebApplicationContext 中的这行代码// 注意这一行后面会来回来的 wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));private class ContextRefreshListener implements ApplicationListenerContextRefreshedEvent {Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {FrameworkServlet.this.onApplicationEvent(event);} }// 类DispatcherServlet中进行了实现 Override protected void onRefresh(ApplicationContext context) {initStrategies(context); }protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context); }六、onFresh 在rootContext刷新完毕且servletContext刷新完后servletContext还需要执行一次特别的刷新这次刷新是为了完成对web的扩展。 // 类DispatcherServlet中进行了实现 Override protected void onRefresh(ApplicationContext context) {initStrategies(context); }protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context); }1、getDefaultStrategy 对于Spring MVC而言有些组件是不可或缺有些组件是可以没有的当用户或系统没有指定特定组件时Spring使用默认组件可能Spring已经帮你指定了但你还不知道所以这些默认的并不是一定的而要以实物为准。 虽然是以实物为准但大体上过一下还是很有必要的。以下是获取组件默认配置的方法 // 来自类 DispatcherServletinitStrategies各子方法都会调用本方法以获取默认web组件 SuppressWarnings(unchecked) protected T ListT getDefaultStrategies(ApplicationContext context, ClassT strategyInterface) {String key strategyInterface.getName();// key为类的全名形如org.xxx.XxxxString value defaultStrategies.getProperty(key);if (value ! null) {String[] classNames StringUtils.commaDelimitedListToStringArray(value);ListT strategies new ArrayList(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 DispatcherServlets default strategy class [ className ] for interface [ key ], ex);}catch (LinkageError err) {throw new BeanInitializationException(Unresolvable class definition for DispatcherServlets default strategy class [ className ] for interface [ key ], err);}}return strategies;}else {return new LinkedList();} }具体的默认组件就是之前提到过的DispatcherServlet.properties中的内容DispatcherServlet静态代码块如下 # Default implementation classes for DispatcherServlets 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.LocaleResolverorg.springframework.web.servlet.i18n.AcceptHeaderLocaleResolverorg.springframework.web.servlet.ThemeResolverorg.springframework.web.servlet.theme.FixedThemeResolverorg.springframework.web.servlet.HandlerMappingorg.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\org.springframework.web.servlet.function.support.RouterFunctionMappingorg.springframework.web.servlet.HandlerAdapterorg.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.HandlerFunctionAdapterorg.springframework.web.servlet.HandlerExceptionResolverorg.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverorg.springframework.web.servlet.RequestToViewNameTranslatororg.springframework.web.servlet.view.DefaultRequestToViewNameTranslatororg.springframework.web.servlet.ViewResolverorg.springframework.web.servlet.view.InternalResourceViewResolverorg.springframework.web.servlet.FlashMapManagerorg.springframework.web.servlet.support.SessionFlashMapManager继续追踪。 2、初始化MultipartResolver 在Spring中MultipartResolver主要用来处理文件上传。在高版本的Spring提供了StandardServletMultipartResolver基于Servlet3.0和CommonsMultipartResolver需要引入apache-common-upload架包。 MultipartResolver就是在initMultipartResolver(context)中被加入到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进而初始化MultipartResolver。 这个组件Spring默认是不配置的如果我们想支持文件上传需要自己显示声明。 3、初始化LocaleResolver 在Spring的国际化配置中一共有3种使用方式。分别如下 基于URL参数的配置 通过URL参数来控制国际化比如你在页面上加一句a href“?localezn_CH”简体中文/a来控制项目中使用的国际化参数。而提供这个功能的就是AcceptHeaderLocaleResolver默认的参数名为locale注意大小写。里面放的就是你的提交参数比如en_US、zh_CN之类的具体配置如下 bean idlocaleResolver classorg.springframework.web.servlet.il8n.AcceptHeaderLocaleResolver/基于session的配置 他通过检验用户会话中预置的属性来解析区域。最常用的是根据用户本次会话过程中的语言设定语言种类例如用户登录时选择语言种类则此次登录周期内同一使用此语言设定如果该会话属性不存在它会根据accept-language Http头部确定默认区域。 bean idlocaleResolver classorg.springframework.web.servlet.il8n.SessionLocaleResolver/基于Cookie的国际化配置 CookieLocaleResolver用于通过浏览器的cookie设置取得Locale对象。这种策略在应用程序不支持会话或者状态必须保存在客户端时有用配置如下 bean idlocaleResolver classorg.springframework.web.servlet.il8n.CookieLocaleResolver/这3种方式都可以解决国际化的问题但是对于LocaleResolver的使用基础是在DispatcherServlet中的初始化。 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 ]);}} }4、初始化ThemeResolver 在Web开发中经常会遇到通过主题Theme来控制网页风格这将进一步改善用户体验。简单地说一个主题就是一组静态资源比如样式表和图片他们可以影响应用程序的视觉效果。Spring中的主题功能和国际化功能非常相似。构成Spring主题主要包括如下内容。 主题资源 org.springframework.ui.context.ThemeSource是Spring中主题资源的接口Spring的主题需要通过ThemeSource接口来实现存放主题信息的资源。 org.springframework.ui.context.support.ResourceBundleThemeSource是ThemeSource接口默认实现类也就是通过ResourceBundle资源的方式定义主题在Spring中配置如下 bean idthemeSource classorg.springframework.ui.context.support.ResourceBundle.ThemeSourceproperty namebasenamePrefix valuecom.text. /property /bean 默认状态下是在类路径根目录下查找相应的资源文件也可以通过basenamePrefix来指定。这样DispatcherServlet就会在com.test包下查找资源文件。 主题解析器 ThemeSource定义了一些主题资源那么不同的用户使用什么主题资源由谁定义呢org.springframework.web.servlet.ThemeResolver是主题解析器的接口主题解析的工作便是由它的子类来完成。对于主题解析器的子类主要有3个比较常用的实现以主题文件summmer.properties为例 1FixedThemeResolver用于选择一个固定的主题。 bean idthemeResolver classorg.springframework.web.servlet.theme.FixedThemeResolverproperty namedefaultThemeName valuesummer/ /bean以上配置的作用是设置主题文件为summer.properties在整个项目内固定不变。 2CookieThemeResolver用于实现用户所选的主题以cookie的形式存放在客户端的机器上配置如下 bean idthemeResolver classorg.springframework.web.servlet.theme.CookieThemeResolverproperty namedefaultThemeName valuesummer/ /bean3)SessionThemeResolver用于把主题保存在用户的Http的session中 bean idthemeResolver classorg.springframework.web.servlet.theme.SsessionThemeResolverproperty namedefaultThemeName valuesummer/ /bean4AbstractThemeResolver是一个抽象类被SessionThemeResolver和FixedThemeResolver继承用户也可以继承它来自定义主题解析器。 拦截器 如果需要根据用户请求来改变主题那么Spring提供了一个已经实现的拦截器ThemeChangeInterceptor拦截器了配置如下 bean idthemeChangeInterceptor classorg.springframework.web.servlet.theme.ThemeChangeInterceptorproperty nameparamName valuethemeName/ /bean其中设置用户请求名参数为themeName即URL为themeName具体的主题名称。此外还需要在handlerMapping中配置拦截器需要在HandleMapping中添加拦截器。 property nameinterceptorslistref localthemeChangeInterceptor/list /property了解了主题文件的简单使用方式后再来查看解析器的初始化工作与其他变量的初始化工作相同主题文件解析器的初始化工作并没有任何需要说明的地方。 private void initThemeResolver(ApplicationContext context) {try {this.themeResolver context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);if (logger.isDebugEnabled()) {logger.debug(Using ThemeResolver [ this.themeResolver ]);}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.this.themeResolver getDefaultStrategy(context, ThemeResolver.class);if (logger.isDebugEnabled()) {logger.debug(Unable to locate ThemeResolver with name THEME_RESOLVER_BEAN_NAME : using default [ this.themeResolver ]);}} }5、初始化HandlerMappings 当客户端发出request时DispatcherServlet会将request提交给HandleMapping然后HandlerMapping根据WebApplicationContext的配置来回传给DispatcherServlet中相应的Controller。 在基于Spring MVC的Web应用程序中我们可以为DispatcherServlet提供多个HandlerMapping供其使用。DispatcherServlet在选用HandlerMapping的过程中将根据我们所指定的一系列HandlerMapping的优先级进行排序然后优先使用优先级在前的HandlerMapping。 如果当前HandlerMapping能够返回可用的HandlerDispatcherServlet则使用当前返回的Handler进行Web请求的处理而不在继续询问其他的HandlerMapping。否则DispatchServlet将继续按照各个HandlerMapping的优先级进行询问直到获取一个可用的Handler为止。初始化配置如下 private void initHandlerMappings(ApplicationContext context) {this.handlerMappings null;// 默认为trueif (this.detectAllHandlerMappings) {// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.MapString, HandlerMapping matchingBeans BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings new ArrayListHandlerMapping(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, well 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);}} }默认情况下Spring MVC将加载当前系统中所有实现HandlerMapping接口的bean。如果只期望Spring MVC加载指定的HandlerMapping时可以修改web.xml中的DispatcherServlet的初始参数将detectAllHandlerMappings的值设置为false。 init-paramparam-namedetectAllHandleMappings/param-nameparam-valuefalse/param-value /init-param此时Spring MVC将查找名为“handleMapping”的bean并作为当前系统唯一的HandlerMapping。如果没有定义HandlerMapping的话则Spring MVC将按照org.springframework.web.servlet.DispatcherServlet所在目录下的DispatcherServlet.properties中所定义的org.springframework.web.servlet.HandlerMapping的内容来加载默认的HandlerMapping用户没有自定义Strategies的情况下。 6、初始化HandlerAdapters 从名字也能联想到这是一个典型的适配器模式的使用在计算机编程中适配器模式将一个类的接口适配成用户所期待的。使用适配器可以使接口不兼容而无法在一起工作的类协同工作做法是将类自己的接口包裹在一个已存在的类中。那么在处理Handler时为什么会使用适配器模式呢回答这个问题我们首先要分析它的初始化逻辑。 private void initHandlerAdapters(ApplicationContext context) {this.handlerAdapters null;if (this.detectAllHandlerAdapters) {// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.MapString, HandlerAdapter matchingBeans BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerAdapters new ArrayListHandlerAdapter(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, well 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);}} }同样在初始化的过程中涉及了一个变量detectAllHandlerAdaptersdetectAllHandlerAdapters作用和detectAllHandlerMappings类似。只不过在无法找到对应的beanhandlerAdapter系统会尝试加载默认的适配器。 在getDefaultStrategies方法中Spring会尝试从defaultStrategies中加载对应的HandlerAdapter的属性。如果程序开发人员没有在配置文件中定义自己的适配器那么Spring会默认加载配置文件中的3个适配器。 作为总控制器的DispatcherServlet通过处理器映射得到处理器后会轮询处理器适配器模块查找能够处理当前Http请求的处理器适配器的实现处理器适配器模块根据处理器映射返回的处理器类型例如简单的控制器类型、注解控制器类型或远程调用处理器类型来选择某一个适当的处理器适配器的实现从而适配当前的Http请求。 1、Http请求处理器适配器HttpRequestHandlerAdapter Http请求处理器适配器仅仅支持对Http请求处理器的适配。它简单地将Http请求对象和响应对象传递给Http请求处理器的实现它不需要返回值。主要应用在基于Http的远程调用的实现上。 2、简单控制器处理器适配器SimpleControllerHandlerAdapter 这个实现类将Http请求适配到一个控制器的实现进行处理。这里的控制器的实现是一个简单的控制器接口的实现。简单控制器处理器适配器被设计成一个框架类的实现不需要被改写客户化的业务逻辑通常是在控制器接口的实现类中实现的。 3、注解方法处理器适配器AnnotationMethodHandlerAdapter 这个类的实现是基于注解的实现它需要结合注解方法映射和注解方法处理器协同工作。它通过解析声明在注解控制器的请求映射信息来解析相应的处理器方法来处理当前Http请求。在处理的过程中它通过反射来发现探测处理器方法的参数调用处理器方法并且映射返回值到模型和控制器对象最后返回模型和控制器对象给作为主控制器的DispatcherServlet。 我们现在基本上可以回答之前的问题了Spring中所使用的Handler并没有任何特殊的联系但是为了统一处理Spring提供了不同情况下的适配器。 7、初始化HandlerExceptionResolvers 基于HandlerExceptionResolver接口的异常处理使用这种方式只需要实现resolveException方法该方法返回一个ModelAndView对象在方法内部对异常的类型进行判断然后尝试生成对应的ModelAndView对象如果该方法返回了null则Spring会继续寻找其他的实现了HandlerExceptionResolver接口的bena。换句话说Spring会搜索所有注册在其环境中的实现了HandlerExceptionResolver接口的bean逐个执行直到返回一个ModelAndView对象。 public interface HandlerExceptionResolver {ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);}private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers null;if (this.detectAllHandlerExceptionResolvers) {// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.MapString, HandlerExceptionResolver matchingBeans BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers new ArrayListHandlerExceptionResolver(matchingBeans.values());// We keep HandlerExceptionResolvers in sorted order.OrderComparator.sort(this.handlerExceptionResolvers);}}else {try {HandlerExceptionResolver her context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);this.handlerExceptionResolvers Collections.singletonList(her);}catch (NoSuchBeanDefinitionException ex) {// Ignore, no HandlerExceptionResolver is fine too.}}// Ensure we have at least some HandlerExceptionResolvers, by registering// default HandlerExceptionResolvers if no other resolvers are found.if (this.handlerExceptionResolvers null) {this.handlerExceptionResolvers getDefaultStrategies(context, HandlerExceptionResolver.class);if (logger.isDebugEnabled()) {logger.debug(No HandlerExceptionResolvers found in servlet getServletName() : using default);}} }8、初始化RequestToViewNameTranslator 当Controller处理器方法没有返回一个View对象或逻辑视图名称并且在该方法中没有直接往response的输出流里面写数据的时候Spring就会采用约定好的方式提供一个逻辑实录名称。这个逻辑视图名称是通过Spring定义的org.springframework.web.servlet.RequestToViewNameTranslator接口的getViewName方法来实现的我们可以实现自己的RequestToViewNameTranslator接口来约定好没有返回视图名称的时候如何确定视图名称。 Spring已经给我们提供了一个它自己的实现那就是org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator。 在介绍DefaultRequestToViewNameTranslator是如何约定视图名称之前我们先来看一下它支持用户定义的属性 prefix前缀表示约定好的视图名称需要加上的前缀默认空串。suffix后缀表示约定好的视图名称需要加上的后缀默认空串。separator分隔符默认是斜杠“/”。stripLeadinSlash如果首字符是分隔符是否去除默认true。stripTrailingSlash如果最后一个字符是分隔符是否去除默认true。stripExtension如果请求路径包含扩展名是否要去除默认true。urlDecode是否对URL解码默认true。它会采用请求中指定的编码对URL进行解码。 这里举一个例子进行说明假设请求路径为https://www.xiaokui.site/blog/good-looking-hk/test.html真是请求URL为/blog/good-looking-hk/test.html则有如下结论 prefix和suffix如果都存在其他为默认值那么对应的逻辑视图名称应该是{prefix}blog/good-looking-hk/test{suffix}。stripLeadinSlash和stripTrailingSlash如果都为false其他默认这时候对应的逻辑视图应该是/blog/good-looking-hk/test.html如果是采用默认配置的话返回的逻辑视图名称应该是blog/good-looking-hk/test。 private void initRequestToViewNameTranslator(ApplicationContext context) {try {this.viewNameTranslator context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);if (logger.isTraceEnabled()) {logger.trace(Detected this.viewNameTranslator.getClass().getSimpleName());}else if (logger.isDebugEnabled()) {logger.debug(Detected this.viewNameTranslator);}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.this.viewNameTranslator getDefaultStrategy(context, RequestToViewNameTranslator.class);if (logger.isTraceEnabled()) {logger.trace(No RequestToViewNameTranslator REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME : using default [ this.viewNameTranslator.getClass().getSimpleName() ]);}} }9、初始化ViewResolvers 在Spring MVC中当Controller将请求处理结果放入ModelAndView中以后DispacherServlet会根据ModelAndView选择合适的视图进行渲染。ViewResolver接口定义了resolveViewName方法根据viewName创建合适类型的View实现。 如下是一个经典的jsp配置 // 提供默认InternalResourceViewResolver实现但默认前后缀均为空串 bean classorg.springframework.web.servlet.view.InternalResourceViewResolverproperty nameprefix value//property namesuffix value.jsp/ /beanviewResolver相关属性的初始化工作在initViewResolvers中完成的。 private void initViewResolvers(ApplicationContext context) {this.viewResolvers null;if (this.detectAllViewResolvers) {// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.MapString, ViewResolver matchingBeans BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.viewResolvers new ArrayList(matchingBeans.values());// We keep ViewResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.viewResolvers);}}else {try {ViewResolver vr context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);this.viewResolvers Collections.singletonList(vr);}catch (NoSuchBeanDefinitionException ex) {// Ignore, well add a default ViewResolver later.}}// Ensure we have at least one ViewResolver, by registering// a default ViewResolver if no other resolvers are found.if (this.viewResolvers null) {this.viewResolvers getDefaultStrategies(context, ViewResolver.class);if (logger.isTraceEnabled()) {logger.trace(No ViewResolvers declared for servlet getServletName() : using default strategies from DispatcherServlet.properties);}} }10、初始化FlashMapManager Spring MVC Flash attributes提供了一个请求存储属性可供其他请求使用。在使用重定向时候非常必要例如Post、Redirect、Get模式。Flash attributes在重定向之前暂存就像存在session中以便重定向之后还能使用并立即删除。 Spring MVC有两个主要的抽象来支持flash attributes。FlashMap用于保持flash attributes而FlashMapManager用于存储、检索、管理FlashMap实例。 flash attribute支持默认开启并不需要显示启用它永远不会导致Http Seesion的创建。这两个FlashMap实例都可以通过静态方法RequestContextUtils从Spring MVC的任何位置访问。初始化代码如下 private void initFlashMapManager(ApplicationContext context) {try {this.flashMapManager context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);if (logger.isTraceEnabled()) {logger.trace(Detected this.flashMapManager.getClass().getSimpleName());}else if (logger.isDebugEnabled()) {logger.debug(Detected this.flashMapManager);}}catch (NoSuchBeanDefinitionException ex) {// We need to use the default.this.flashMapManager getDefaultStrategy(context, FlashMapManager.class);if (logger.isTraceEnabled()) {logger.trace(No FlashMapManager FLASH_MAP_MANAGER_BEAN_NAME : using default [ this.flashMapManager.getClass().getSimpleName() ]);}} }至此DispatchServlet已经初始化完成特别的有如下输出日志 15:41:57.563 [0.1][INFO] web.servlet.FrameworkServlet 525: Initializing Servlet dispatcher # 中间省略若干行.... 15:42:04.758 [0.1][INFO] web.servlet.FrameworkServlet 547: Completed initialization in 7195 ms下篇我们将详细分析DispatchServlet中的doService、doGet、doPost等具体实现逻辑。
http://www.lakalapos1.cn/news/8807/

相关文章:

  • 会展中心网站平台建设方案睢宁建设局网站
  • 做企业竞争模拟的网站网站未及时续费
  • 常州语言网站建设wordpress小工具支持
  • dede 获取网站标题免费空间自带域名
  • 交换机做网站运动器材网站开发方案
  • 滁州市南谯区建设局网站自定义手机网站建设
  • 网站备案的账号找不到wordpress简单么
  • 昌邑市建设局官方网站门头广告设计图片
  • 做专题页的背景网站wordpress ssl插件
  • 做培训网站前端广州技术支持 奇亿网站建设
  • 网站在线建设2022电商平台哪个值得做
  • 做散热网站旅游网站开发的目的和意义
  • 淘宝优惠券微网站开发wordpress如何构建页面
  • 网站流量优化桂林网站制作培训学校
  • 网站主题颜色淄博网站制作哪家好
  • 网站建设的总体需求flash 网站 源码
  • 怎么花最少的钱做网站做 理财网站有哪些问题
  • 网站建设有哪些名词天水市网站建设
  • html5单页面网站seo搜索推广费用多少
  • 做二手衣服的网站张家港外贸网站设计
  • wordpress 站群模板呼和浩特做网站
  • 重庆颐众达网站wordpress安装文件下载
  • 建设游戏运营网站开展工作群辉可以做网站服务器吗
  • 如何做公司网站网页制作和设计实验目的
  • 中国做网站的公司有哪些网站的风格设计包括哪些内容
  • 儿童网站设计做本地网站能做吗
  • 室内设计效果图的网站兼职做网站这样的网站
  • 公司建设网站的申请报告效果图制作网站有哪些
  • 网站建设公司郴州北京十大装饰装修公司
  • 淮南高端网站建设苗木门户网站模板