概念

  • spring实现web模块,简化web开发
  • SpringMVC == spring模块划分中的Web模块
  • SpringMVC思想是有一个前端控制器能拦截所有请求,并智能派发;
    • 这个前端控制器是一个servlet,应该在web.xml中配置;

运行流程

  1. 客户端点击链接会发送 http://localhost:8080/index.jsp
  2. 来到tomcat服务器
  3. SpringMVC的前端控制器收到请求
  4. 看请求地址和哪个@RequestMapping注解匹配,来找到哪个Controller的哪个方法
  5. 前端控制器找到目标Controller和目标方法,直接利用反射执行目标方法
  6. 返回值就是要去的页面
  7. 拿到返回值后,使用视图解析器拼串得到完整的页面地址
  8. 前端控制器转发到页面

web.xml

DispatcherServlet

  1. <servlet>
  2. <servlet-name>springDispatcherServlet</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <init-param>
  5. <!--contextConfigLocation:指定SpringMVC配置文件位置-->
  6. <param-name>contextConfigLocation</param-name>
  7. <param-value>classpath:springmvc.xml</param-value>
  8. </init-param>
  9. <!--
  10. servlet启动加载,servlet原本是第一次访问创建对象;
  11. load-on-startup:服务器启动的时候创建对象,值越小以优先级越高,越先创建对象;
  12. -->
  13. <load-on-startup>1</load-on-startup>
  14. </servlet>
  15. <servlet-mapping>
  16. <servlet-name>springDispatcherServlet</servlet-name>
  17. <!--
  18. /* 和 / 都是拦截所有请求
  19. /* 的范围更大,会拦截到jsp页面:*.jsp;而 / 不会
  20. -->
  21. <url-pattern>/</url-pattern>
  22. </servlet-mapping>

若在web.xml中不指定spring的配置文件的位置

  1. <init-param>
  2. <!--contextConfigLocation:指定SpringMVC配置文件位置-->
  3. <param-name>contextConfigLocation</param-name>
  4. <param-value>classpath:springmvc.xml</param-value>
  5. </init-param>

默认去找

  1. /WEB-INF/springDispatcherServlet-servlet.xml

  1. <servlet-name>springDispatcherServlet</servlet-name>
  1. /WEB-INF/<servlet-name>-servlet.xml

服务器的大web.xml中有一个DefaultServlet是url-pattern = /

我们的配置中前端控制器url-pattern = /

  1. <servlet-mapping>
  2. <servlet-name>springDispatcherServlet</servlet-name>
  3. <!--
  4. /* 和 / 都是拦截所有请求
  5. /* 的范围更大,会拦截到jsp页面:*.jsp;而 / 不会
  6. -->
  7. <url-pattern>/</url-pattern>
  8. </servlet-mapping>

处理 *.jsp 是tomcat做的事;所有项目的小web.xml都是继承于大web.xml

Defaultservlet是Tomcat中处理静态资源的

  • 除过jsp,和servlet外剩下的都是静态资源
  • index.html:静态资源,tomcat就会在服务器下找到这个资源并返回;·
  • 我们前端控制器的/禁用了tomcat服务器中的Defaultservlet
    • 所以静态资源会来到DispathcerServlet,找哪个方法的@ReuqestMapping是index.html
    • 必然没有,所以无法访问
  • jsp能访问的原因是,tomcat中的JspServlet的url-pattern是 .jsp .jspx,而DispatcherServlet没有覆盖这个配置

若DispatcherServlet配置 /*,则拦截所有请求

所以应当写 / ,也可以迎合REST风格


@RequestMapping

基本属性

method:限定请求方式

  • method= RequestMethod.POST:只接受这种类型的请求;默认是什么都可以
  • 不是规定的方式报错:4xx,客户端错误

params:支持简单的表达式

  • param1:表示请求必须包含名为param1的请求参数
    • params={“username”}
    • 发送请求必须包含名为username的请求参数
    • 没带404
  • !param1:表示请求必须不包含名为param1的请求参数
  • param1 = xxx:表示请求必须包含名为param1的请求参数且值为xxx
  • param1 != xxx:表示请求必须包含名为param1的请求参数且值不为xxx
  • 复杂表达式:params={“username=123”, “pwd”, “!age”}
    • 请求的参数必须包含username且值为123,必须包含pwd,不许不包含age

headers:支持简单的表达式;规定请求头

User-Agent:浏览器信息

谷歌User-Agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36

火狐User-Agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0

只允许火狐访问的配置

  1. headers = {"User-Agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0"}

consumes:

只接收内容类型为xxx的请求,即规定请求头中的Content-Type

produces:

告诉浏览器返回的内容类型是xxx,即给响应头中加入Content-Type:text/html;charset=utf-8

Ant风格的URL

  1. RequestMapping的模糊匹配功能
  2. URL地址可以写模糊的通配符:
  3. ? : 能替代任意一个字符
  4. * : 能替代任意多个字符,和一层路径
  5. ** : 能替代多层路径

@PathVariable

路径上的占位符

语法:{变量名}

只能占一层路径

  1. /**
  2. * /user/{dynamicParam}
  3. */
  4. @RequestMapping("/user/{name}")
  5. public String pathVariableTest(@PathVariable("name") String name){
  6. System.out.println("路径上的占位符为:" + name);
  7. System.out.println("pathVariableTest...");
  8. return "success";
  9. }

REST

REST:即Representational State Transfer(资源/表现层状态转化)

  • 是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便所以正得到越来越多网站的采用。
  • 资源(Resources):网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI即为每一个资源的独一无二的识别符。
  • 表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层
    • 比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式。
  • 状态转化(State Transfer):每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以就是“表现层状态转化”。
    • 具体说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、 DELETE
    • 它们分别对应四种基本操作:
      GET用来获取资源,POST用来新建资源,PUT用来更新资源, DELETE用来删除资源。

REST推荐url写法:/资源名/资源标识符

例:user/1

然后根据HTTP协议中的操作区分请求的CRUD


