咻pur慢 2020-03-27
在web项目中,ContextLoaderListener起到的作用就是实例化一个父容器,管理跟逻辑业务相关的bean对象,Dispatcher实例化一个子容器,给管理跟前面比较近的一些bean对象。把拦截下来的请求,依据相应的规则分发到目标Handler来处理。
public class DispatcherServlet extends FrameworkServlet public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware
可以看到DispatcherServlet 有这样一些继承关系。
HttpServlet类初学web项目的时候经常用到的,它的作用就是可以处理不同的请求方法(比如:get方法,post方法等等),可以获得servletContext对象,可以获得为这个servlet配置的初始信息(initParam)。特别重要的是每一个servlet都是有生命周期的 init,service,destroy方法代表着这个servlet不同的生命周期。
httpServletBean抽象类除了继承了httpServlet类还是实现了EnvironmentCapable, EnvironmentAware接口。这两个接口的作用分别是返回一个Environment类型的对象和设置一个Environment类型的对象,相当于是为Environment的get/set方法专门设置了两个接口。httpServletBean抽象类主要的作用就是将web.xml中为DispatcherServlet配置的相关信息(init-param)赋值给对应的专门的变量中供后面使用。
FrameworkServlet抽象类除了实现HttpServletBean类还实现了ApplicationContextAware接口,这个接口的作用就是ApplicationContext的set方法。FrameworkServlet抽象类主要的作用集中在其initServletBean方法中,实例化了一个webApplicationContext并完成相关初始化。
DispatcherServlet继承了FrameworkServlet抽象类,他主要功能就是完成FrameworkServlet剩下的初始化工作以及对请求的处理。
从上面的继承可以看出DispacherServlet也是一个Servlet,在tomcat中使用一个servlet必须按照他的标准生命周期一步一步完成执行才行。
首先会先执行这个servlet的init方法对servlet进行初始化,初始化只执行一次,之后就可以多次使用service方法来处理你的请求了,最后在关闭容器的时候会使用destroy方法。既然DispacherServlet也是一个Servlet(继承了HttpServlet类,在web.xml配置的时候也是把它当做一个Servlet进行配置的),那么DispatcherServlet也需要经过这样的步骤才能被使用。
首先看看init()方法
public final void init() throws ServletException { // 将web.xml 中 dispatcherServlet的配置信息以key-value形式包装到PropertyValue中 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); //将为dispatchServlet配置的initParam赋值给相关变量 bw.setPropertyValues(pvs, true); } catch (BeansException ex) { throw ex; } } initServletBean(); }
上面函数将web.xml中dispatchServlet配置的信息(init-param)封装一个PropertyValue然后放在PropertyValues中。
然后把dispatchServlet包装成一个BeanWrapper类实例,这个类是Sping提供的一个来操作javaBean属性的工具,使用它可以直接修改一个对象的属性。bw.setPropertyValues(pvs, true)这个方法就是将配置的参数设置到对应的属性中去。
initServletBean()是一个模板方法,在FrameworkServlet具体实现。
至于它是如何实现这个功能的,就不去探究了。通过debug可以看到确实是将我们配置的信息设置到对象的属性中了。
在没有执行init()方法的时候可以看到contextId,contextConfigLocation是null值
在执行了PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties)语句后,就将配置的ContextConfigLocation信息包装到PropertyValue并放入PropertyValues的list中了。
在执行了bw.setPropertyValues(pvs, true)语句后可以看到ContextConfigLocation属性被设置了值(因为只设置了ContextConfigLocation参数,所以contextId还是null)。
接着看看initServletBean()方法做了什么事情
protected final void initServletBean() throws ServletException { .... try { this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); } catch (ServletException | RuntimeException ex) { logger.error("Context initialization failed", ex); throw ex; } }
可以看到这个函数调用了另外两个函数,其中initFrameworkServlet()是一个模板方法但是在其子类DispatcherServlet中并没有具体实现。
所以主要看看initWebApplicationContext()方法做了什么。
protected WebApplicationContext initWebApplicationContext() { //从servletContext中查找是否设置了rootContext,也就是ContextLoaderListener实例化的webApplicationContext 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()) { //设置父容器 if (cwac.getParent() == null) { cwac.setParent(rootContext); } //进行相关配置并实例化单例bean configureAndRefreshWebApplicationContext(cwac); } } } //如果没有通过构造方法注入,就查看servletContext中是否有DispacherServlet实例化的WebApplicationContext(通过查找Atrribute的方式) if (wac == null) { wac = findWebApplicationContext(); } //如果之前没有实例化过,那就实例化一个 if (wac == null) { wac = createWebApplicationContext(rootContext); } //初始化一些处理请求的工具 if (!this.refreshEventReceived) { synchronized (this.onRefreshMonitor) { onRefresh(wac); } } //将WebApplicationContext放入servletContext中 if (this.publishContext) { // Publish the context as a servlet context attribute. String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; }
可以看到大概做了这么几件事情:
步骤1.查找父容器(rootContext)
步骤2.获得一个WebApplicationContext
步骤3.onrefresh方法中初始化处理请求使用到的工具(比如:handlerMapping,handlerAdapter等等)
步骤4.将webApplicationContext设置到servletContext中
步骤1 和 步骤4 都很简单,容易理解。主要深入了解步骤2和步骤3。
步骤2---获得一个WebApplicationContext有三个步骤:
1.通过构造方法注入一个
2.通过servletContext获得一个(查找servletContext的atrribute属性,属性名和查找父容器不同)
3.自己实例化一个(主要手段)
看看是如何实例化一个webApplicationContext。
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { //获得要实例化的webApplicationContext的class对象,这里获得的是XmlWebApplicationContext class对象 Class<?> contextClass = getContextClass(); //判断class对象是否是ConfigurableWebApplicationContext的子类 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"); } //实例化一个 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); //设置相关属性 wac.setEnvironment(getEnvironment()); wac.setParent(parent); String configLocation = getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } //设置相关属性并实例化单例bean主要是(refresh方法) configureAndRefreshWebApplicationContext(wac); return wac; }
在看看为什么获得的是一个xmlWebApplicationContext class对象。
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; private Class<?> contextClass = DEFAULT_CONTEXT_CLASS; public void setContextClass(Class<?> contextClass) { this.contextClass = contextClass; } public Class<?> getContextClass() { return this.contextClass; }
在FrameworkServlet中直接指定了默认值,当然你也可以自己通过init-param配置webApplicationContext的class对象,前提是配置的对象必须是ConfigurableWebApplicationContext的子类。配置后会像contextConfigLocation属性一样注入到contextClass对象中。
在看看configureAndRefreshWebApplicationContext(wac)方法具体做了什么。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { //设置Contextid(这个属性可以自己配置) if (ObjectUtils.identityToString(wac).equals(wac.getId())) { if (this.contextId != null) { wac.setId(this.contextId); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + ‘/‘ + getServletName()); } } //设置ServletContext属性 wac.setServletContext(getServletContext()); //设置servletConfig属性 wac.setServletConfig(getServletConfig()); //设置namespace属性(可以自己配置) wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); ConfigurableEnvironment env = wac.getEnvironment(); //将servletContext,servletConfig作为属性保存在environment中 if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } //在刷新给定的WebApplicationContext之前对其进行后处理(后处理相信了解springFramework的一定知道) // 这是个空方法 postProcessWebApplicationContext(wac); //执行配置的实现了 ApplicationContextInitializer接口的类的initialize(C applicationContext)方法 applyInitializers(wac); //ioc容器启动方法,会设置一些属性然后实例化没有设置懒加载的单例的bean wac.refresh(); }
看看applyInitializers(wac)方法
protected void applyInitializers(ConfigurableApplicationContext wac) { //从servletContext中设置init-param中获得配置属性名为globalInitializerClasses的属性值 String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM); if (globalClassNames != null) { for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) { //分解globalInitializerClasses属性值(可能一次配了多个,用,隔开的) //然后loadInitializer方法实例化并放入contextInitializers中 this.contextInitializers.add(loadInitializer(className, wac)); } } // contextInitializerClass 配置在servlet的init-param属性中,然后通过跟ContextConfigLocation同样的方法注入 if (this.contextInitializerClasses != null) { for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) { this.contextInitializers.add(loadInitializer(className, wac)); } } // 进行排序 AnnotationAwareOrderComparator.sort(this.contextInitializers); //一个一个的执行其initialize方法 for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) { initializer.initialize(wac); } }
步骤3----在看看onRefresh方法,这是个模板方法,在DispatcherServlet中实现。
protected void onRefresh(ApplicationContext context) { initStrategies(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); }
可以看到它初始化了各种处理请求时用到的工具。包括MultipartResolver,LocaleResolver,ThemeResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver,RequestToViewNameTranslator,ViewResolvers,FlashMapManager。
HandlerMapping:是根据request找到响应的处理器handler和Intercepter。
HandlerAdapter:可以理解为使用处理器的。
HandlerExceptionResolver:是处理异常的。
ViewResolver:是将String类型的属兔名和Locale解析为View类型的视图。
RequestToViewNameTranslator: ViewResolver是根据ViewName查找View,但有的Handler处理完并没有设置view也没有设置viewName,这时就需要从
request获取viewName了,RequestToViewNameTranslator 就是做这件事的。
LocaleResolver: 解析视图需要两个参数:一个是视图名,另一个是locale。视图名是处理器返回的或者使用RequestToViewNameTranslator 解析的默认视
图名,locale则是由LocaleResolver解析出来的。
ThemeResolver: 是解析主题的。
MultipartResolver: 用于处理上传请求,处理方法是将普通的request包装成MltipartHttpServletRequest,然后直接调动其getFile方法获得file。
FlashMapManager:用于管理FlashMap的,FlashMap主要用在redirect中传递参数。
看看在DispatcherServlet中这些工具的声明
@Nullable private MultipartResolver multipartResolver; /** LocaleResolver used by this servlet */ @Nullable private LocaleResolver localeResolver; /** ThemeResolver used by this servlet */ @Nullable private ThemeResolver themeResolver; /** List of HandlerMappings used by this servlet */ @Nullable private List<HandlerMapping> handlerMappings; /** List of HandlerAdapters used by this servlet */ @Nullable private List<HandlerAdapter> handlerAdapters; /** List of HandlerExceptionResolvers used by this servlet */ @Nullable private List<HandlerExceptionResolver> handlerExceptionResolvers; /** RequestToViewNameTranslator used by this servlet */ @Nullable private RequestToViewNameTranslator viewNameTranslator; /** FlashMapManager used by this servlet */ @Nullable private FlashMapManager flashMapManager; /** List of ViewResolvers used by this servlet */ @Nullable private List<ViewResolver> viewResolvers;
所以可以看到有的工具只能有一个,而有的工具可以有多个。
看看是如何初始化这些工具的。
private void initMultipartResolver(ApplicationContext context) { try { this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); } catch (NoSuchBeanDefinitionException ex) { // Default is no multipart resolver. this.multipartResolver = null; } }
MultipartResolver工具的初始化过程是:首先从IOC容器中查找name=multipartResolver的bean对象,如果IOC容器中没有则会抛出异常,在catch中将其赋值为null。
private void initLocaleResolver(ApplicationContext context) { try { this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); } catch (NoSuchBeanDefinitionException ex) { // We need to use the default. this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); } }
LocaleResolver工具的初始化过程是:从IOC容器获取name=localeResolver的bean对象,如果IOC容器没有则抛出异常,在catch中可以看到这个语句this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);获得了一个默认的localeResolver给了this.localeResolver。
默认的LocaleResolver从哪里来??
static { try { 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()); } }
在DispatcherServlet中有这么一段静态代码段,加载了DispatchServlet.properties文件
文件位于org/springframework/web/servlet/DispatcherServlet.properties
文件内容:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
....
这个文件中设置一些必须工具的默认class(String类型),getDefaultStrategy方法会通过反射实例化这些默认工具(在自己没有配置的时候)
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. // 找出 所有的handlerMappings 包括父Context中的 Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.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); } }
HandlerMapping 还可以通过设置detectAllHandlerMappings来判断是否需要找出父类的HandlerMapping。