配置一个简单案例

第一步,在web.xml文件中配置DispatchServlet:主要是为了配置springmvc配置文件的位置和名称

  1. <servlet>
  2. <servlet-name>springDispatcherServlet</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <!-- 配置DispatcherServlet的初始化参数:SpringMvc配置文件的位置和名称 -->
  5. <init-param>
  6. <param-name>contextConfigLocation</param-name>
  7. <param-value>classpath:springMvc.xml</param-value>
  8. </init-param>
  9. <load-on-startup>1</load-on-startup>
  10. </servlet>
  11. <!-- Map all requests to the DispatcherServlet for handling -->
  12. <servlet-mapping>
  13. <servlet-name>springDispatcherServlet</servlet-name>
  14. <url-pattern>/</url-pattern>
  15. </servlet-mapping>

第二步,创建springmvc配置文件,根据web.xml中的配置

  1. <!-- 配置自动扫描的包 -->
  2. <context:component-scan base-package="springmvc.helloworld"></context:component-scan>
  3. <!-- 配置视图解析器 -->
  4. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  5. <property name="prefix" value="/WEB-INF/views/"></property>
  6. <property name="suffix" value=".jsp"></property>
  7. </bean>

第三步,使用@RequestMapping映射请求

  1. @Controller
  2. public class HelloWorld {
  3. @RequestMapping("/helloWorld")
  4. public String hello(){
  5. System.out.println("helloWorld");
  6. return "success";
  7. }
  8. }

处理模型数据

ModelAndView

控制器处理方法的返回值可以是ModelAndView,其包含模型和数据信息,SpringMvc会将model中的数据方法request域中

添加模型数据:

ModelAndView addObject(String attributeName,Object attributeValue)

ModelAndView addAllObject(Map modelMap)

设置视图:

void setView(View view)

void setViewName(String ViewName)

  1. @RequestMapping("/testModelAndView")
  2. public ModelAndView testModelAndView() {
  3. ModelAndView mv = new ModelAndView("success");
  4. mv.addObject("time", new Date());
  5. return mv;
  6. }

Map以及Model

可以传入Map和Model类型,目标方法可以添加Map类型(也可以是Model类型或是ModelMap类型参数)

  1. @RequestMapping("testMap")
  2. public String testMap(Map<String, Object> map) {
  3. map.put("names", Arrays.asList("Li","Wu","Zhang"));
  4. return "success";
  5. }

前端接收

  1. names:<%=request.getAttribute("names") %>

@SessionAttributes

此注解只能放在类上,即多个请求之间共用某个模型参数SpringMVC将在模型中对应的属性暂时存到HttpSession中。

value属性:通过属性名指定需要放到会话中的属性

types属性:通过模型属性的对象类型指定哪些模型属性需要放到会话中

  1. @SessionAttributes(value= {"user"},types= {String.class})
  2. @Controller
  3. public class HelloWorld {
  4. @RequestMapping("/testSessionAttributes")
  5. public String testSessionAttributes(Map<String, Object> map) {
  6. User user = new User("Li", "123456", 24);
  7. map.put("user", user);
  8. return "success";
  9. }
  10. }

前端接收

  1. user:<%=session.getAttribute("user") %>

@ModelAttribute

@ModelAttribute标记的方法,会在每个目标方法执行之前被SpringMva调用

  1. @ModelAttribute
  2. public void getUser(@RequestParam(value="id") Integer id,Map<String,Object> map) {
  3. if(id!=null) {
  4. User user = new User(1, "Tom", "123456", 24);
  5. System.out.println("从数据库中取出:"+user);
  6. map.put("user", user);
  7. }
  8. }
  9. @RequestMapping("/testModelAttribute")
  10. public String testModelAttribute(User user) {
  11. System.out.println("修改:" + user);
  12. return "success";
  13. }

视图以及视图处理器

对于那些返回String、view或者ModelMap等类型的处理方法,SpringMvc都会将其装配成一个ModelAndView对象。view接口是无状态的,所以不会有线程安全问题

重定向

将字符串url重向到指定页面

数据的转换、格式化、校验

数据转换