SpringMVC中的Filter,可以把普通的请求转化为规定形式的请求

  1. <filter>
  2. <filter-name>HiddenHttpMethodFilter</filter-name>
  3. <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>HiddenHttpMethodFilter</filter-name>
  7. <url-pattern>/*</url-pattern>
  8. </filter-mapping>
  1. 创建一个post类型的表单
  2. 表单项中携带一个_method的参数,此值即为DELETE、PUT

HiddenHttpMethodFilter

  1. public static final String DEFAULT_METHOD_PARAM = "_method";
  2. private String methodParam = DEFAULT_METHOD_PARAM;
  3. @Override
  4. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
  5. throws ServletException, IOException {
  6. HttpServletRequest requestToUse = request;
  7. //若表单是POST
  8. if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
  9. //获取表单上_method带来的值
  10. String paramValue = request.getParameter(this.methodParam);
  11. //_method有值
  12. if (StringUtils.hasLength(paramValue)) {
  13. //将_method转换为大写
  14. String method = paramValue.toUpperCase(Locale.ENGLISH);
  15. if (ALLOWED_METHODS.contains(method)) {
  16. //重写request.getMethod();
  17. requestToUse = new HttpMethodRequestWrapper(request, method);
  18. }
  19. }
  20. }
  21. filterChain.doFilter(requestToUse, response);
  22. }

SpringMVC获取请求带来的信息

原生携带请求参数:<a href=”handler01?name=jjj>handler01
给方法的形参列表中写上和请求参数名相同的变量,此变量即可接收请求参数

  • @RequestParam:获取请求参数;参数默认必须携带
    • 例: ```java @RequestParam(“username”) String name //即: name = request.getParameter(“username);

value():指定要获取的参数的key required():参数是否必须携带 defaultValue():若没携带的默认值

  1. - @RequestHeader:获取请求头中的某个key
  2. - 原生:
  3. ```java
  4. request.getHeader("User-Agent")
  • 例:
    1. @RequestHeader("User-Agent") String userAgent
    2. //即:
    3. userAgent = request.getHeader("User-Agent")
  • @CookieValue:获取某个cookie的值

    • 原生:

      1. Cookie[] cookies = request.getCookies();
      2. for(Cookie c : cookies){
      3. if("JSESSIONID".equals(c.getName())){
      4. String cv = c.getValue();
      5. }
      6. }
    • 例:

      1. @CookieValue("JSESSIONID") String jsessionid

SpringMVC可以直接在参数上写原生API

  1. HttpServletRequest
  2. HttpServletResponse
  3. HttpSession
  4. java.security.Principal
  5. Locale:国际化有关的区域信息对象
  6. InputStream
  7. ServletInputStream inputStream = request.getInputStream();
  8. OutputStream
  9. ServletOutputStream outputStream = response.getOutputStream();
  10. Reader
  11. BufferedReader reader = request.getReader();
  12. Writer
  13. PrintWriter writer = response.getWriter();
  1. @RequestMapping(value = "/nativeAPI")
  2. public String nativeAPI(HttpSession httpSession,
  3. HttpServletRequest request,
  4. HttpServletResponse response){
  5. httpSession.setAttribute("session", "session域");
  6. request.setAttribute("request", "请求域");
  7. return "success";
  8. }

提交的数据可能有乱码

  • 请求乱码:

    • GET:修改server.xml,添加URIEncoding=”UTF-8”

      1. <Connector port="8080" protocol="HTTP/1.1"
      2. URIEncoding="UTF-8"
      3. connectionTimeout="20000"
      4. redirectPort="8443" useBodyEncodingForURI="true"/>
    • POST:在第一次获取请求之前设置

      1. request.setCharacterEncoding("UTF-8");
  • 响应乱码:

    1. response.setContentType("text/html;charset=utf-8);
    • 写一个filter;SpringMVC中有这个filter
      • CharacterEncodingFilter
        1. <filter>
        2. <filter-name>CharacterEncodingFilter</filter-name>
        3. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        4. <init-param>
        5. <param-name>encoding</param-name>
        6. <param-value>UTF-8</param-value>
        7. </init-param>
        8. <init-param>
        9. <param-name>forceEncoding</param-name>
        10. <param-value>true</param-value>
        11. </init-param>
        12. </filter>
        13. <filter-mapping>
        14. <filter-name>CharacterEncodingFilter</filter-name>
        15. <url-pattern>/*</url-pattern>
        16. </filter-mapping>

数据输出

BindingAwareModelMap(隐含模型)

SpringMVC除过使用原生的request和session,还能将数据带给页面的办法

  1. 在方法形参表中传入Map、Model或ModelMap:
    • 这些参数里保存的数据都会放在请求域中,可以再页面获取
    • org.springframework.validation.support.BindingAwareModelMap
    • BindingAwareModelMap中保存的数据都会放在请求域中
      1. Map(interface(jdk)) Model(interface(spring))
      2. |
      3. ModelMap(class) |
      4. ExtendedModelMap
      5. BindingAwareModelMap
      ```java @RequestMapping(“/handle01”) public String handle01(Map map){ map.put(“key”, “value1”); System.out.println(map.getClass()); System.out.println(“handle01…”); return “output”; }

@RequestMapping(“/handle02”) public String handle02(Model model){ model.addAttribute(“key”, “value2”); System.out.println(model.getClass()); System.out.println(“handle02…”); return “output”; }

@RequestMapping(“/handle03”) public String handle03(ModelMap modelMap){ modelMap.addAttribute(“key”, “value3”); System.out.println(modelMap.getClass()); System.out.println(“handle03…”); return “output”; }

  1. 2. 方法的返回值可以变为ModelAndView
  2. - 既包含视图信息(页面地址),也包含模型数据(给页面带的数据),数据放在请求域中
  3. ```java
  4. @RequestMapping("/handle04")
  5. public ModelAndView handle04(){
  6. ModelAndView mv = new ModelAndView("output");
  7. mv.addObject("key", "value4");
  8. System.out.println("handle04...");
  9. return mv;
  10. }

ModelAttribute

执行全更新的sql时会有风险:

  • 例:修改user
  • 问题描述:若表单中未填入money,则SpringMVC自动创建的User对象的money将为null,此时会覆盖掉数据库中原先的money字段,本意是不修改money,但执行全更新sql后会覆盖掉原字段
  • 解决方法:执行更新sql时,SpringMVC不创建User对象,而是从数据库中取出相应id的一个User对象,且携带数据库中的值;然后根据表单中携带的数据决定是否进行数据更新:携带了就覆盖,没携带就保持原值

源码

SpringMVC - 图1

doDispatch()细节

  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. //1.检查是否是文件上传请求
  11. processedRequest = checkMultipart(request);
  12. multipartRequestParsed = (processedRequest != request);
  13. // Determine handler for the current request.
  14. //2.根据当前其请求找到能处理的Controller类;拿到执行链
  15. mappedHandler = getHandler(processedRequest);
  16. //3.若没有找到可以处理的Controller类,则抛异常/404
  17. if (mappedHandler == null) {
  18. noHandlerFound(processedRequest, response);
  19. return;
  20. }
  21. // Determine handler adapter for the current request.
  22. //4.拿到能执行这个Controller的所有方法的适配器
  23. //(反射工具RequestMappingHandlerAdapter)
  24. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  25. // Process last-modified header, if supported by the handler.
  26. //5.获取请求方式
  27. String method = request.getMethod();
  28. boolean isGet = "GET".equals(method);
  29. if (isGet || "HEAD".equals(method)) {
  30. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  31. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  32. return;
  33. }
  34. }
  35. //执行所有拦截器的preHandle
  36. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  37. return;
  38. }
  39. // Actually invoke the handler.
  40. //*6.使用适配器执行处理(handler)/控制(controller)器中的方法,方法返回值作为视图名,放入一个ModelAndView中
  41. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  42. if (asyncManager.isConcurrentHandlingStarted()) {
  43. return;
  44. }
  45. //7.若返回的ModelAndView没有视图名,则使用默认视图名
  46. applyDefaultViewName(processedRequest, mv);
  47. mappedHandler.applyPostHandle(processedRequest, response, mv);
  48. }
  49. catch (Exception ex) {
  50. dispatchException = ex;
  51. }
  52. catch (Throwable err) {
  53. // As of 4.3, we're processing Errors thrown from handler methods as well,
  54. // making them available for @ExceptionHandler methods and other scenarios.
  55. dispatchException = new NestedServletException("Handler dispatch failed", err);
  56. }
  57. //*8.将mv转发到目标页面
  58. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  59. }
  60. catch (Exception ex) {
  61. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  62. }
  63. catch (Throwable err) {
  64. triggerAfterCompletion(processedRequest, response, mappedHandler,
  65. new NestedServletException("Handler processing failed", err));
  66. }
  67. finally {
  68. if (asyncManager.isConcurrentHandlingStarted()) {
  69. // Instead of postHandle and afterCompletion
  70. if (mappedHandler != null) {
  71. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  72. }
  73. }
  74. else {
  75. // Clean up any resources used by a multipart request.
  76. if (multipartRequestParsed) {
  77. cleanupMultipart(processedRequest);
  78. }
  79. }
  80. }
  81. }

步骤:

  1. DispatcherServlet收到请求
  2. 调用doDispatch()方法
    1. getHandler():根据当前请求地址找到能处理的Controller
    2. getHandlerAdapter():根据当前Controller类获取能执行Controller类中方法的适配器
    3. 使用适配器(RequestMappingHandlerAdapter)执行目标方法
    4. 目标方法执行后返回一个ModelAndView对象mv
    5. 根据mv信息转发到具体的页面,且可以再请求域中取出mv中的数据

getHandler()细节:

getHandler()方法会返回目标Controller类的执行器链(HandlerExecutionChain)

  1. mappedHandler = getHandler(processedRequest);

SpringMVC - 图2

getHandler()内部:

  1. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  2. if (this.handlerMappings != null) {
  3. for (HandlerMapping mapping : this.handlerMappings) {
  4. HandlerExecutionChain handler = mapping.getHandler(request);
  5. if (handler != null) {
  6. return handler;
  7. }
  8. }
  9. }
  10. return null;
  11. }

handlerMappings(处理器映射):保存了每一个Controller能处理哪些请求的方法的映射信息

SpringMVC - 图3

getHandlerAdapter()细节:

通过目标Controller类的执行器链(HandlerExecutionChain)mappedHandler获得适配器ha

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

DispatcherServlet有几个引用类型的属性:SpringMVC九大组件

SpringMVC在工作的时候,关键位置都是由这些组建完成

共同点:全部都是接口;接口就是规范;提供了强大的扩展性

  1. /** MultipartResolver used by this servlet. */
  2. //文件上传解析器
  3. @Nullable
  4. private MultipartResolver multipartResolver;
  5. /** LocaleResolver used by this servlet. */
  6. //区域信息解析器,和国际化有关
  7. @Nullable
  8. private LocaleResolver localeResolver;
  9. /** ThemeResolver used by this servlet. */
  10. //主题解析器,强大的主体效果更换(一般不用)
  11. @Nullable
  12. private ThemeResolver themeResolver;
  13. /** List of HandlerMappings used by this servlet. */
  14. //handler映射信息
  15. @Nullable
  16. private List<HandlerMapping> handlerMappings;
  17. /** List of HandlerAdapters used by this servlet. */
  18. //handler适配器
  19. @Nullable
  20. private List<HandlerAdapter> handlerAdapters;
  21. /** List of HandlerExceptionResolvers used by this servlet. */
  22. //handler异常解析器
  23. @Nullable
  24. private List<HandlerExceptionResolver> handlerExceptionResolvers;
  25. /** RequestToViewNameTranslator used by this servlet. */
  26. //请求-视图名转换器
  27. @Nullable
  28. private RequestToViewNameTranslator viewNameTranslator;
  29. /** FlashMapManager used by this servlet. */
  30. //SprngMVC中允许重定向携带数据的功能
  31. @Nullable
  32. private FlashMapManager flashMapManager;
  33. /** List of ViewResolvers used by this servlet. */
  34. //视图解析器
  35. @Nullable
  36. private List<ViewResolver> viewResolvers;

