SpringMVC学习记录2
废话
最近在看SpringMVC...里面东西好多...反正东看一点西看一点吧...
分享一下最近的一些心得..是关于DispatcherServlet的
DispatcherServlet与ContextLoaderListener
dispattcherServlet这个类大家肯定不陌生的...因为使用SpringMVC一定会在web.xml里配置它.
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 默认/WEB-INF/[servlet名字]-servlet.xml加载上下文, 如果配置了contextConfigLocation参数,
将使用classpath:/mvc-dispatcher-servlet.xml加载上下文 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/mvc-dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
从配置中就可以看出这是1个servlet.
而使用SpringMVC需要使用Spring,Spring在web环境中时通过一个listener来配置的
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <!-- 配置spring的bean用 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/spring/application-context.xml
</param-value>
</context-param>
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {.....}
ContextLoaderListener实现了ServletContextListener.
public interface ServletContextListener extends EventListener {
/**
** Notification that the web application initialization
** process is starting.
** All ServletContextListeners are notified of context
** initialization before any filter or servlet in the web
** application is initialized.
*/ public void contextInitialized ( ServletContextEvent sce ); /**
** Notification that the servlet context is about to be shut down.
** All servlets and filters have been destroy()ed before any
** ServletContextListeners are notified of context
** destruction.
*/
public void contextDestroyed ( ServletContextEvent sce );
}
ServletContextListener的contextInitialized是在任何filter或者servlet之前调用的.
贴了这么多代码片段只为了说明一个问题:Spring的applicationContext先于SpringMVC的applicationContext加载.
HttpServletBean
DispatcherServlet继承自FrameworkServlet继承自HttpServletBean继承自HttpServlet
所以我觉得按时间顺序的话应该从HttpServletBean的init方法看起.
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
} // Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
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");
}
}
9-14行主要就是把当前类的对象(dispatcherServlet)包装成BeanWrapperImpl对象,然后设置属性.
PropertyValues可以来自ServletConfig,然后PropertyValues又会被设置到dispatcher的属性里去.
从中我们可以得到的信息是什么呢?
我们可以得到的信息就是:
<init-param>
<!-- 默认/WEB-INF/[servlet名字]-servlet.xml加载上下文, 如果配置了contextConfigLocation参数,
将使用classpath:/mvc-dispatcher-servlet.xml加载上下文 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/mvc-dispatcher-servlet.xml</param-value>
</init-param>
dispatcherServlet里面的init-param我们可以配置dispatcherServlet里的所有属性名称和值
/** ServletContext attribute to find the WebApplicationContext in */
private String contextAttribute; /** WebApplicationContext implementation class to create */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS; /** WebApplicationContext id to assign */
private String contextId; /** Namespace for this servlet */
private String namespace; /** Explicit context config location */
private String contextConfigLocation; ...............
做完这些以后就转到22行,让FrameworkServlet来做剩下的事情
FrameworkServlet
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's 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");
}
}
FrameworkServlet我觉得就做了一件事情.就是初始化了applicationContext(第14行).只是这个初始化非常复杂.....
然后留了1个扩展点:
第15行initFrameworkServlet()现在的实现是空的.它会在applicationContext初始化完成之后被调用.
所以我们可以通过继承dispatcherServlet来扩展.但是话又说回来.Spring的扩展点真的很多.可以通过继承很多接口来参与Spring bean的生命周期.也并不一定要通过这个方法来做.
然后再来看看initWebApplicationContext方法...这个方法里面嵌套了N层..
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null; if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
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) {
// 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 id
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;
}
里面有很多种情况,很多if情况我都没遇到过..我只知道在我最一般的配置下.这个时候Spring的rootContext(11行)是已经完成了初始化.而SpringMVC的wac applicationContext还没有初始化.所以这个时候wac = createWebApplicationContext(rootContext);
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
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");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation()); configureAndRefreshWebApplicationContext(wac); return wac;
}
如果不通过init-param修改dispatcherServlet的属性的话.contextClass默认是XmlWebApplicationContext.class
所以通过BeanUtils通过反射来创建XmlWebApplicationContext(applicationContext).
然后各种setter方法以后调用configureAndRefreshWebApplicationContext方法
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);
wac.refresh();
}
然后又是各种setter方法.
18行这里添加了一个eventListener,当xmlWebapplicationContext refresh(创建各种bean?)以后会通知这个listener.
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> { @Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
而这个listener会delegate另外1个linstener ContextRefreshListener来处理.
而ContextRefreshListener是FrameworkServlet的内部类.因为是内部类.他可以调用FrameworkServlet的方法(onApplicationEvent方法).
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
onRefresh又是一个扩展接口.这个时间点是xmlWebapplicationContext finishRefresh的时候.
这个方法被dispatcherServlet用到了.后面会说.
25行 ConfigurableWebEnvironment的(实际上是StandardServletEnvironment)的initPropertySources方法具体做的事情是:
Replace
Servlet
-basedstub property sources
with actual instances populated with the givenservletContext
andservletConfig
objects.This method is idempotent with respect to the fact it may be called any number of times but will perform replacement of stub property sources with their corresponding actual property sources once and only once.
这我没太看懂...根据我的理解就是有些properties一开始在servletContext或者servletConfig还没有的时候会先用占位符properties去占位,然后现在通过这个方法替换成真正的包含servletContext或者servletConfig的properties.但是SpringMVC初始化的时候这些都是有了的...到底什么时候会存在这种情况我就不知道了..说不定Spring初始化xmlWebApplicationContext的时候会遇到吧.毕竟那个时候还是没有servletConfig的....
然后28行又是一个扩展点.
postProcessWebApplicationContext(wac);我们可以像先前那样,通过继承dispatcherServlet来实现.
这个时间点就是applicationContext还没有refresh(生成各种bean?)的时候.
然后又是1个扩展点:
applyInitializers(wac);
protected void applyInitializers(ConfigurableApplicationContext wac) {
String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
if (globalClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
} if (this.contextInitializerClasses != null) {
for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
} AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
可以通过在web.xml里配置globalInitializerClasses和contextInitializerClasses来初始化applicationContext
比如公司的做法:
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>XXXX.spring.SpringApplicationContextInitializer</param-value>
</context-param>
public class SpringApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> { /**
* The Constant LOGGER.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(SpringApplicationContextInitializer.class); /* (non-Javadoc)
* @see org.springframework.context.ApplicationContextInitializer#initialize(org.springframework.context.ConfigurableApplicationContext)
*/
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
ResourcePropertySource rps = new ResourcePropertySource(
applicationContext.getResource("classpath:application-cfg.properties"));
applicationContext.getEnvironment().getPropertySources().addFirst(rps);
} catch (IOException e) {
LOGGER.error("Fail to add properties in application-cfg.properties to environment", e);
}
}
}
通过编码方式强行加载application-cfg.properties
最后就是调用applicationContext的refresh方法了:
wac.refresh(); 容器的refresh里面也有很多很多东西.但是和SpringMVC好像没啥关系..和Spring有关.我就没细看了..
DispatcherServlet
前面提到在frameworkServlet初始化applicationContext finish的时候会触发监听器的事件,然后调用DispatcherServlet的onRefresh方法.
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
} /**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
这个方法要做的就是各种init....各种init方法里面都是差不多的.我们来看一个就可以了.
/**
* Initialize the HandlerMappings used by this class.
* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null; if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(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");
}
}
}
detectAllHandlerMappings默认是true.所以会去applicationContext里找HandlerMapping.class类型的bean.因为配置过<mvc:annotation-driven>所以可以加载到3个bean..
把dispatcherServlet里的成员变量指向这个HandlerMapping的list.因为后面分发请求的时候要用到.弄成成员域就不需要每次都去applicationContext里找了.
3个bean到底使用哪个呢? 先来排个序... 这个3个bean都实现了Ordered接口...排序结果不用多说大家肯定知道.RequestMappingHanddlerMapping肯定是最优先的...
后续
然后...然后以要做的后续事情就不多了...回到FrameworkServlet.把applicationContext放到ServletContext.与Spring初始化applicationContext的一样..
然后貌似就真的没有然后了....
思考与收获
这次学习学到了什么:
1. DispatcherServlet里有很多扩展点:
DispatcherServlet提供的扩展方法至少有(可能还有些我没发现):
(1) initFrameworkServlet(),在xmlWebApplicationContext初始化完成后(refresh完成后)调用
(2) postProcessWebApplicationContext(wac);在xmlWebApplicationContext各种set以后但是xmlWebApplicationContext还没有refresh(生成bean?)的时候调用
(3) onRefresh(ApplicationContext context);在xmlWebApplicationContext内部被调用,时间点是finish refresh的时候(bean初始化完成?).
DispatcherServlet指明的通过接口实现的扩展点至少有(可能还有些我没发现):
(1) 配置globalInitializerClasses或contextInitializerClasses,自己实现 ApplicationContextInitializer<C extends ConfigurableApplicationContext>接口,时间点是xmlWebApplicationContext refresh方法调用之前,postProcessWebApplicationContext(wac);之后.
2.结合上一篇文章,我知道了DispatcherServlet其实也并不是很特殊的,SpringMVC的@Controller自动委派请求等功能还是通过配置Bean(比如mvc:annotation-driven标签),让Bean参与Spring Bean的声明周期来完成的.所以这些事情是在xmlWebApplicationContext的refresh的时候发生的.并不是在DispatcherServlet里实现的.所以要搞清楚这些奇特的bean需要研究这些bean到底参与了bean声明周期的哪些步骤,到底做了写什么.此外还需要自己研究下xmlWebApplicationContext的refresh到底干了些什么事情.DispatcherServlet的initStrategies里的各种resolver,每种resolver都是为request与response的某一个环节服务的.比如RequestMappingHandlerMapping是为了让请求委派给正确的Controller服务的.研究这些resolver可以了解从解析请求到生成响应的细节.
3.对SpringMVC初始化的步骤有了了解.
SpringMVC学习记录2的更多相关文章
- springMVC学习记录1-使用XML进行配置
SpringMVC是整个spring中的一个很小的组成,准确的说他是spring WEB这个模块的下一个子模块,Spring WEB中除了有springMVC还有struts2,webWork等MVC ...
- SpringMVC学习记录5
Springmvc流程中的扩展点有很多,可以在很多地方插入自己的代码逻辑达到控制流程的目的. 如果要对Controller的handler方法做统一的处理.我想应该会有很多选择,比如:@ModelAt ...
- SpringMVC学习记录4
主题 SpringMVC有很多很多的注解.其中有2个注解@SessionAttributes @ModelAttribute我平时一般不用,因为实在是太灵活了.但是又有一定限制,用不好容易错.. 最近 ...
- SpringMVC学习记录3
这次的主题 最近一直在学习SpringMVC..(这句话我已经至少写了3,4遍了....).这次的研究主要是RequestMappingHandlerAdapter中的各种ArgumentsResol ...
- SpringMVC学习记录
1E)Spring MVC框架 ①Jar包结构: docs+libs+schema. 版本区别:核心包,源码包. SpringMVC文档学习: 学习三步骤: 1)是什么? 开源框架 2)做什么? IO ...
- springMVC学习记录2-使用注解配置
前面说了一下使用xml配置springmvc,下面再说说注解配置.项目如下: 业务很简单,主页和输入用户名和密码进行登陆的页面. 看一下springmvc的配置文件: <?xml version ...
- SpringMVC学习记录1
起因 以前大三暑假实习的时候看到公司用SpringMVC而不是Struts2,老司机告诉我SpringMVC各种方便,各种解耦. 然后我自己试了试..好像是蛮方便的.... 基本上在Spring的基础 ...
- springMVC学习记录3-拦截器和文件上传
拦截器和文件上传算是springmvc中比较高级一点的内容了吧,让我们一起看一下. 下面先说说拦截器.拦截器和过滤器有点像,都可以在请求被处理之前和请求被处理之到做一些额外的操作. 1. 实现Hand ...
- SpringMVC学习记录七——sjon数据交互和拦截器
21 json数据交互 21.1 为什么要进行json数据交互 json数据格式在接口调用中.html页面中较常用,json格式比较简单,解析还比较方便. 比如:webservi ...
随机推荐
- 简单说下COALESCE这个日常使用的函数
COALESCE 作用是返回第一个非空的值. SELECT COALESCE(NULL,NULL,'A','CC') ---- A 原理的话其实也是相当于 case when A is not nul ...
- python安装numpy、scipy和matplotlib等whl包的方法
最近装了python和PyCharm开发环境,但是在安装numpy和matplotlib等包时出现了问题,现总结一下在windows平台下的安装方法. 由于现在找不到了工具包新版本的exe文件,所以采 ...
- linux hosts文件详+mac主机名被莫名其妙修改
1.名词解析 主机名: 无论是在局域网还是在INTERNET上,每台主机都有一个IP地址,用来区分当前是那一台机器(其实底层是使用机器的物理地址),也就是说IP地址就是一个主机的门牌号,唯一的标示这一 ...
- [LeetCode] Nim Game 尼姆游戏
You are playing the following Nim Game with your friend: There is a heap of stones on the table, eac ...
- LTE中的各种ID含义
原文链接:http://www.hropt.com/ask/?q-7128.html ECI (28 Bits) = eNB ID(20 Bits) + Cell ID(8 Bits) 换成16进制就 ...
- hihocoder-1453-Rikka with Tree
#Hihocoder 1453 : Rikka with Tree 时间限制:10000ms 单点时限:1000ms 内存限制:256MB source: https://hihocoder.co ...
- jq.validate隐藏元素忽略验证
jq.validate隐藏元素忽略验证 现在有这样一个需求,当触发某类事件时候,需要在页面中显示input框,但是当不需要加载页面中的元素时候,进行隐藏.在这个需求的前提下,程序中对于input中的输 ...
- windows中LNK文件打开方式恢复(每日一修(1))
相信有些用户曾试过错误地把LNK文件的打开方式更改其他文件,导致系统所有的快捷方式都失效.在vista与Windows7系统还不普遍使用的时候,相信大家会有点惊慌失措,不要紧,下面只要大家进行如下操作 ...
- JsonResult类设置返回json的长度(工作笔记 json转化出错)
public JsonResult PoundageReportSearch() { JsonResult jr = new JsonResult(); // 实例 ...
- console的花式用法
1.百度的招聘启示 如图: 下面是输出代码: if(window.console){ var cons = console; if(cons){ cons.log("%c\n ", ...