SpringMvc定义了3中类型的转换器接口:

  • Converter:将S类型转换为T类型对象
  • ConverterFactory:将相同系列多个“同质”Conterver封装在一起
  • GenericConverter: 会根据源类对象及目标所在的宿主主类中的上下文信息进行类型转换

实例如下:

表单

  1. <form action="testConverter" method="POST">
  2. <!-- 格式例如:Harry-123456-24 -->
  3. Employee:<input type="text" name="user">
  4. <input type="submit" name="Submit">
  5. </form>

转换器

  1. @Component
  2. public class UserConverter implements Converter<String, User> {
  3. @Override
  4. public User convert(String source) {
  5. if(source != null) {
  6. System.out.println("aa");
  7. String[] vals = source.split("-");
  8. if(vals != null) {
  9. String username = vals[0];
  10. String password = vals[1];
  11. String age = vals[2];
  12. User user = new User(username, password, Integer.parseInt(age));
  13. System.out.println("converter:"+user);
  14. return user;
  15. }
  16. }
  17. return null;
  18. }
  19. }
  1. 3).配置文件
  1. <mvc:annotation-driven conversion-service="converterService"></mvc:annotation-driven>
  2. <!-- 配置converterService -->
  3. <bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
  4. <property name="converters">
  5. <set>
  6. <ref bean="userConverter"/>
  7. </set>
  8. </property>
  9. </bean>
  1. 4).目标方法
  1. @RequestMapping("/testConverter")
  2. public String testConverter(@RequestParam("user") User user) {
  3. System.out.println(user);
  4. return "success";
  5. }

数据格式化

1).在配置文件中添加上

2).在目标类型的属性上配置

  1. @NumberFormat(pattern="#.###")
  2. private double price;
  3. @DateTimeFormat(pattern="yyyy-mm-dd")
  4. private Date birth;

数据校验

使用JSR 303验证标准,在SpringMvc配置文件中添加

  1. <mvc:annotation-driven />

会默认装配好一个LocalValidatorFactoryBean,然后在bean的属性上添加对应注解,例如@NotNull,表示被注释的元素不能为空;@Email,表示被注释的元素必须是一个email,最后再添加 @Valid注解

文件上传

SpringMvc通过MutipartResolver实现文件上传

配置MutipartResolver

  1. <!-- 配置 MultipartResolver -->
  2. <bean id="multipartResolver"
  3. class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  4. <property name="defaultEncoding" value="UTF-8"></property>
  5. <property name="maxUploadSize" value="1024000"></property>
  6. </bean>

表单

  1. <form action="testFileUpload" method="POST" enctype="multipart/form-data">
  2. File: <input type="file" name="file"/>
  3. Desc: <input type="text" name="desc"/>
  4. <input type="submit" value="Submit"/>
  5. </form>

目标方法

  1. @RequestMapping("/testFileUpload")
  2. public String testFileUpload(@RequestParam("desc") String desc,
  3. @RequestParam("file") MultipartFile file) throws Exception {
  4. System.out.println("InputStream:" + file.getInputStream());
  5. return "success";
  6. }

拦截器

可以使用拦截器对请求进行拦截,可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口

  1. public class FirstInterceptor implements HandlerInterceptor {
  2. /**
  3. * 渲染视图之后被调用. 释放资源
  4. */
  5. @Override
  6. public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
  7. throws Exception {
  8. System.out.println("afterCompletion");
  9. }
  10. /**
  11. * 调用目标方法之后, 但渲染视图之前.
  12. * 可以对请求域中的属性或视图做出修改.
  13. */
  14. @Override
  15. public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
  16. throws Exception {
  17. System.out.println("postHandle");
  18. }
  19. /**
  20. * 该方法在目标方法之前被调用.
  21. * 若返回值为 true, 则继续调用后续的拦截器和目标方法.
  22. * 若返回值为 false, 则不会再调用后续的拦截器和目标方法.
  23. *
  24. * 可以考虑做权限. 日志, 事务等.
  25. */
  26. @Override
  27. public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
  28. System.out.println("preHandle");
  29. return true;
  30. }
  31. }