九大组件的初始化

  1. /**
  2. * This implementation calls {@link #initStrategies}.
  3. */
  4. @Override
  5. protected void onRefresh(ApplicationContext context) {
  6. initStrategies(context);
  7. }
  8. /**
  9. * Initialize the strategy objects that this servlet uses.
  10. * <p>May be overridden in subclasses in order to initialize further strategy objects.
  11. */
  12. protected void initStrategies(ApplicationContext context) {
  13. initMultipartResolver(context);
  14. initLocaleResolver(context);
  15. initThemeResolver(context);
  16. initHandlerMappings(context);
  17. initHandlerAdapters(context);
  18. initHandlerExceptionResolvers(context);
  19. initRequestToViewNameTranslator(context);
  20. initViewResolvers(context);
  21. initFlashMapManager(context);
  22. }

可以在web.xml中修改DispatcherServlet中的某些属性

  1. <init-param>
  2. <param-name>detectAllHandlerMappings</param-name>
  3. <param-value>false</param-value>
  4. </init-param>

初始化HandlerMapping

  1. /**
  2. * Initialize the HandlerMappings used by this class.
  3. * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
  4. * we default to BeanNameUrlHandlerMapping.
  5. */
  6. private void initHandlerMappings(ApplicationContext context) {
  7. this.handlerMappings = null;
  8. if (this.detectAllHandlerMappings) {
  9. // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
  10. Map<String, HandlerMapping> matchingBeans =
  11. BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
  12. if (!matchingBeans.isEmpty()) {
  13. this.handlerMappings = new ArrayList<>(matchingBeans.values());
  14. // We keep HandlerMappings in sorted order.
  15. AnnotationAwareOrderComparator.sort(this.handlerMappings);
  16. }
  17. }
  18. else {
  19. try {
  20. HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
  21. this.handlerMappings = Collections.singletonList(hm);
  22. }
  23. catch (NoSuchBeanDefinitionException ex) {
  24. // Ignore, we'll add a default HandlerMapping later.
  25. }
  26. }
  27. // Ensure we have at least one HandlerMapping, by registering
  28. // a default HandlerMapping if no other mappings are found.
  29. if (this.handlerMappings == null) {
  30. this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
  31. if (logger.isTraceEnabled()) {
  32. logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
  33. "': using default strategies from DispatcherServlet.properties");
  34. }
  35. }
  36. }

组件的初始化:

  • 有些组件在容器中使用类型找,有些使用id找
  • 在容器中找这个组件,若没有找到就使用默认配置

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

mav = invokeHandlerMethod(request, response, handlerMethod);


转发forward

  1. @RequestMapping("/hello1")
  2. public String hello1() {
  3. System.out.println("hello1");
  4. return "../../hello";
  5. }
  6. /**
  7. * forward:转发到一个页面
  8. * forward:/hello.jsp:转发到当前项目下的hello.jsp
  9. * 不会使用视图解析器进行拼串
  10. */
  11. @RequestMapping("/hello2")
  12. public String hello2() {
  13. System.out.println("hello2");
  14. return "forward:/hello.jsp";
  15. }
  16. /**
  17. * forward:多次转发
  18. */
  19. @RequestMapping("/hello3")
  20. public String hello3() {
  21. System.out.println("hello3");
  22. return "forward:hello2";
  23. }

重定向redirect

  1. /**
  2. * redirect:重定向
  3. * 原生的Servlet重定向/路径需要加上项目名
  4. * response.sendRedirect("/hello.jsp")
  5. * SpringMVC会自动为路径拼接项目名
  6. */
  7. @RequestMapping("/hello4")
  8. public String hello4() {
  9. System.out.println("hello4");
  10. return "redirect:/hello.jsp";
  11. }
  12. /**
  13. * redirect:多次重定向
  14. */
  15. @RequestMapping("/hello5")
  16. public String hello5() {
  17. System.out.println("hello5");
  18. return "redirect:hello4";
  19. }

