1. 二话不说先上图
- 该图是以为大咖在他的文章里发的,我只是转载,学习。
2.HttpServletBean
-
HttpServletBean主要负责init-param里面的属性注入
-
springmvc利用spring的BeanWrapper功能,自动设置了initParam的所有值到DispathServlet中去。先上代码,配注释
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
try {
// 从servletconfig中读取所有的initParam,并转换成spring的PropertyValues
// PropertyValues中包含了PropertyValue数组,PropertyValue是一个name,value对
// ServletConfigPropertyValues是HttpServletBean的一个内部类,稍后帖源码
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
// 创建BeanWrapper,BeanWrapper是一个对象的包裹器,提供对这个对象的属性信息的修改访问等功能
// 需要特别注意这里的this(this很经典),HttpServletBean是抽象类,这里的this其实就是DispatchServlet实例。所以BeanWrapper包裹的就是DispatchServlet
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
initBeanWrapper(bw);
// 设置所有init-param值到DispatchServlet中
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");
}
}
- 可以看看ServletConfigPropertyValues
private static class ServletConfigPropertyValues extends MutablePropertyValues {
// requiredProperties其实是子类定义的哪些init-param是必须的
public ServletConfigPropertyValues(ServletConfig config, Set requiredProperties)
throws ServletException {
// 用一个hashset来记录所有必须存在的init-param,遍历真正的param时,找到一个就从这里remove掉一个,最后遍历完了,还有=没有remove完,则会报错(DispatchServlet没有定义requiredProperties)
Set missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
new HashSet(requiredProperties) : null;
// 从servlet中读取所有的init-param值
Enumeration en = config.getInitParameterNames();
while (en.hasMoreElements()) {
String property = (String) en.nextElement();
Object value = config.getInitParameter(property);
// 调用父类的addPropertyValue方法添加值
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, ", "));
}
}
}
- 再看看BeanWrapper到底是干啥的
可以举个例子,加入我们有这样一个bean:
public class Test {
private String a;
private String b;
// 嵌套
private Test c;
/*
getter,setters
*/
}
我们可以用BeanWrapper包装它,然后直接操作其属性:
Test tb = new Test();
BeanWrapper bw = new BeanWrapperImpl(tb);
bw.isReadableProperty("a");//判断a属性是否可读
bw.setPropertyValue("a", "tom"); //设置name属性的值为tom
bw.getPropertyValue("b")//取得属性
// 设置可以属性嵌套
bw.setPropertyValue("c.a", "tom");
// bw 同样支持数组和集合,map属性设置 (这两个值只是举例,Test中无此属性)
bw.getPropertyValue("array[0].name");
bw.getPropertyValue("map[key4][0].name");
- 综合,HttpServletBean就是提供了init-param的自动注入功能,注入完成后调用子类的initServletBean实现真正的web spring上下文初始化
3.可爱强大的FrameworkServlet,真正的web层spring初始化工作执行者
-
FrameworkServlet就是用来初始化web的controller, Interceptor, handerMapping, hanlderAdapter这些web层的组件的。(关键:它就是根据init-param里面配置的spring-servlet.xml进行web层spring初始化的)
-
看看源码,主要看initServletBean,它复写了HttpServletBean。
protected final void initServletBean() throws ServletException, BeansException {
// 省略
try {
// 可以看到这里调用initWebApplicationContext进行了spring上下文的初始化
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
// 省略
}
- 下面我们进入initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() throws BeansException {
// 先看看是否已经存在上下文了
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
// 查找根上下文,即在web.xml中配置的org.springframework.web.context.ContextLoaderListener;
WebApplicationContext parent =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// 创建web层的spring上下文,并以根上下文为父上下文
wac = createWebApplicationContext(parent);
}
if (!this.refreshEventReceived) {
// Apparently not a ConfigurableApplicationContext with refresh support:
// triggering initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
// 将上下文放进servlet的属性中去
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
- 进入createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
throws BeansException {
// 省略非关键代码
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());
// 设置父上下文
wac.setParent(parent);
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
// 设置xxx-servlet.xml的位置
wac.setConfigLocation(getContextConfigLocation());
wac.addApplicationListener(new SourceFilteringListener(wac, this));
postProcessWebApplicationContext(wac);
// 进入spring初始化逻辑
wac.refresh();
return wac;
}
-
这里需要理清楚第一件事,父子上下文是个什么鬼?spring的applicationcontext可以有父子关系的,子上下文内的组件可以使用访问父上下文的组件(如:你在controller中可以直接@autowired你在service层定义的bean),而父上下文是不能访问子上下文的组件的,springmvc对web层和业务层的spring容器进行了隔离。(原因?个人觉得是防止污染吧,其实就用一个上下文也没啥问题呀,这里给个传送门,stackoverflow上的人的回答:https://stackoverflow.com/questions/18682486/why-does-spring-mvc-need-at-least-two-contexts)
-
父子上下文不是在一段代码里连续执行的,他们是怎么能关联起来的?
哈哈,秘密很简单啊,就是利用ServletConfig嘛,root上下文初始化完成后先放进ServletConfig的属性里,web的上下文初始化时去读取就行啦,我们看代码验证下吧
先看看root上下文的初始化,查看ContextLoaderListener源码,
@Override
public void contextInitialized(ServletContextEvent event) {
// 调用父类
initWebApplicationContext(event.getServletContext());
}
再看看initWebApplicationContext方法:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 可以看到如果初始化完了是放在servletContext的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性里面的
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
// 已经初始化过了会直接报错哦
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只用一次,所以不做成类变量了,直接写在方法里。哈哈,可以的
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) {
// spring上下文初始化
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, etc
if (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);
}
}
// 终于找到你,把rootcontext放到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;
}
}
再回头看看我们的webcontext的初始化入口:
protected WebApplicationContext initWebApplicationContext() throws BeansException {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {org.springframework.web.context.ContextLoaderListener;
// 我们看看它是如何找到根上下文的
WebApplicationContext parent =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
wac = createWebApplicationContext(parent);
}
// 省略。。。
}
进入这个方法:
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
// 哈哈哈,就是你servlet的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
- 综合,FrameworkServlet就是对xxx-servlet.xml这个配置文件进行spring初始化的,所有的controller等东东都已经被spring托管了
4.真正的mvc核心分发器:DispatchServlet
-
FrameworkServlet把所有的玩意儿都纳入了spring的怀抱,那么DispatchServlet就是真正的这些玩意儿的使用者,它从spring中读取所有的这些玩意,并存在自己的属性里,用它们去处理每一个http请求。
-
先看他的初始化
/*
就是从sping里读取它所欲想要的所有bean,读取不到自己也有定义默认bean。
常见读取方式:根据beaname读取,根据class读取等等
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
}
随便找个看看,例如initHandlerMappings方法,源码的注释已经很清楚了,先加载,加载不到直接反射创建默认bean:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext,
// including ancestor contexts.
Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList(matchingBeans.values());
// We keep HandlerMappings in sorted order.
Collections.sort(this.handlerMappings, new OrderComparator());
}
}
else {
try {
Object hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
- 当上面这些玩意儿都读取到之后,springmvc就可以处理http请求了。
有请求来了,看我们的DispatchServlet干了些什么
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String requestUri = new UrlPathHelper().getRequestUri(request);
logger.debug("DispatcherServlet with name '" + getServletName() +
"' processing request for [" + requestUri + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
logger.debug("Taking snapshot of request attributes before include");
attributesSnapshot = new HashMap();
Enumeration attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
try {
doDispatch(request, response);
}
finally {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
入口处,把一些属性丢进request中去,就直接进入doDispatch了(用request的属性来存放这一对信息真的不错哦,看这doDispatch方法,就两个参数,request和response,不错。。),下面进入doDispatch方法(springmvc的核心之核心方法):
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
// 执行链,包含一堆拦截器和一个真正的hanler(即controller)
HandlerExecutionChain mappedHandler = null;
int interceptorIndex = -1;
// Expose current LocaleResolver and request as LocaleContext.
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
// Expose current RequestAttributes to current thread.
RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread: " + request);
}
try {
ModelAndView mv = null;
boolean errorView = false;
try {
// 如果是上传文件请求,对request进行转换。通过multipartResolver
processedRequest = checkMultipart(request);
// Determine handler for the current request.
// 这一步尤其至关重要,根据request匹配执行链
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Apply preHandle methods of registered interceptors.
// 执行执行链里所有的拦截器的preHandle方法
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
// Actually invoke the handler.
// 根据hanlder获取到hanlderAdapter(hanlder的执行者)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 用HandlerAdapter去执行hanlder得到结果视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
// Apply postHandle methods of registered interceptors.
// 执行完成后执行拦截器的所有postHandle方法
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
}
catch (ModelAndViewDefiningException ex) {
logger.debug("ModelAndViewDefiningException encountered", ex);
mv = ex.getModelAndView();
}
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" +
getServletName() + "': assuming HandlerAdapter completed request handling");
}
}
// Trigger after-completion for successful outcome.
// 执行拦截器的afterCompletion方法
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
catch (Exception ex) {
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
finally {
// Clean up any resources used by a multipart request.
if (processedRequest != request) {
cleanupMultipart(processedRequest);
}
// Reset thread-bound context.
RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
// Clear request attributes.
requestAttributes.requestCompleted();
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
}
}
可以看到大体流程:通过request匹配执行链,执行前置拦截器,通过执行链获取hanlderadapter,hanlderadapter执行真正的业务逻辑,执行postHandle拦截器,试图渲染,执行后置拦截器
先看看springmvc默认的一些组件实例:
image.png- 匹配执行链
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
HandlerExecutionChain handler =
(HandlerExecutionChain) request.getAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
if (handler != null) {
if (!cache) {
request.removeAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE);
}
return handler;
}
// 可以看到是遍历handlerMappings找到匹配的hanlderchain
Iterator it = this.handlerMappings.iterator();
while (it.hasNext()) {
HandlerMapping hm = (HandlerMapping) it.next();
if (logger.isTraceEnabled()) {
logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" +
getServletName() + "'");
}
handler = hm.getHandler(request);
if (handler != null) {
if (cache) {
request.setAttribute(HANDLER_EXECUTION_CHAIN_ATTRIBUTE, handler);
}
return handler;
}
}
return null;
}
不想写了,根据上面的图可以找到默认的handlermaping adapter等可以去看看具体是怎么匹配的...
最后
其实我们可以自己实现handlermapping adapter实现自己的mapping逻辑,自己的hanlder处理方式,springmvc的可扩展性确实很强,核心组件全部是接口,自己想怎么玩就可以怎么玩
网友评论