配置拦截器

  1. <!-- 配置拦截器 -->
  2. <mvc:interceptors>
  3. <bean class="springmvc.helloworld.FirstInterceptor"></bean>
  4. </mvc:interceptors>

SpringMvc的执行流程

image.png

DispatcherServlet(中央控制器):作为springmvc最重要的一部分,它本身也是一个servlet。它负责调用handlerMapping,即处理器映射器来处理我们编写的handler,处理器映射器处理完handler后向中央控制器返回handlerExecutionChain对象,该对象包含处理器拦截器和处理器对象等信息,中央控制器根据这些信息去匹配带合适的HandlerAdapter,即处理器适配器,去处理handler,最终向中央控制器返回一个ModelAndView对象,中央控制器接收到这个对象后会向ViewResolver,即视图解析器发送请求,让视图解析器去解析、渲染这个ModelAndView对象,然后视图解析器想中央控制器返回view,中央控制器渲染view后响应用户。

Handler:用Object接受的,也就是我们自己写的Controller。我们要使用spring、mvc来处理一般有三种方式:第一种是实现Controller接口,第二种可以实现HttpRequestHandler接口,第三种就是走@RequestMapping注解方式。这三种方式会分别使用不同的HandlerMapping来处理,返回的handler类型也会不一样

HandlerExecutionChain:里面封装了handler和HandlerInterceptor的List集合

源码分析

DispatcherServlet继承里FrameworkServlet,FrameworkServlet又继承了HttpServlet,并覆写了HttpServlet的service(HttpServletRequest request,HttpServletResponse reponse)方法,FrameworkServlet的service方法也设计到调用,调用后还做了很多初始化的工作,但这些不重要,重点是它会调用doService方法,FrameworkServlet的doService方法是个抽象方法,所以直接转到它的实现类DIspatcherServlet中区,DispatcherServlet的doService方法有回做很多初始化的工作,向Request域保存很多信息,重点是它会调用本类的doDispatch(request,response)方法

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HttpServletRequest processedRequest = request;
  3. HandlerExecutionChain mappedHandler = null;
  4. boolean multipartRequestParsed = false;
  5. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  6. try {
  7. ModelAndView mv = null;
  8. Exception dispatchException = null;
  9. try {
  10. processedRequest = checkMultipart(request);
  11. multipartRequestParsed = (processedRequest != request);
  12. // 1、调用处理器映射器(HandlerMapping),得到HandlerExecutionChain对象
  13. mappedHandler = getHandler(processedRequest);
  14. if (mappedHandler == null || mappedHandler.getHandler() == null) {
  15. noHandlerFound(processedRequest, response);
  16. return;
  17. }
  18. // 2、根据我们编写的Contorller得到匹配到适合的处理器适配器
  19. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  20. // Process last-modified header, if supported by the handler.
  21. String method = request.getMethod();
  22. boolean isGet = "GET".equals(method);
  23. if (isGet || "HEAD".equals(method)) {
  24. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  25. if (logger.isDebugEnabled()) {
  26. logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
  27. }
  28. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  29. return;
  30. }
  31. }
  32. // 执行每个拦截器的preHandle方法
  33. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  34. return;
  35. }
  36. /*
  37. 3、利用处理器适配器,执行我们编写的Contorller方法,
  38. 并进一步处理,返会ModelAndView对象 */
  39. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  40. if (asyncManager.isConcurrentHandlingStarted()) {
  41. return;
  42. }
  43. applyDefaultViewName(request, mv);
  44. // 执行每个拦截器的postHandle方法
  45. mappedHandler.applyPostHandle(processedRequest, response, mv);
  46. }
  47. // 对自定义的异常处理器进行捕获
  48. catch (Exception ex) {
  49. dispatchException = ex;
  50. }
  51. // 3、处理结果(页面渲染),这里过了,基本主要流程就走完了
  52. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  53. }
  54. catch (Exception ex) {
  55. // 异常时执行拦截器的afterCompletion方法
  56. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  57. }
  58. catch (Error err) {
  59. // 错误时也会执行拦截器的afterCompletion方法
  60. triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
  61. }
  62. //==========================================================================================
  63. // 下面的不是重点了
  64. finally {
  65. if (asyncManager.isConcurrentHandlingStarted()) {
  66. // Instead of postHandle and afterCompletion
  67. if (mappedHandler != null) {
  68. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  69. }
  70. }
  71. else {
  72. // Clean up any resources used by a multipart request.
  73. if (multipartRequestParsed) {
  74. cleanupMultipart(processedRequest);
  75. }
  76. }
  77. }
  78. }