forward和redirect前缀都不会使用视图解析器拼串


SpringMVC视图解析原理

方法执行后的返回值会作为页面地址参考,转发或重定向到页面

视图解析器可能会进行页面地址的拼串

  1. 去页面的方法

    1. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  2. 试图渲染流程:将请求域中的数据在页面展示,页面就是用来渲染模型数据的

  3. 渲染页面

    1. render(mv, request, response);
  4. View与ViewResolver
    ViewResolver的作用:根据视图名(方法的返回值)得到View对象

  5. 如何根据视图名(方法的返回值)得到View对象?

    1. @Nullable
    2. protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
    3. Locale locale, HttpServletRequest request) throws Exception {
    4. if (this.viewResolvers != null) {
    5. //遍历所有的ViewResolver
    6. for (ViewResolver viewResolver : this.viewResolvers) {
    7. //根据视图名(方法的返回值)得到View对象
    8. View view = viewResolver.resolveViewName(viewName, locale);
    9. if (view != null) {
    10. return view;
    11. }
    12. }
    13. }
    14. return null;
    15. }
  6. resolveViewName(viewName, locale);细节:

    1. @Override
    2. @Nullable
    3. public View resolveViewName(String viewName, Locale locale) throws Exception {
    4. if (!isCache()) {
    5. return createView(viewName, locale);
    6. }
    7. else {
    8. Object cacheKey = getCacheKey(viewName, locale);
    9. View view = this.viewAccessCache.get(cacheKey);
    10. if (view == null) {
    11. synchronized (this.viewCreationCache) {
    12. view = this.viewCreationCache.get(cacheKey);
    13. if (view == null) {
    14. // Ask the subclass to create the View object.
    15. //*根据方法的返回值创建View对象
    16. view = createView(viewName, locale);
    17. if (view == null && this.cacheUnresolved) {
    18. view = UNRESOLVED_VIEW;
    19. }
    20. if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
    21. this.viewAccessCache.put(cacheKey, view);
    22. this.viewCreationCache.put(cacheKey, view);
    23. }
    24. }
    25. }
    26. }
    27. else {
    28. if (logger.isTraceEnabled()) {
    29. logger.trace(formatKey(cacheKey) + "served from cache");
    30. }
    31. }
    32. return (view != UNRESOLVED_VIEW ? view : null);
    33. }
    34. }
  7. createView(viewName, locale);细节

    1. @Override
    2. protected View createView(String viewName, Locale locale) throws Exception {
    3. // If this resolver is not supposed to handle the given view,
    4. // return null to pass on to the next resolver in the chain.
    5. if (!canHandle(viewName, locale)) {
    6. return null;
    7. }
    8. // Check for special "redirect:" prefix.
    9. //redirect前缀:创建RedirectView对象
    10. if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
    11. String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
    12. RedirectView view = new RedirectView(redirectUrl,
    13. isRedirectContextRelative(), isRedirectHttp10Compatible());
    14. String[] hosts = getRedirectHosts();
    15. if (hosts != null) {
    16. view.setHosts(hosts);
    17. }
    18. return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
    19. }
    20. // Check for special "forward:" prefix.
    21. //forward前缀:InternalResourceView
    22. if (viewName.startsWith(FORWARD_URL_PREFIX)) {
    23. String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
    24. InternalResourceView view = new InternalResourceView(forwardUrl);
    25. return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
    26. }
    27. // Else fall back to superclass implementation: calling loadView.
    28. //两个前缀都不是:创建父类默认的view对象
    29. return super.createView(viewName, locale);
    30. }
  8. 得到view对象(InternalResourceView)
    SpringMVC - 图4
    视图解析器得到View对象的流程:
    所有配置好的视图解析器都会尝试根据视图名(返回值)得到View对象,能得到就return,得不到就找下一个
    调用View对象的render方法(DispatcherServlet)

    1. view.render(mv.getModelInternal(), request, response);
    1. /**
    2. * Prepares the view given the specified model, merging it with static
    3. * attributes and a RequestContext attribute, if necessary.
    4. * Delegates to renderMergedOutputModel for the actual rendering.
    5. * @see #renderMergedOutputModel
    6. */
    7. @Override
    8. public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
    9. HttpServletResponse response) throws Exception {
    10. if (logger.isDebugEnabled()) {
    11. logger.debug("View " + formatViewName() +
    12. ", model " + (model != null ? model : Collections.emptyMap()) +
    13. (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
    14. }
    15. Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    16. prepareResponse(request, response);
    17. //*渲染要给页面输出的数据
    18. renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
    19. }
  9. InternalResourceView的方法renderMergedOutputModel:

    1. /**
    2. * Render the internal resource given the specified model.
    3. * This includes setting the model as request attributes.
    4. */
    5. @Override
    6. protected void renderMergedOutputModel(
    7. Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    8. // Expose the model object as request attributes.
    9. //*将模型中的数据放到请求域中
    10. exposeModelAsRequestAttributes(model, request);
    11. // Expose helpers as request attributes, if any.
    12. exposeHelpers(request);
    13. // Determine the path for the request dispatcher.
    14. String dispatcherPath = prepareForRendering(request, response);
    15. // Obtain a RequestDispatcher for the target resource (typically a JSP).
    16. RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    17. if (rd == null) {
    18. throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
    19. "]: Check that the corresponding file exists within your web application archive!");
    20. }
    21. // If already included or response already committed, perform include, else forward.
    22. if (useInclude(request, response)) {
    23. response.setContentType(getContentType());
    24. if (logger.isDebugEnabled()) {
    25. logger.debug("Including [" + getUrl() + "]");
    26. }
    27. rd.include(request, response);
    28. }
    29. else {
    30. // Note: The forwarded resource is supposed to determine the content type itself.
    31. if (logger.isDebugEnabled()) {
    32. logger.debug("Forwarding to [" + getUrl() + "]");
    33. }
    34. rd.forward(request, response);
    35. }
    36. }
  10. 将模型中的所有数据取出放在请求域中

    1. /**
    2. * Expose the model objects in the given map as request attributes.
    3. * Names will be taken from the model Map.
    4. * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}.
    5. * @param model a Map of model objects to expose
    6. * @param request current HTTP request
    7. */
    8. protected void exposeModelAsRequestAttributes(Map<String, Object> model,
    9. HttpServletRequest request) throws Exception {
    10. model.forEach((name, value) -> {
    11. if (value != null) {
    12. request.setAttribute(name, value);
    13. }
    14. else {
    15. request.removeAttribute(name);
    16. }
    17. });
    18. }
  11. 总结:视图解析器为了得到View对象,View对象可以渲染视图:转发(将模型数据放在请求域中)或重定向到页面


JstlView

  1. <!--配置视图解析器-->
  2. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
  3. p:prefix="/WEB-INF/jsp/"
  4. p:suffix=".jsp"
  5. p:viewClass="org.springframework.web.servlet.view.JstlView"
  6. />
  1. 导包jstl时会自动创建一个JstlView,JstlView可以快速方便的支持国际化功能
  2. 国际化:

    • JavaWeb国际化步骤:

      • 得得到一个Locale对象
      • 使用ResourceBundle绑定国际化资源文件
      • 使用ResourceBundle.getString(“key”)获取到国际化配置文件中的值
      • web页面的国际化,fmt标签库来做
        1. <fmt:setlocale>
        2. <fmt:setBundle>
        3. <fmt:message>
    • Jstl

      • 让Spring管理国际化资源就行

        1. <!--让SpringMVC管理国际化资源文件:配置一个资源文件管理器
        2. 注意:id必须为:messageSource
        3. -->
        4. <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"
        5. p:basename="i18n"
        6. />
      • 直接去页面使用

        1. <fmt:message>

注意:

  1. 一定要过SpringMVC的视图解析流程,否则:
    • SpringMVC - 图5
  2. 不能写前缀:forward、redirect
    • forward:new InternalResourceView();
    • redirect:new RedirectView();
    • 且创建这两个对象的构造参数中都没有locale,所以必须使用默认的View
  3. 因为需要过SpringMVC的视图解析流程,所以得在Controller类中写方法,这样代码会很冗余
    • 解决方法:
      1. <!--发送一个请求 login 直接来到WEB-INF/jsp/login.jsp页面
      2. mvc名称空间下有一个请求映射标签
      3. path:指定哪个请求
      4. -->
      5. <mvc:view-controller path="/hello/login" view-name="login"/>
      6. <!--开启mvc注解驱动模式后,mvc:view-controller不会使其他方法失效-->
      7. <mvc:annotation-driven/>

扩展:加深视图解析器和视图对象

  • 视图解析器根据方法的返回值得到视图对象;
  • 多个视图解析器都会尝试能否得到视图对象
  • 视图对象不同就可以具有不同功能;

自定义视图解析器和视图对象

  1. package com.jjj.view;
  2. import org.springframework.core.Ordered;
  3. import org.springframework.core.annotation.Order;
  4. import org.springframework.web.servlet.View;
  5. import org.springframework.web.servlet.ViewResolver;
  6. import java.util.Locale;
  7. /**
  8. * @author <a href="jinyu52370@163.com">JJJ</a>
  9. * @date 2020/5/17 23:00
  10. */
  11. public class MySeTuViewResolver implements ViewResolver, Ordered {
  12. private Integer order = 0;
  13. public void setOrder(Integer order) {
  14. this.order = order;
  15. }
  16. @Override
  17. public View resolveViewName(String viewName, Locale locale) throws Exception {
  18. if (viewName.startsWith("hso:")){
  19. return new MySeTuView();
  20. }
  21. return null;
  22. }
  23. @Override
  24. public int getOrder() {
  25. return order;
  26. }
  27. }
  1. package com.jjj.view;
  2. import org.springframework.web.servlet.View;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. import java.util.Map;
  6. /**
  7. * @author <a href="jinyu52370@163.com">JJJ</a>
  8. * @date 2020/5/17 23:02
  9. */
  10. public class MySeTuView implements View {
  11. @Override
  12. public String getContentType() {
  13. return "text/html";
  14. }
  15. @Override
  16. public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  17. System.out.println("之前保存的数据:" + model);
  18. response.getWriter().write("<h1>即将展示涩图</h1>");
  19. }
  20. }
  1. <!--自定义视图解析器
  2. order:默认的视图解析器的优先级最低:Integer.MAX_VALUE
  3. -->
  4. <bean id="mySeTuViewResolver" class="com.jjj.view.MySeTuViewResolver"
  5. p:order="1"
  6. />

