题记:本文对Spring MVC相关知识点做了归纳整理,针对其工作流程及主要组件做了简单说明,也介绍了基本使用及其常用技术;之后,从源码角度对 DispatcherServlet 类继承结构及其中主要方法做了说明,辅助理解 SpringMVC 接收请求后的行为, 最后,简单对 SSM 框架进行整合。
在 B/S 架构中,系统标准的三层架构包括:
持久层[dao层]
负责数据持久化,
包括数据库和数据访问层。
业务层[service层]
负责业务逻辑处理,
依赖持久层
表现层[web层]
负责接收客户端请求,向客户端响应结果,
包括展示层和控制层,
表现层依赖业务层;
表现层的设计一般使用MVC模型(MVC 是表现层的设计模型,和其他层没有关系)
MVC:Model View Controller,分层是为了解耦,方便维护和分工协作
Spring MVC 全名为 Spring Web MVC,是一种基于 Java 的实现 MVC 设计模式和请求驱动类型的轻量级 Web 框架,属于 SpringFrameWork 的后续产品。是 最优秀的 MVC 框架 。Spring MVC 本质可以认为是对 servlet 的封装,简化了我们 servlet 的开发。
HandlerMapping
作用:找到请求对应的处理器(Handler)和处理器拦截器(HandlerInterceptor)
HandlerAdapter
Spring MVC 中的 Handler 可以是任意形式的(比如标注了@RequestMapping 的每个方法都是一个Handler),但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是 doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理方法调用 Handler 来处理,便是 HandlerAdapter 的职责。
HandlerExceptionResolver
处理 Handler 产生的异常情况,会根据异常设置 ModelAndView,之后交给渲染方法渲染为页面
ViewResolver
将 String 类型的视图名和 Locale 解析为 View 类型的视图。Spring MVC 默认配置针对 JSP 类型视图的InternalResourceViewResolver
RequestToViewNameTranslator
从请求中获取 ViewName
LocaleResolver
ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。 LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。
ThemeResolver
ThemeResolver 组件是⽤来解析主题的
MultipartResolver
MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实 现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还 可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就 是封装普通的请求,使其拥有⽂件上传的功能。
FlashMapManager
FlashMap ⽤于重定向时的参数传递
方式一:带后缀
<!-- 举例 --> <url-pattern>*.do</url-pattern> 复制代码
方式二:/,/ 不会拦截 .jsp,但是会拦截静态资源(.html等),因为此时覆盖了tomcat 容器中父 web.xml 的 DefaultServlet 的默认规则。
<!-- 举例 --> <url-pattern>/</url-pattern> 复制代码
<mvc:dafault-servlet-handler> <mvc:resources>
方法:声明形参 Model, Map 或 ModelMap,然后调用 addAttribute() 或 put() 方法
原理:运行时的具体类型是 BindingAwareModelMap,BindingAwareModelMap 继承了 ExtendedModelMap,ExtendedModelMap 继承了 ModelMap,实现了 Model 接口。
默认支持 Servlet API 作为方法参数。
绑定简单数据类型
绑定 Pojo 类型参数
绑定Pojo包装对象参数
绑定日期类型参数
定义⼀个SpringMVC的类型转换器
implement Converter<String, Date>
扩展实现接口
重写 public Date convert(String source) 方法
注册你的实现
<!--注册⾃定义类型转换器--> <bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.test.converter.DateConverter"></bean> </set> </property> </bean> <!-- ⾃动注册最合适的处理器映射器,处理器适配器(调⽤handler⽅法)--> <mvc:annotation-driven conversionservice="conversionServiceBean"/> 复制代码
Restful 是⼀种 web 软件架构⻛格,它不是标准也不是协议,它倡导的是⼀个资源定位及资源操作的⻛格。
REST(Representational State Transfer)
资源在网络中以某种表现形式进行状态转移
资源(Resource):⽹络上的⼀个实体,或者说是⽹络上的⼀个具体信息。
表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层 (Representation),比如用JSON,XML,JPEG等;
状态转化(State Transfer):状态变化。通过HTTP动词实现。
每发出⼀个请求,就代表了客户端和服务器的⼀次交互过程。HTTP 协议,是⼀个⽆状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器, 必须通过某种⼿段,让服务器端发⽣“状态转化”(State Transfer)。⽽这种转化是建⽴在表现层 之上的,所以就是 “ 表现层状态转化” 。具体说, 就是 HTTP 协议⾥⾯,四个表示操作⽅式的动词: GET 、POST 、PUT 、DELETE 。它们分别对应四种基本操作:GET ⽤来获取资源,POST ⽤来新建资 源,PUT ⽤来更新资源,DELETE ⽤来删除资源。
@PathVariable 注解获取 RESTful ⻛格的请求 URL中的路径变量。
配置springmvc请求⽅式转换过滤器,会检查请求参数中是否有_method参数,如果有就按照指定的请求⽅式进⾏转换
<filter> <filter-name>hiddenHttpMethodFilter</filter-name> <filterclass>org.springframework.web.filter.HiddenHttpMethodFilter</filterclass> </filter> <filter-mapping> <filter-name>hiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 复制代码
javax.servlet.ServletContextListener
接⼝的服务器端组件,它随 Web应⽤的启动⽽启动,只初始化⼀次,然后会⼀直运⾏监视,随Web应⽤的停⽌⽽销毁
ContextLoaderListener
HttpSession
, ServletRequest
的创建和销毁;变量的创建、 销毁和修改等。可以在某些动作前后增加处理,实现监控,⽐如统计在线⼈数,利⽤ HttpSessionLisener
等。 /*
可以对所有的资源访问(servlet、js/css静态资源等)进⾏过滤处理 注:Interceptor1 配置在前
定义类实现 HandlerInterceptor 接口
重写相应方法
注册 Spring MVC 拦截器
<mvc:interceptors> <!--拦截所有handler--> <!--<bean class="com.lagou.edu.interceptor.MyIntercepter01"/>--> <mvc:interceptor> <!--配置当前拦截器的url拦截规则,**代表当前⽬录下及其⼦⽬录下的所有url--> <mvc:mapping path="/**"/> <!--exclude-mapping可以在mapping的基础上排除⼀些url拦截--> <!--<mvc:exclude-mapping path="/demo/**"/>--> <bean class="com.test.interceptor.MyIntercepter01"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="com.test.interceptor.MyIntercepter02"/> </mvc:interceptor> </mvc:interceptors> 复制代码
所需依赖
<!--⽂件上传所需jar坐标--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> 复制代码
配置文件上传解析器
<!--配置⽂件上传解析器,id是固定的multipartResolver--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--设置上传⼤⼩,单位字节--> <property name="maxUploadSize" value="1000000000"/> </bean> 复制代码
handle 声明形参 MultipartFile uploadFile
,形参名与上传文件名相同
捕捉当前 Controller 下异常
在当前 Controller 下创建方法,贴 @ExceptionHandler
注解,声明异常类型,进行处理
全局异常处理
创建类,贴 @ControllerAdvice
注解,其中可创建多个方法,针对不同异常进行捕捉处理。
解决重定向参数丢失问题:
手动拼接参数
缺点:属于 get 请求,参数长度和安全性均有限制。
声明参数 RedirectAttributes redirectAttributes
, 使用 redirectAttributes.addAttribute(key, value)
缺点:同上
声明参数 RedirectAttributes redirectAttributes
, 使用 redirectFlashAttributes.addAttribute(key, value), 该方法设置了一个 flash 类型属性, 该属性会被暂存到 session 中,在跳转到页面后该属性销毁。
doDispatch() 源码如下
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { // 1 检查是否是文件上传的请求 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // 2 取得处理当前请求的Controller,这里也称为Handler,即处理器 // 这里并不是直接返回 Controller,而是返回 HandlerExecutionChain 请求处理链对象,该对象封装了Handler和Inteceptor mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { // 如果 handler 为空,则返回404 noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. // 3 获取处理请求的处理器适配器 HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. // 处理 last-modified 请求头 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. // 4 实际处理器处理请求,返回结果视图对象 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } // 结果视图对象的处理 applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); }catch (Exception ex) { dispatchException = ex; }catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } // 5 跳转页面,渲染视图 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }catch (Exception ex) { //最终会调用HandlerInterceptor的afterCompletion 方法 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); }catch (Throwable err) { //最终会调用HandlerInterceptor的afterCompletion 方法 triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); }finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } 复制代码
试图从 BeanNameUrlHandlerMapping
和 RequestMappingHandlerMapping
中获取能够处理当前请求的执行链
遍历各个HandlerAdapter,看哪个Adapter⽀持处理当前Handler
ha.handle() 调用 handleInternal()
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. // 判断当前是否需要支持在同一个session中只能线性地处理请求 if (this.synchronizeOnSession) { // 获取当前请求的session对象 HttpSession session = request.getSession(false); if (session != null) { // 为当前session生成一个唯一的可以用于锁定的key Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { // 对HandlerMethod进行参数等的适配处理,并调用目标handler mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary // 如果当前不存在session,则直接对HandlerMethod进行适配 mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... // 如果当前不需要对session进行同步处理,则直接对HandlerMethod进行适配 mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; } 复制代码
invokeHandlerMethod()
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中配置的InitBinder,用于进行参数的绑定 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); // 获取容器中全局配置的ModelAttribute和当前当前HandlerMethod所对应的Controller中配置的ModelAttribute,这些配置的方法将会在目标方法调用之前进行调用 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 将handlerMethod封装为一个ServletInvocableHandlerMethod对象 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { // 设置当前容器中配置的所有ArgumentResolver invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { // 设置当前容器中配置的所有ReturnValueHandler invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } // 将前面创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中 invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); // 这里initModel()方法主要作用是调用前面获取到的@ModelAttribute标注的方法, // 从而达到@ModelAttribute标注的方法能够在目标Handler调用之前调用的目的 modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(logger, traceOn -> { String formatted = LogFormatUtils.formatValue(result, !traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } // 对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象 invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } // 对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果进行了重定向, // 还会判断是否需要将FlashAttributes封装到新的请求中 return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } } 复制代码
invokeAndHandle()
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 对目标handler的参数进行处理,并且调用目标handler Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 设置相关的返回状态 setResponseStatus(webRequest); if (returnValue == null) { if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } } 复制代码
invokeForRequest()
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 将request中的参数转换为当前handler的参数形式 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } // 这里doInvoke()方法主要是结合处理后的参数,使用反射对目标方法进行调用 return doInvoke(args); } 复制代码
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); View view; String viewName = mv.getViewName(); if (viewName != null) { // We need to resolve the view name. // 根据视图解析器解析出View视图对象 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } // Delegate to the View object for rendering. if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // 调用 view 对象的render方法 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } } 复制代码
resolveViewName() 方法
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) { for (ViewResolver viewResolver : this.viewResolvers) { // 视图解析器解析出View视图对象 View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } } return null; } 复制代码
resolveViewName() 调用 createView() 方法
createView() 方法 在解析出View视图对象的过程中会判断是否重定向、是否转发等,不同的情况封装的是不同的 View实现
调用super.createView()方法
createView() 调用 loadView()
loadView() 调用 buildView()
buildView() 中将逻辑视图名解析为物理视图名
public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { logger.debug("View " + formatViewName() + ", model " + (model != null ? model : Collections.emptyMap()) + (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes)); } Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); // 渲染数据 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); } 复制代码
renderMergedOutputModel()
protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Expose the model object as request attributes. // 把modelMap中的数据暴露到request域中 exposeModelAsRequestAttributes(model, request); // Expose helpers as request attributes, if any. exposeHelpers(request); // Determine the path for the request dispatcher. String dispatcherPath = prepareForRendering(request, response); // Obtain a RequestDispatcher for the target resource (typically a JSP). RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // If already included or response already committed, perform include, else forward. if (useInclude(request, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including [" + getUrl() + "]"); } rd.include(request, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to [" + getUrl() + "]"); } rd.forward(request, response); } } 复制代码
exposeModelAsRequestAttributes()
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception { model.forEach((name, value) -> { if (value != null) { // 将数据设置到请求域中 request.setAttribute(name, value); } else { request.removeAttribute(name); } }); } 复制代码
在DispatcherServlet中定义了九个属性,每⼀个属性都对应⼀种组件
/** MultipartResolver used by this servlet. */ // 多部件解析器 @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. */ // 处理器适配器组件 @Nullableprivate 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. */ // flash属性管理组件 @Nullable private FlashMapManager flashMapManager; /** List of ViewResolvers used by this servlet. */ // 视图解析器 @Nullable private List<ViewResolver> viewResolvers; 复制代码
九⼤组件的初始化时机
DispatcherServlet中的onRefresh(),该⽅法中初始化了九⼤组件
protected void onRefresh(ApplicationContext context) { // 初始化策略 initStrategies(context); } 复制代码
initStrategies()
protected void initStrategies(ApplicationContext context) { // 多文件上传的组件 initMultipartResolver(context); // 初始化本地语言环境 initLocaleResolver(context); // 初始化模板处理器 initThemeResolver(context); // 初始化HandlerMapping initHandlerMappings(context); // 初始化参数适配器 initHandlerAdapters(context); // 初始化异常拦截器 initHandlerExceptionResolvers(context); // 初始化视图预处理器 initRequestToViewNameTranslator(context); // 初始化视图转换器 initViewResolvers(context); // 初始化 FlashMap 管理器 initFlashMapManager(context); } 复制代码
initHandlerMappings(context)
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. // 在IoC 容器中按照 HandlerMapping.class 找到所有的HandlerMapping 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 { // 否则在ioc中按照固定名称id(handlerMapping)去找 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.isTraceEnabled()) { logger.trace("No HandlerMappings declared for servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties"); } } } 复制代码
getDefaultStrategies()
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { String key = strategyInterface.getName(); // DispatcherServlet.properties String value = defaultStrategies.getProperty(key); if (value != null) { String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> 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 DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", ex); } catch (LinkageError err) { throw new BeanInitializationException("Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", err); } } return strategies; } else { return new LinkedList<>(); } } 复制代码
DispatcherServlet.properties
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,/ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,/ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,/ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.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.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager 复制代码
initMultipartResolver()
private void initMultipartResolver(ApplicationContext context) { try { // MULTIPART_RESOLVER_BEAN_NAME = mulitipartResolver this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); if (logger.isTraceEnabled()) { logger.trace("Detected " + this.multipartResolver); } else if (logger.isDebugEnabled()) { logger.debug("Detected " + this.multipartResolver.getClass().getSimpleName()); } } catch (NoSuchBeanDefinitionException ex) { // Default is no multipart resolver. this.multipartResolver = null; if (logger.isTraceEnabled()) { logger.trace("No MultipartResolver '" + MULTIPART_RESOLVER_BEAN_NAME + "' declared"); } } } 复制代码
引入依赖
Spring 配置文件 applicationContext.xml 的配置
<context:component-scan base-package="com.test.xx"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--包扫描--> <context:component-scan base-package="com.test.xx"/> <!--数据库连接池以及事务管理都交给Spring容器来完成--> <!--引⼊外部资源⽂件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--第三⽅jar中的bean定义在xml中--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--SqlSessionFactory对象应该放到Spring容器中作为单例对象管理 原来mybaits中sqlSessionFactory的构建是需要素材的:SqlMapConfig.xml中的内容 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--别名映射扫描--> <property name="typeAliasesPackage" value="com.test.xx.pojo"/> <!--数据源dataSource--> <property name="dataSource" ref="dataSource"/> </bean> <!--Mapper动态代理对象交给Spring管理,我们从Spring容器中直接获得Mapper的代理对 象--> <!--扫描mapper接⼝,⽣成代理对象,⽣成的代理对象会存储在ioc容器中--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--mapper接⼝包路径配置--> <property name="basePackage" value="com.test.xx.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!--事务管理--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--事务管理注解驱动--> <tx:annotation-driven transaction-manager="transactionManager"/> </beans> 复制代码
springmvc.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/springcontext.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--扫描controller--> <context:component-scan base-package="com.lagou.edu.controller"/> <mvc:annotation-driven/> </beans> 复制代码
Spring容器和SpringMVC容器是有层次的(⽗⼦容器) Spring容器:service对象+dao对象 SpringMVC容器:controller对象,,,,可以引⽤到Spring容器中的对象
web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:applicationContext*.xml</param-value> </context-param> <!--spring框架启动--> <listener> <listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass> </listener> <!--springmvc启动--> <servlet> <servlet-name>springmvc</servlet-name><servletclass>org.springframework.web.servlet.DispatcherServlet</servletclass> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> 复制代码
Dao、Service、Controller各层代码完善