getHandler(processedRequest),请求HandlerMapping获取HandlerExecutionChain对象:

  1. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  2. for (HandlerMapping hm : this.handlerMappings) {
  3. if (logger.isTraceEnabled()) {
  4. logger.trace(
  5. "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
  6. }
  7. HandlerExecutionChain handler = hm.getHandler(request);
  8. if (handler != null) {
  9. return handler;
  10. }
  11. }
  12. return null;
  13. }

getHandler()方法会先遍历handlerMapping这个list集合,这个集合封装了所有的HandlerMapping对象,其信息包括BeanNameUrlHandlerMapping(处理xml配置的Controller)和RequestMappingHandlerMapping(处理注解配置的Controller),遍历获取到HandlerMapping后一次执行getHandler(request)

HandlerAdapter ha =getHandlerAdapter(mappedHandler.getHandler());

  1. protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  2. for (HandlerAdapter ha : this.handlerAdapters) {
  3. if (logger.isTraceEnabled()) {
  4. logger.trace("Testing handler adapter [" + ha + "]");
  5. }
  6. if (ha.supports(handler)) {
  7. return ha;
  8. }
  9. }
  10. throw new ServletException("No adapter for handler [" + handler +
  11. "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  12. }

getHandlerAdapter方法接收的参数就是封装在HandlerExecutionChain里面的handler独享。首先,它会先遍历HandlerAdapter这个List集合,这个集合封装了所有的HandlerAdapter对象,其信息包括HttpRequestHandlerAdapter(处理实现了HttpRequestHandler接口中自己写的控制器)和SimpleControllerHandlerAdapter(实现了Controller接口中自己写的控制器),还有RequestMappingHandlerAdapter(处理使用了注解@RequestMapping的控制器),然后依次执行HandlerAdapter的supports(handler)方法,来判断传入的handler会适配哪一个HandlerAdapter,最终返回这个匹配的HandlerAdapter

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

HandlerAdapter处理handler,返回ModelAndView对象,即调用在Controller中声明的方法,然后返回。

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

请求ViewResolver对ModelAndView解析,然后页面渲染

  1. protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
  2. HttpServletRequest request) throws Exception {
  3. for (ViewResolver viewResolver : this.viewResolvers) {
  4. View view = viewResolver.resolveViewName(viewName, locale);
  5. if (view != null) {
  6. return view;
  7. }
  8. }
  9. return null;
  10. }

也是一个for循环,找到合适的视图解析器,解析出view对象,然后根据这个view对象中的render方法来进行渲染,渲染做的就是取出modelmap里面的数据,将其放进request域中,并给视图加上前后缀,利用request获取RequestDispatcher对象,然后forwar转发到对应的视图中

  1. @Override
  2. protected void renderMergedOutputModel(
  3. Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  4. // 取出model里的数据保存到request域中
  5. exposeModelAsRequestAttributes(model, request);
  6. // Expose helpers as request attributes, if any.
  7. exposeHelpers(request);
  8. // 获取物理视图地址(就是转发路径)
  9. String dispatcherPath = prepareForRendering(request, response);
  10. // (获取RequestDispatcher以方便后面的转发)
  11. RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
  12. if (rd == null) {
  13. throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
  14. "]: Check that the corresponding file exists within your web application archive!");
  15. }
  16. // If already included or response already committed, perform include, else forward.
  17. if (useInclude(request, response)) {
  18. response.setContentType(getContentType());
  19. if (logger.isDebugEnabled()) {
  20. logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
  21. }
  22. // 请求转发
  23. rd.include(request, response);
  24. }

渲染结束后,再调用拦截器的afterCompletion方法