流程:

  1. 让自定义的视图解析器工作(视图解析器必须放在ioc容器中)
  2. 得到自定义视图对象
  3. 自定义视图对象自定义渲染逻辑
    1. response.getWriter().write("<h1>即将展示涩图</h1>");

CRUD

create; retrieve; update; delete

通过 SpringMVC的表单标签可以实现将模型数据中的属性和HTML表单元素相绑定,以实现表单数据更便捷编辑和表单值的回显

用了表单标签的页面可能会报这个错误:请求域中没有一个command类型的对象

  1. java.lang.IllegalStateException:
  2. Neither BindingResult nor plain target object for bean name 'command' available as request attribute

数据转换&数据格式化&数据校验

SpringMVC封装自定义类型对象时,javaBean要和页面提交的数据进行一一绑定

  • 页面提交的所有数据都是字符串: Integer age,Date birth;

牵扯到以下操作:

  1. 数据绑定期间的数据类型转换:String -> Integet;
  2. 数据绑定期间的数据格式化问题:birth=1999-05-07 -> Date=1999/05/07 1999.05.07
  3. 数据校验:提交的数据必须是合法的
    • 前端校验:js+正则表达式
    • 后端校验:重要数据必须

自定义类型转换器

  1. 步骤
    1. 实现接口ConversionService:其中有很多Converter工作
    2. Converter是ConversionService中的组件,需要将WebDataBinder中的ConversionService设置成加了自定义类型转换器的ConversionService ```java package com.jjj.component;

import com.jjj.entity.User; import org.springframework.core.convert.converter.Converter;

/**

  • @author JJJ
  • @date 2020/5/19 21:30 */ public class MyStringToUser implements Converter { @Override public User convert(String s) {

    1. User user = new User();
    2. System.out.println("页面提交的将要转换的字符串:" + s);
    3. if (s.contains("/")){
    4. String[] split = s.split("/");
    5. user.setName(split[0]);
    6. user.setAge(Integer.parseInt(split[1]));
    7. user.setBirthday(split[2]);
    8. user.setPassword(split[3]);
    9. user.setMoney(Double.valueOf(split[4]));
    10. user.setAddress(split[5]);
    11. return user;
    12. }
    13. return null;

    } } ```

    1. 配置出ConversionService ```java

  1. ---
  2. <a name="24c6c82b"></a>
  3. ## 关于<mvc:annotation- driven/>
  4. ```xml
  5. <mvc:annotation-driven/>

会自动注册:

  • RequestMapping Mapping
  • RequestMapping Adapter
  • ExceptionHandlerException Resolve

还捋提供以下支持:

  • 支持使用 ConversionService实例对表单参数进行类型转换
  • 支持使用@NumberFormat annotation、@ DateTimeFormat注解完成数据类型的格式化
  • 支持使用@vaid注解对 JavaBean实例进行JSR 303验证
  • 支持使用@RequestBody和@ResponseBody注解
  1. <mvc:default-servlet-handler/>
  2. <mvc:annotation-driven/>

现象:

  1. 都没配?动态资源@RequestMapping映射的资源能访问,静态资源(.html.js,. img)不能访问
  2. 加上mvc: default-serv1et-hand1er/,不加mvc: annotation- driven/,静态资源ok,动态资源完蛋
  3. 都加上,静态动态都能访问
  1. <!--使用FormattingConversionServiceFactoryBean,既具有自定义的类型转换,也可以格式化-->
  2. <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
  3. <property name="converters">
  4. <bean class="com.jjj.component.MyStringToUser"/>
  5. </property>
  6. </bean>

数据校验

前端校验不安全,重要数据一定要后端校验

SpringMVC可以使用JSR 303做数据校验

JSR303

  • JSR303是Java为Bean数据合法性校验提供的标准框架,它已经包含在JavaEE6.0中
  • JSR303通过在Bean属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口Bean进行验证 | 注解 | 功能说明 | | —- | —- | | @Null | 被注释的元素必须为nuIl | | @NotNull | 被注释的元素必须不为null | | @AssertTrue | 被注释的元素必须为true | | @AssertFalse | 被注释的元素必须为 false | | @Min(value) | 被注释的元素必须是一个数字,基值必须大于等于指定的最小值 | | @Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | | @DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | | @DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | | @Size(max, min) | 被注释的元素的大小必须在指定的范围内 | | @Digits(integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 | | @Past | 被注释的元素必须是一个过去的日期 | | @Future | 被注释的元素必须是一个未来的日期 | | @Pattern(value) | 被注释的元素必须符合指定的正则表达式 |

  • JSR303规范:Hibernate validator扩展注解(第三方校验框架)

    • Hibernate validator是JSR303的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解注解 | 注解 | 功能说明 | | —- | —- | | @Email | 被注解的元素必须是电子邮箱地址 | | @Length | 被注解的字符串的大小必须在指定的范围内 | | @NotEmpty | 被注解的字符串必须非空 | | @Range | 被注解的元素必须在合适的范围内 |

快速开始后端校验

  1. 导包
  2. 给bean上加校验注解

    1. public class User {
    2. private Integer id;
    3. @NotEmpty
    4. @Length(min = 6, max = 18)
    5. private String name;
    6. private Integer age;
    7. @DateTimeFormat(pattern = "yyyy-MM-dd")
    8. @Past
    9. private Date birthday;
    10. @NotEmpty
    11. private String password;
    12. @NumberFormat(pattern = "#,###,###.##")
    13. private Double money;
    14. private String address;
    15. }
  3. 在SpringMVC封装对象的时候,告诉springMVC这个bean需要校验

    1. @RequestMapping(value = "/user", method = RequestMethod.POST)
    2. public String addUser(@Valid User user, BindingResult bindingResult){
    3. System.out.println(user);
    4. if (bindingResult.hasErrors()){
    5. System.out.println("有校验错误");
    6. return "add";
    7. }
    8. System.out.println("添加了" + userService.addUser(user) + "条数据");
    9. return "redirect:/users";
    10. }
  4. 校验结果
    给需要校验的bean后加BindingResult,这个BindingResult就是封装前一个bean的校验结果

    1. public String addUser(@Valid User user, BindingResult bindingResult)
  5. 根据不同的校验结果做不同的业务

    1. if (bindingResult.hasErrors()){
    2. System.out.println("有校验错误");
    3. return "add";
    4. }
    5. System.out.println("添加了" + userService.addUser(user) + "条数据");
    6. return "redirect:/users";

AJAX

  1. SpringMVC快速完成ajax功能
    • 返回数据是json就行
    • 页面发送ajax
  2. 步骤

    1. 导包

      1. <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
      2. <dependency>
      3. <groupId>com.fasterxml.jackson.core</groupId>
      4. <artifactId>jackson-databind</artifactId>
      5. </dependency>
      6. <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
      7. <dependency>
      8. <groupId>com.fasterxml.jackson.core</groupId>
      9. <artifactId>jackson-core</artifactId>
      10. </dependency>
      11. <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
      12. <dependency>
      13. <groupId>com.fasterxml.jackson.core</groupId>
      14. <artifactId>jackson-annotations</artifactId>
      15. </dependency>


拦截器HandlerInterceptor

允许运行目标方法之前进行一些拦截工作,或目标方法执行之后进行一些其他处理

preHandle:在目标方法之前调用;返回boolean:true(chain.doFilter())放行;false不放行

postHandle:在目标方法之后调用:目标方法调用之后

afterCompletion:在请求整个完成之后:来到目标页面(chain.doFilter());资源响应之后

使用步骤:

  1. 实现HandlerInterceptor接口
  2. 在SpringMVC中注册

    1. <!--拦截器-->
    2. <mvc:interceptors>
    3. <!--配置某个拦截器;默认拦截所有请求-->
    4. <bean class="com.jjj.component.MyFirstInterceptor"/>
    5. <!--配置某个拦截器;可以有更详细的信息-->
    6. <!--<mvc:interceptor>-->
    7. <!--只拦截test01请求-->
    8. <!--<mvc:mapping path="/interceptor/test01"/>-->
    9. <!--<bean class="com.jjj.component.MyFirstInterceptor"/>-->
    10. <!--</mvc:interceptor>-->
    11. </mvc:interceptors>

执行流程:跟filter一样

正常流程:拦截器.preHandle -> 目标方法 -> 拦截器.postHandle -> 页面 -> 拦截器.afterCompletion

  1. preHandle...
  2. test01...
  3. postHandle...
  4. success.jsp
  5. afterCompletion...

异常流程:

  1. preHandle不放行(return false)就没有以后的流程
  2. 只要放行了,afterCompletion都会执行

多拦截器:

正常流程:

  1. MyFirstInterceptor...preHandle...
  2. MySecondInterceptor...preHandle...
  3. test01...
  4. MySecondInterceptor...postHandle...
  5. MyFirstInterceptor...postHandle...
  6. success.jsp
  7. MySecondInterceptor...afterCompletion...
  8. MyFirstInterceptor...afterCompletion...

异常流程:

  1. 不放行:
    1. 都不放行:之后都没有
    2. first放行,second不放行:放行的拦截器的afterCompletion总会执行
      1. MyFirstInterceptor...preHandle...
      2. MySecondInterceptor...preHandle...
      3. MyFirstInterceptor...afterCompletion...

拦截器的preHandle:按照xml中的配置顺序执行

拦截器的postHandle:逆序

拦截器的afterCompletion:逆序

放行的拦截器的afterCompletion总会执行

源码

  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. // Determine handler for the current request.
  13. //拿到方法的执行链,包含拦截器
  14. mappedHandler = getHandler(processedRequest);
  15. if (mappedHandler == null) {
  16. noHandlerFound(processedRequest, response);
  17. return;
  18. }
  19. // Determine handler adapter for the current request.
  20. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  21. // Process last-modified header, if supported by the handler.
  22. String method = request.getMethod();
  23. boolean isGet = "GET".equals(method);
  24. if (isGet || "HEAD".equals(method)) {
  25. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  26. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  27. return;
  28. }
  29. }
  30. //拦截器的preHandle执行;只要有一个拦截器返回false,目标方法之后都不会执行,直接跳到afterCompletion
  31. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  32. return;
  33. }
  34. // Actually invoke the handler.
  35. //目标方法执行
  36. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  37. if (asyncManager.isConcurrentHandlingStarted()) {
  38. return;
  39. }
  40. applyDefaultViewName(processedRequest, mv);
  41. //拦截器的postHandle;目标方法正常执行才会走到这步
  42. mappedHandler.applyPostHandle(processedRequest, response, mv);
  43. }
  44. catch (Exception ex) {
  45. dispatchException = ex;
  46. }
  47. catch (Throwable err) {
  48. // As of 4.3, we're processing Errors thrown from handler methods as well,
  49. // making them available for @ExceptionHandler methods and other scenarios.
  50. dispatchException = new NestedServletException("Handler dispatch failed", err);
  51. }
  52. //页面渲染;出现异常跳到afterCompletion
  53. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  54. }
  55. //任何期间有异常;执行afterCompletion
  56. catch (Exception ex) {
  57. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  58. }
  59. catch (Throwable err) {
  60. triggerAfterCompletion(processedRequest, response, mappedHandler,
  61. new NestedServletException("Handler processing failed", err));
  62. }
  63. finally {
  64. if (asyncManager.isConcurrentHandlingStarted()) {
  65. // Instead of postHandle and afterCompletion
  66. if (mappedHandler != null) {
  67. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  68. }
  69. }
  70. else {
  71. // Clean up any resources used by a multipart request.
  72. if (multipartRequestParsed) {
  73. cleanupMultipart(processedRequest);
  74. }
  75. }
  76. }
  77. }

applyPreHandle

  1. boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HandlerInterceptor[] interceptors = getInterceptors();
  3. if (!ObjectUtils.isEmpty(interceptors)) {
  4. for (int i = 0; i < interceptors.length; i++) {
  5. HandlerInterceptor interceptor = interceptors[i];
  6. //若拦截器preHandle返回false,即不放行
  7. if (!interceptor.preHandle(request, response, this.handler)) {
  8. //不执行后续,调用afterCompletion
  9. triggerAfterCompletion(request, response, null);
  10. return false;
  11. }
  12. //放行,记录索引
  13. this.interceptorIndex = i;
  14. }
  15. }
  16. return true;
  17. }

preHandle细节:first放行,second不放行

次数 interceptor 操作
第一次 ConversionServiceExposingInterceptor interceptorIndex = 0
第二次 MyFirstInterceptor interceptorIndex = 1
第三次 MySecondInterceptor 不放行 -> 执行triggerAfterCompletion() -> 倒序遍历 -> i == 1

已经放行了的拦截器的afterCompletion总会执行

applyPostHandle

  1. void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
  2. throws Exception {
  3. HandlerInterceptor[] interceptors = getInterceptors();
  4. if (!ObjectUtils.isEmpty(interceptors)) {
  5. //倒序遍历
  6. for (int i = interceptors.length - 1; i >= 0; i--) {
  7. HandlerInterceptor interceptor = interceptors[i];
  8. interceptor.postHandle(request, response, this.handler, mv);
  9. }
  10. }
  11. }

页面渲染

  1. private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
  2. @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
  3. @Nullable Exception exception) throws Exception {
  4. boolean errorView = false;
  5. if (exception != null) {
  6. if (exception instanceof ModelAndViewDefiningException) {
  7. logger.debug("ModelAndViewDefiningException encountered", exception);
  8. mv = ((ModelAndViewDefiningException) exception).getModelAndView();
  9. }
  10. else {
  11. Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
  12. mv = processHandlerException(request, response, handler, exception);
  13. errorView = (mv != null);
  14. }
  15. }
  16. // Did the handler return a view to render?
  17. if (mv != null && !mv.wasCleared()) {
  18. //页面渲染
  19. render(mv, request, response);
  20. if (errorView) {
  21. WebUtils.clearErrorRequestAttributes(request);
  22. }
  23. }
  24. else {
  25. if (logger.isTraceEnabled()) {
  26. logger.trace("No view rendering, null ModelAndView returned.");
  27. }
  28. }
  29. if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
  30. // Concurrent handling started during a forward
  31. return;
  32. }
  33. if (mappedHandler != null) {
  34. // Exception (if any) is already handled..
  35. //正常执行:拦截器的afterCompletion;
  36. //异常执行:外部方法catch中依旧执行afterCompletion
  37. mappedHandler.triggerAfterCompletion(request, response, null);
  38. }
  39. }

afterCompletion

  1. void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
  2. throws Exception {
  3. HandlerInterceptor[] interceptors = getInterceptors();
  4. if (!ObjectUtils.isEmpty(interceptors)) {
  5. //倒序遍历
  6. for (int i = this.interceptorIndex; i >= 0; i--) {
  7. HandlerInterceptor interceptor = interceptors[i];
  8. try {
  9. interceptor.afterCompletion(request, response, this.handler, ex);
  10. }
  11. catch (Throwable ex2) {
  12. logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
  13. }
  14. }
  15. }
  16. }

多拦截器执行流程

SpringMVC - 图6


国际化

  1. 写国际化资源文件
  2. 让SpringMVC的ResourceBundleMessageSource管理国际化资源
  3. 去页面取值

SpringMVC中,区域信息是由区域信息解析器LocaleResolver得到的,默认会用AcceptHeaderLocaleResolver

  1. @Override
  2. public Locale resolveLocale(HttpServletRequest request) {
  3. Locale defaultLocale = getDefaultLocale();
  4. if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
  5. return defaultLocale;
  6. }
  7. //得到区域信息
  8. Locale requestLocale = request.getLocale();
  9. List<Locale> supportedLocales = getSupportedLocales();
  10. if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
  11. return requestLocale;
  12. }
  13. Locale supportedLocale = findSupportedLocale(request, supportedLocales);
  14. if (supportedLocale != null) {
  15. return supportedLocale;
  16. }
  17. return (defaultLocale != null ? defaultLocale : requestLocale);
  18. }

点击链接切换国际化

实现LocaleResolver接口

  1. package com.jjj.component;
  2. import org.springframework.web.servlet.LocaleResolver;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. import java.util.Locale;
  6. /**
  7. * @author <a href="jinyu52370@163.com">JJJ</a>
  8. * @date 2020/5/24 18:01
  9. *
  10. * 自定义区域信息解析器
  11. */
  12. public class MyLocaleResolver implements LocaleResolver {
  13. /**
  14. * 解析返回locale
  15. */
  16. @Override
  17. public Locale resolveLocale(HttpServletRequest request) {
  18. String localeStr = request.getParameter("locale");
  19. //若携带了locale参数,就使用参数指定的区域信息
  20. if (localeStr != null && !"".equals(localeStr)){
  21. return new Locale(localeStr.split("_")[0], localeStr.split("_")[1]);
  22. }
  23. //否则,使用请求头的locale
  24. return request.getLocale();
  25. }
  26. /**
  27. * 修改locale
  28. */
  29. @Override
  30. public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
  31. throw new UnsupportedOperationException(
  32. "Cannot change HTTP accept header - use a different locale resolution strategy");
  33. }
  34. }

jsp页面

  1. <a href="/i18n/toLoginPage?locale=zh_CN">中文</a>|<a href="/i18n/toLoginPage?locale=en_US">English</a>

springMVC配置文件

  1. <!--自定义区域信息解析器-->
  2. <bean id="localeResolver" class="com.jjj.component.MyLocaleResolver"/>

AcceptHeaderLocaleResolver:使用请求头的区域信息

  1. public class AcceptHeaderLocaleResolver implements LocaleResolver {
  2. @Override
  3. public Locale resolveLocale(HttpServletRequest request) {
  4. Locale defaultLocale = getDefaultLocale();
  5. if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
  6. return defaultLocale;
  7. }
  8. Locale requestLocale = request.getLocale();
  9. List<Locale> supportedLocales = getSupportedLocales();
  10. if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
  11. return requestLocale;
  12. }
  13. Locale supportedLocale = findSupportedLocale(request, supportedLocales);
  14. if (supportedLocale != null) {
  15. return supportedLocale;
  16. }
  17. return (defaultLocale != null ? defaultLocale : requestLocale);
  18. }
  19. @Override
  20. public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
  21. throw new UnsupportedOperationException(
  22. "Cannot change HTTP accept header - use a different locale resolution strategy");
  23. }
  24. }

FixedLocaleContextResolver:使用系统默认的区域信息

  1. public class FixedLocaleContextResolver implements LocaleContextResolver {
  2. @Override
  3. public LocaleContext resolveLocaleContext(ServerWebExchange exchange) {
  4. return new TimeZoneAwareLocaleContext() {
  5. @Override
  6. public Locale getLocale() {
  7. return locale;
  8. }
  9. @Override
  10. @Nullable
  11. public TimeZone getTimeZone() {
  12. return timeZone;
  13. }
  14. };
  15. }
  16. @Override
  17. public void setLocaleContext(ServerWebExchange exchange, @Nullable LocaleContext localeContext) {
  18. throw new UnsupportedOperationException(
  19. "Cannot change fixed locale - use a different locale context resolution strategy");
  20. }
  21. }

SessionLocaleResolver

CookieLocaleResolver

SpringMVC - 图7


filter和interceptor的使用时机

如果某些功能,需要其他组件配合完成,就使用拦截器(可以Autowried组件)


异常处理

默认是此HandlerExcptionResolver

ExceptionHandlerExceptionResolver:@ExceptionHandler
ResponseStatusExceptionResolver:@ResponseStatus
DefaultHandlerExceptionResolver:判断是否是SpringMVC自带的异常

普通bean中的处理异常方法

  1. /**
  2. * 告诉SpringMVC,这个方法专门处理这个类的异常
  3. * 1.在参数表中写上Exception变量,可以接收发生的异常
  4. * 2.参数表中不能写Model
  5. * 3.可以返回ModelAndView
  6. * 4.精确优先
  7. */
  8. @ExceptionHandler(ArithmeticException.class)
  9. public ModelAndView arithmeticException(ArithmeticException e) {
  10. System.out.println("handleException01..." + e);
  11. ModelAndView modelAndView = new ModelAndView();
  12. modelAndView.addObject("e", e);
  13. modelAndView.setViewName("error");
  14. return modelAndView;
  15. }

全局处理异常的类

  1. /**
  2. * @author <a href="jinyu52370@163.com">JJJ</a>
  3. * @date 2020/5/24 21:48
  4. *
  5. * 1.集中处理所有异常的类
  6. * 2.加入到ioc中:@ControllerAdvice
  7. * 3.全局处理异常类和某个bean的处理异常方法同时存在,bean的优先
  8. */
  9. @ControllerAdvice
  10. public class MyCentralizedException {
  11. /**
  12. * 告诉SpringMVC,这个方法专门处理这个类的异常
  13. * 1.在参数表中写上Exception变量,可以接收发生的异常
  14. * 2.参数表中不能写Model
  15. * 3.可以返回ModelAndView
  16. * 4.精确优先
  17. */
  18. @ExceptionHandler(Exception.class)
  19. public ModelAndView handleException(Exception e) {
  20. System.out.println("全局handleException..." + e);
  21. ModelAndView modelAndView = new ModelAndView();
  22. modelAndView.addObject("e", e);
  23. modelAndView.setViewName("error");
  24. return modelAndView;
  25. }
  26. }

SpringMVC自己的异常

  1. HttpRequestMethodNotSupportedException

由DefaultHandlerExceptionResolver处理

  1. @Override
  2. @Nullable
  3. protected ModelAndView doResolveException(
  4. HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
  5. try {
  6. if (ex instanceof HttpRequestMethodNotSupportedException) {
  7. return handleHttpRequestMethodNotSupported(
  8. (HttpRequestMethodNotSupportedException) ex, request, response, handler);
  9. }
  10. else if (ex instanceof HttpMediaTypeNotSupportedException) {
  11. return handleHttpMediaTypeNotSupported(
  12. (HttpMediaTypeNotSupportedException) ex, request, response, handler);
  13. }
  14. else if (ex instanceof HttpMediaTypeNotAcceptableException) {
  15. return handleHttpMediaTypeNotAcceptable(
  16. (HttpMediaTypeNotAcceptableException) ex, request, response, handler);
  17. }
  18. else if (ex instanceof MissingPathVariableException) {
  19. return handleMissingPathVariable(
  20. (MissingPathVariableException) ex, request, response, handler);
  21. }
  22. else if (ex instanceof MissingServletRequestParameterException) {
  23. return handleMissingServletRequestParameter(
  24. (MissingServletRequestParameterException) ex, request, response, handler);
  25. }
  26. else if (ex instanceof ServletRequestBindingException) {
  27. return handleServletRequestBindingException(
  28. (ServletRequestBindingException) ex, request, response, handler);
  29. }
  30. else if (ex instanceof ConversionNotSupportedException) {
  31. return handleConversionNotSupported(
  32. (ConversionNotSupportedException) ex, request, response, handler);
  33. }
  34. else if (ex instanceof TypeMismatchException) {
  35. return handleTypeMismatch(
  36. (TypeMismatchException) ex, request, response, handler);
  37. }
  38. else if (ex instanceof HttpMessageNotReadableException) {
  39. return handleHttpMessageNotReadable(
  40. (HttpMessageNotReadableException) ex, request, response, handler);
  41. }
  42. else if (ex instanceof HttpMessageNotWritableException) {
  43. return handleHttpMessageNotWritable(
  44. (HttpMessageNotWritableException) ex, request, response, handler);
  45. }
  46. else if (ex instanceof MethodArgumentNotValidException) {
  47. return handleMethodArgumentNotValidException(
  48. (MethodArgumentNotValidException) ex, request, response, handler);
  49. }
  50. else if (ex instanceof MissingServletRequestPartException) {
  51. return handleMissingServletRequestPartException(
  52. (MissingServletRequestPartException) ex, request, response, handler);
  53. }
  54. else if (ex instanceof BindException) {
  55. return handleBindException((BindException) ex, request, response, handler);
  56. }
  57. else if (ex instanceof NoHandlerFoundException) {
  58. return handleNoHandlerFoundException(
  59. (NoHandlerFoundException) ex, request, response, handler);
  60. }
  61. else if (ex instanceof AsyncRequestTimeoutException) {
  62. return handleAsyncRequestTimeoutException(
  63. (AsyncRequestTimeoutException) ex, request, response, handler);
  64. }
  65. }
  66. catch (Exception handlerEx) {
  67. if (logger.isWarnEnabled()) {
  68. logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
  69. }
  70. }
  71. return null;
  72. }

SimpleMappingExceptionResourceBundle

通过配置的方式进行异常处理

优先级最低


SpringMVC和Spring整合

目的:分工明确

  • SpringMVC的配置文件就来配置和网站转发逻辑以及网站功能有关的
    • 视图解析器,文件上传解析器,支持ajax…
  • Spring的配置文件来配置和业务有关的
    • 事务控制,数据源…

解决方法

  1. 合并配置文件:

    1. <import resource="spring.xml"/>
  2. 分容器:

    • Spring管理业务逻辑组件:扫描时排除控制器和异常管理类

      1. <context:component-scan base-package="com.jjj">
      2. <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
      3. <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
      4. </context:component-scan>
    • SpringMVC管理控制器组件:只扫描控制器和异常管理类

      1. <context:component-scan base-package="com.jjj" use-default-filters="false">
      2. <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
      3. <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
      4. </context:component-scan>
    • Spring作为父容器;SpringMVC作为子容器

      • 当从SpringMVC管理的Controller中装配Spring管理的Service组件时,可以装配;反之不可
      • 即:容器可以装配容器的组件