整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

SpringMVC 中的参数还能这么传递?涨姿势了

SpringMVC 中的参数还能这么传递?涨姿势了

天来聊一个 JavaWeb 中简单的话题,但是感觉却比较稀罕,因为这个技能点,有的小伙伴们可能没听过!

1.缘起

说到 Web 请求参数传递,大家能想到哪些参数传递方式?

参数可以放在地址栏中,不过地址栏参数的长度有限制,并且在有的场景下我们可能不希望参数暴漏在地址栏中。参数可以放在请求体中,这个没啥好说的。

小伙伴们试想这样一个场景:

在一个电商项目中,有一个提交订单的请求,这个请求是一个 POST 请求,请求参数都在请求体中。当用户提交成功后,为了防止用户刷新浏览器页面造成订单请求重复提交,我们一般会将用户重定向到一个显示订单的页面,这样即使用户刷新页面,也不会造成订单请求重复提交。

大概的代码就像下面这样:

@Controller
public class OrderController {
    @PostMapping("/order")
    public String order(OrderInfo orderInfo) {
        //其他处理逻辑
        return "redirect:/orderlist";
    }
}

这段代码我相信大家都懂吧!如果不懂可以看看松哥录制的免费的 SpringMVC 入门教程(硬核!松哥又整了一套免费视频,搞起!)。

但是这里有一个问题:如果我想传递参数怎么办?

如果是服务器端跳转,我们可以将参数放在 request 对象中,跳转完成后还能拿到参数,但是如果是客户端跳转我们就只能将参数放在地址栏中了,像上面这个方法的返回值我们可以写成:return "redirect:/orderlist?xxx=xxx";,这种传参方式有两个缺陷:

  • 地址栏的长度是有限的,也就意味着能够放在地址栏中的参数是有限的。
  • 不想将一些特殊的参数放在地址栏中。

那该怎么办?还有办法传递参数吗?

有!这就是今天松哥要和大家介绍的 flashMap,专门用来解决重定向时参数的传递问题。

2.flashMap

在重定向时,如果需要传递参数,但是又不想放在地址栏中,我们就可以通过 flashMap 来传递参数,松哥先来一个简单的例子大家看看效果:

首先我们定义一个简单的页面,里边就一个 post 请求提交按钮,如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/order" method="post">
    <input type="submit" value="提交">
</form>
</body>
</html>

然后在服务端接收该请求,并完成重定向:

@Controller
public class OrderController {
    @PostMapping("/order")
    public String order(HttpServletRequest req) {
        FlashMap flashMap = (FlashMap) req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
        flashMap.put("name", "江南一点雨");
        return "redirect:/orderlist";
    }

    @GetMapping("/orderlist")
    @ResponseBody
    public String orderList(Model model) {
        return (String) model.getAttribute("name");
    }
}

首先在 order 接口中,获取到 flashMap 属性,然后存入需要传递的参数,这些参数最终会被 SpringMVC 自动放入重定向接口的 Model 中,这样我们在 orderlist 接口中,就可以获取到该属性了。

当然,这是一个比较粗糙的写法,我们还可以通过 RedirectAttributes 来简化这一步骤:

@Controller
public class OrderController {
    @PostMapping("/order")
    public String order(RedirectAttributes attr) {
        attr.addFlashAttribute("site", "www.javaboy.org");
        attr.addAttribute("name", "微信公众号:江南一点雨");
        return "redirect:/orderlist";
    }

    @GetMapping("/orderlist")
    @ResponseBody
    public String orderList(Model model) {
        return (String) model.getAttribute("site");
    }
}

RedirectAttributes 中有两种添加参数的方式:

  • addFlashAttribute:将参数放到 flashMap 中。
  • addAttribute:将参数放到 URL 地址中。

经过前面的讲解,现在小伙伴们应该大致明白了 flashMap 的作用了,就是在你进行重定向的时候,不通过地址栏传递参数。

很多小伙伴可能会有疑问,重定向其实就是浏览器发起了一个新的请求,这新的请求怎么就获取到上一个请求保存的参数呢?这我们就要来看看 SpringMVC 的源码了。

3.源码分析

首先这里涉及到一个关键类叫做 FlashMapManager,如下:

public interface FlashMapManager {
 @Nullable
 FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
 void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}

两个方法含义一眼就能看出来:

  • retrieveAndUpdate:这个方法用来恢复参数,并将恢复过的的参数和超时的参数从保存介质中删除。
  • saveOutputFlashMap:将参数保存保存起来。

FlashMapManager 的实现类如下:

从这个继承类中,我们基本上就能确定默认的保存介质时 session。具体的保存逻辑则是在 AbstractFlashMapManager 类中。

整个参数传递的过程可以分为三大步:

第一步,首先我们将参数设置到 outputFlashMap 中,有两种设置方式:我们前面的代码 req.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE) 就是直接获取 outputFlashMap 对象然后把参数放进去;第二种方式就是通过在接口中添加 RedirectAttributes 参数,然后把需要传递的参数放入 RedirectAttributes 中,这样当处理器处理完毕后,会自动将其设置到 outputFlashMap 中,具体逻辑在 RequestMappingHandlerAdapter#getModelAndView 方法中:

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
  ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
 //省略...
 if (model instanceof RedirectAttributes) {
  Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
  HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
  if (request != null) {
   RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
  }
 }
 return mav;
}

可以看到,如果 model 是 RedirectAttributes 的实例的话,则通过 getOutputFlashMap 方法获取到 outputFlashMap 属性,然后相关的属性设置进去。

这是第一步,就是将需要传递的参数,先保存到 flashMap 中。

第二步,重定向对应的视图是 RedirectView,在它的 renderMergedOutputModel 方法中,会调用 FlashMapManager 的 saveOutputFlashMap 方法,将 outputFlashMap 保存到 session 中,如下:

protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
  HttpServletResponse response) throws IOException {
 String targetUrl = createTargetUrl(model, request);
 targetUrl = updateTargetUrl(targetUrl, model, request, response);
 // Save flash attributes
 RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);
 // Redirect
 sendRedirect(request, response, targetUrl, this.http10Compatible);
}

RequestContextUtils.saveOutputFlashMap 方法最终就会调用到 FlashMapManager 的 saveOutputFlashMap 方法,将 outputFlashMap 保存下来。我们来大概看一下保存逻辑:

public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
 if (CollectionUtils.isEmpty(flashMap)) {
  return;
 }
 String path = decodeAndNormalizePath(flashMap.getTargetRequestPath(), request);
 flashMap.setTargetRequestPath(path);
 flashMap.startExpirationPeriod(getFlashMapTimeout());
 Object mutex = getFlashMapsMutex(request);
 if (mutex != null) {
  synchronized (mutex) {
   List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
   allFlashMaps = (allFlashMaps != null ? allFlashMaps : new CopyOnWriteArrayList<>());
   allFlashMaps.add(flashMap);
   updateFlashMaps(allFlashMaps, request, response);
  }
 }
 else {
  List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
  allFlashMaps = (allFlashMaps != null ? allFlashMaps : new ArrayList<>(1));
  allFlashMaps.add(flashMap);
  updateFlashMaps(allFlashMaps, request, response);
 }
}

其实这里的逻辑也很简单,保存之前会给 flashMap 设置两个属性,一个是重定向的 url 地址,另一个则是过期时间,过期时间默认 180 秒,这两个属性在第三步加载 flashMap 的时候会用到。然后将 flashMap 放入集合中,并调用 updateFlashMaps 方法存入 session 中。

第三步,当重定向请求到达 DispatcherServlet#doService 方法后,此时会调用 FlashMapManager#retrieveAndUpdate 方法从 Session 中获取 outputFlashMap 并设置到 Request 属性中备用(最终会被转化到 Model 中的属性),相关代码如下:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
 //省略...
 if (this.flashMapManager != null) {
  FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
  if (inputFlashMap != null) {
   request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
  }
  request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
  request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
 }
 //省略...
}

注意这里获取出来的 outputFlashMap 换了一个名字,变成了 inputFlashMap,其实是同一个东西。

我们可以大概看一下获取的逻辑 AbstractFlashMapManager#retrieveAndUpdate:

public final FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response) {
 List<FlashMap> allFlashMaps = retrieveFlashMaps(request);
 if (CollectionUtils.isEmpty(allFlashMaps)) {
  return null;
 }
 List<FlashMap> mapsToRemove = getExpiredFlashMaps(allFlashMaps);
 FlashMap match = getMatchingFlashMap(allFlashMaps, request);
 if (match != null) {
  mapsToRemove.add(match);
 }
 if (!mapsToRemove.isEmpty()) {
  Object mutex = getFlashMapsMutex(request);
  if (mutex != null) {
   synchronized (mutex) {
    allFlashMaps = retrieveFlashMaps(request);
    if (allFlashMaps != null) {
     allFlashMaps.removeAll(mapsToRemove);
     updateFlashMaps(allFlashMaps, request, response);
    }
   }
  }
  else {
   allFlashMaps.removeAll(mapsToRemove);
   updateFlashMaps(allFlashMaps, request, response);
  }
 }
 return match;
}
  • 首先调用 retrieveFlashMaps 方法从 session 中获取到所有的 FlashMap。
  • 调用 getExpiredFlashMaps 方法获取所有过期的 FlashMap,FlashMap 默认的过期时间是 180s。
  • 获取和当前请求匹配的 getMatchingFlashMap,具体的匹配逻辑就两点:重定向地址要和当前请求地址相同;预设参数要相同。一般来说我们不需要配置预设参数,所以这一条可以忽略。如果想要设置,则首先给 flashMap 设置,像这样:flashMap.addTargetRequestParam("aa", "bb");,然后在重定向的地址栏也加上这个参数:return "redirect:/orderlist?aa=bb"; 即可。
  • 将获取到的匹配的 FlashMap 对象放入 mapsToRemove 集合中(这个匹配到的 FlashMap 即将失效,放入集合中一会被清空)。
  • 将 allFlashMaps 集合中的所有 mapsToRemove 数据清空,同时调用 updateFlashMaps 方法更新 session 中的 FlashMap。
  • 最终将匹配到的 flashMap 返回。

这就是整个获取 flashMap 的方法,整体来看还是非常 easy 的,并没有什么难点。

4.小结

好啦,今天就和小伙伴们分享了一下 SpringMVC 中的 flashMap,不知道大家有没有在工作中用到这个东西?如果刚好碰到松哥前面所说的需求,用 FlashMap 真的还是蛮方便的。如果需要下载本文案例,小伙伴们可以在公众号【江南一点雨】后台回复 20210302,好啦,今天就和大家聊这么多~

标题:了解如何使用 Spring MVC 和 Thymeleaf 创建网页(来源:Spring中国教育管理中心)

本指南将引导您完成使用 Spring 创建“Hello, World”网站的过程。

你将建造什么

您将构建一个具有静态主页并且还将接受 HTTP GET 请求的应用程序:http://localhost:8080/greeting

它将响应一个显示 HTML 的网页。HTML 的正文将包含一个问候语:“Hello, World!”

name您可以使用查询字符串中的可选参数自定义问候语。该 URL 可能是http://localhost:8080/greeting?name=User.

name参数值覆盖默认值,并通过World内容更改为“Hello,User!”反映在响应中

你需要什么

  • 约15分钟
  • 最喜欢的文本编辑器或 IDE
  • JDK 1.8或更高版本
  • Gradle 4+或Maven 3.2+
  • 您还可以将代码直接导入 IDE:
    • 弹簧工具套件 (STS)
    • IntelliJ IDEA

如何完成本指南

像大多数 Spring入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到工作代码。

从头开始,请继续从 Spring Initializr 开始。

跳过基础知识,请执行以下操作:

  • 下载并解压缩本指南的源存储库,或使用Git克隆它:git clone https://github.com/spring-guides/gs-serving-web-content.git
  • 光盘进入gs-serving-web-content/initial
  • 跳转到创建 Web 控制器。

完成后,您可以对照中的代码检查结果gs-serving-web-content/complete

从 Spring Initializr 开始

您可以使用这个预先初始化的项目并单击 Generate 下载 ZIP 文件。此项目配置为适合本教程中的示例。

手动初始化项目:

  1. 导航到https://start.spring.io。该服务提取应用程序所需的所有依赖项,并为您完成大部分设置。
  2. 选择 Gradle 或 Maven 以及您要使用的语言。本指南假定您选择了 Java。
  3. 单击Dependencies并选择Spring WebThymeleafSpring Boot DevTools
  4. 单击生成
  5. 下载生成的 ZIP 文件,该文件是根据您的选择配置的 Web 应用程序的存档。

如果您的 IDE 具有 Spring Initializr 集成,您可以从您的 IDE 完成此过程。

你也可以从 Github 上 fork 项目并在你的 IDE 或其他编辑器中打开它。

创建 Web 控制器

在 Spring 构建网站的方法中,HTTP 请求由控制器处理。@Controller您可以通过注释轻松识别控制器。在以下示例中,通过返回 a 的名称(在本例中GreetingController为 )来处理 GET 请求。A负责呈现 HTML 内容。以下清单(来自)显示了控制器:/greetingViewgreetingViewsrc/main/java/com/example/servingwebcontent/GreetingController.java

package com.example.servingwebcontent;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class GreetingController {

	@GetMapping("/greeting")
	public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) {
		model.addAttribute("name", name);
		return "greeting";
	}

}复制

这个控制器简洁明了,但是有很多事情要做。我们一步一步分解。

@GetMapping注释确保 HTTP GET 请求/greeting映射到greeting()方法。

@RequestParam将查询字符串参数的值绑定name到方法的name参数中greeting()。此查询字符串参数不是required。如果请求中不存在,则使用defaultValueof World。参数的值name被添加到Model对象中,最终使视图模板可以访问它。

方法体的实现依赖于视图技术(在本例中为Thymeleaf)来执行 HTML 的服务器端呈现。Thymeleaf 解析greeting.html模板并评估th:text表达式以呈现${name}在控制器中设置的参数值。以下清单(来自src/main/resources/templates/greeting.html)显示了greeting.html模板:

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head> 
    <title>Getting Started: Serving Web Content</title> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>

确保你的类路径上有 Thymeleaf(工件坐标:)org.springframework.boot:spring-boot-starter-thymeleaf。它已经存在于 Github 的“初始”和“完整”示例中。

Spring Boot 开发工具

开发 Web 应用程序的一个常见功能是编写更改代码、重新启动应用程序并刷新浏览器以查看更改。整个过程会消耗大量时间。为了加快这个刷新周期,Spring Boot 提供了一个方便的模块,称为spring-boot-devtools。Spring Boot 开发工具:

  • 启用热插拔。
  • 切换模板引擎以禁用缓存。
  • 启用 LiveReload 以自动刷新浏览器。
  • 基于开发而不是生产的其他合理默认值。

运行应用程序

Spring Initializr 为您创建了一个应用程序类。在这种情况下,您不需要进一步修改 Spring Initializr 提供的类。以下清单(来自src/main/java/com/example/servingwebcontent/ServingWebContentApplication.java)显示了应用程序类:

package com.example.servingwebcontent;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ServingWebContentApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServingWebContentApplication.class, args);
    }

}复制

@SpringBootApplication是一个方便的注释,它添加了以下所有内容:

  • @Configuration: 将类标记为应用程序上下文的 bean 定义源。
  • @EnableAutoConfiguration:告诉 Spring Boot 根据类路径设置、其他 bean 和各种属性设置开始添加 bean。例如,如果spring-webmvc位于类路径上,则此注释将应用程序标记为 Web 应用程序并激活关键行为,例如设置DispatcherServlet.
  • @ComponentScan: 告诉 Spring 在包中查找其他组件、配置和服务com/example,让它找到控制器。

main()方法使用 Spring Boot 的SpringApplication.run()方法来启动应用程序。您是否注意到没有一行 XML?也没有web.xml文件。这个 Web 应用程序是 100% 纯 Java,您不必处理任何管道或基础设施的配置。

构建一个可执行的 JAR

您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必要依赖项、类和资源的单个可执行 JAR 文件并运行它。构建可执行 jar 可以在整个开发生命周期、跨不同环境等中轻松地将服务作为应用程序交付、版本化和部署。

如果您使用 Gradle,则可以使用./gradlew bootRun. 或者,您可以使用构建 JAR 文件./gradlew build,然后运行 ?JAR 文件,如下所示:

java -jar build/libs/gs-serving-web-content-0.1.0.jar

如果您使用 Maven,则可以使用./mvnw spring-boot:run. 或者,您可以使用构建 JAR 文件,./mvnw clean package然后运行该 JAR 文件,如下所示:

java -jar 目标/gs-serving-web-content-0.1.0.jar

此处描述的步骤创建了一个可运行的 JAR。您还可以构建经典的 WAR 文件。

显示记录输出。应用程序应在几秒钟内启动并运行。

测试应用程序

现在网站正在运行,请访问http://localhost:8080/greeting,您应该会看到“Hello, World!”。

通过访问提供name查询字符串参数http://localhost:8080/greeting?name=User。注意消息是如何从“Hello, World!”改变的。到“你好,用户!”:

这一变化表明,@RequestParam安排在GreetingController按预期工作。该name参数已被赋予默认值World,但可以通过查询字符串显式覆盖它。

添加主页

静态资源,包括 HTML、JavaScript 和 CSS,可以通过将它们放到源代码中的正确位置从 Spring Boot 应用程序提供。默认情况下,Spring Boot 从位于/static(或/public) 的类路径中的资源中提供静态内容。该index.html资源是特殊的,因为如果它存在,它会被用作“欢迎页面” "serving-web-content/ which means it is served up as the root resource (that is, at `http://localhost:8080/)。因此,您需要创建以下文件(您可以在 中找到该文件src/main/resources/static/index.html):

<!DOCTYPE HTML>
<html>
<head> 
    <title>Getting Started: Serving Web Content</title> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p>Get your greeting <a href="/greeting">here</a></p>
</body>
</html>复制

重新启动应用程序时,您将看到 HTML 位于http://localhost:8080/.

概括

恭喜!您刚刚使用 Spring 开发了一个网页。

#程序员#

活就像海洋,只有意志坚强的人才能到达彼岸。

已经很久没有发文章了呀,想必大家都挂念我了,哈哈。

温故而知新,今天一起来复习一下spring mvc的内容吧。

spring mvc简介与运行原理

Spring的模型-视图-控制器(MVC)框架是围绕一个DispatcherServlet来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等,甚至还能支持文件上传。

原理.png

  • (1) Http请求:客户端请求提交到DispatcherServlet。
  • (2) 寻找处理器:由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller。
  • (3) 调用处理器:DispatcherServlet将请求提交到Controller。
  • (4)(5)调用业务处理和返回结果:Controller调用业务逻辑处理后,返回ModelAndView。
  • (6)(7)处理视图映射并返回模型: DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图。
  • (8) Http响应:视图负责将结果显示到客户端。

主要注解

spring mvc注解.png

ContextLoaderListener

在讲ContextLoaderListener之前,首先来了解一下web.xml的作用。

  • 一个web中可以没有web.xml文件,也就是说,web.xml文件并不是web工程必须的。web.xml文件是用来初始化配置信息:比如Welcome页面、servlet、servlet-mapping、filter、listener、启动加载级别等。当你的web工程没用到这些时,你可以不用web.xml文件来配置你的Application。
  • 当要启动某个web项目时,服务器软件或容器如(tomcat)会第一步加载项目中的web.xml文件,通过其中的各种配置来启动项目,只有其中配置的各项均无误时,项目才能正确启动。web.xml有多项标签,在其加载的过程中顺序依次为:context-param >> listener >> fileter >> servlet?。(同类多个节点以出现顺序依次加载)

web.xml加载过程.png

而spring mvc启动过程大致分为两个过程:

  • ContextLoaderListener初始化,实例化IoC容器,并将此容器实例注册到ServletContext中。
  • DispatcherServlet初始化。

web.xml配置.png

其中ContextLoaderListener监听器它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。

  • ContextLoaderListener在web.xml中的配置
<!-- 配置contextConfigLocation初始化参数 -->
<context-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<!-- 配置ContextLoaderListerner -->
<listener>
 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> 

ServletContextListener 接口有两个方法:contextInitialized,contextDestroyed

DispatcherServlet

Spring MVC框架,与其他很多web的MVC框架一样:请求驱动;所有设计都围绕着一个中央Servlet来展开,它负责把所有请求分发到控制器;同时提供其他web应用开发所需要的功能。不过Spring的中央处理器,DispatcherServlet,能做的比这更多。

下图展示了Spring Web MVC的DispatcherServlet处理请求的工作流。熟悉设计模式的朋友会发现,DispatcherServlet应用的其实就是一个“前端控制器”的设计模式(其他很多优秀的web框架也都使用了这个设计模式)。

  • 流程图

spring mvc处理请求的流程.jpg

  • 在web.xml中的配置
<!-- servlet定义 -->
<servlet>
 <servlet-name>dispatcher</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <load-on-startup>1</load-on-startup>
</servlet>
 
<servlet-mapping>
 <servlet-name>dispatcher</servlet-name>
 <url-pattern>/</url-pattern>
</servlet-mapping>

其中

  • load-on-startup:表示启动容器时初始化该Servlet;
  • url-pattern:表示哪些请求交给Spring Web MVC处理, “/” 是用来定义默认servlet映射的。也可以如“*.html”表示拦截所有以html为扩展名的请求。

在Spring MVC中,每个DispatcherServlet都持有一个自己的上下文对象WebApplicationContext,它又继承了根(root)WebApplicationContext对象中已经定义的所有bean。这些继承的bean可以在具体的Servlet实例中被重载,在每个Servlet实例中你也可以定义其scope下的新bean。

WebApplicationContext继承自ApplicationContext,它提供了一些web应用经常需要用到的特性。它与普通的ApplicationContext不同的地方在于,它支持主题的解析,并且知道它关联到的是哪个servlet(它持有一个该ServletContext的引用)

DispatcherServlet继承结构

spring mvc同时提供了很多特殊的注解,用于处理请求和渲染视图等。DispatcherServlet初始化的过程中会默认使用这些特殊bean进行配置。如果你想指定使用哪个特定的bean,你可以在web应用上下文WebApplicationContext中简单地配置它们。

特殊bean.png

其中,常用的ViewResolver的配置。以jsp作为视图为例

<!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
 <property name="prefix" value="/WEB-INF/jsp/" />
 <property name="suffix" value=".jsp" />
</bean>

配置上传文件限制MultipartResolver

<!-- 上传限制 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
 <!-- 上传文件大小限制为31M,31*1024*1024 -->
 <property name="maxUploadSize" value="32505856"/>
</bean>

applicationContext.xml中的标签

applicationContext.xml配置文件标签.png

文件上传

前面说到DispatcherServlet中有个特殊的Bean叫MultipartResolver,可用于限制文件的上传大小等。当解析器MultipartResolver完成处理时,请求便会像其他请求一样被正常流程处理。

  • 表单
<form method="post" action="/form" enctype="multipart/form-data">
 <input type="text" name="name"/>
 <input type="file" name="file"/>
 <input type="submit"/>
</form>
  • 控制器

@RequestMapping(path="/form", method=RequestMethod.POST)

public String handleFormUpload(@RequestParam("name") String name,

@RequestParam("file") MultipartFile file) {

if (!file.isEmpty()) {

byte[] bytes=file.getBytes();

// store the bytes somewhere

return "redirect:uploadSuccess";

}

return "redirect:uploadFailure";

}

异常处理

先来说下常见的异常处理有几种方式,如下图:

异常处理方式.png

Spring的处理器异常解析器HandlerExceptionResolver接口的实现负责处理各类控制器执行过程中出现的异常。也是上面提到的,是DispatcherServlet中的特殊bean,可以自定义配置处理。

某种程度上讲,HandlerExceptionResolver与你在web应用描述符web.xml文件中能定义的异常映射(exception mapping)很相像,不过它比后者提供了更灵活的方式。比如它能提供异常被抛出时正在执行的是哪个处理器这样的信息。

  • HandlerExceptionResolver 提供resolveException接口
public interface HandlerExceptionResolver { 
 ModelAndView resolveException( 
 HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); 
} 
  • 在BaseController中使用 @ExceptionHandler注解处理异常
 @ExceptionHandler(Exception.class)
 public Object exceptionHandler(Exception ex, HttpServletResponse response, 
 HttpServletRequest request) throws IOException {
 String url="";
 String msg=ex.getMessage();
 Object resultModel=null;
 try {
 if (ex.getClass()==HttpRequestMethodNotSupportedException.class) {
 url="admin/common/500";
 System.out.println("--------毛有找到对应方法---------");
 } else if (ex.getClass()==ParameterException.class) {//自定义的异常
 
 } else if (ex.getClass()==UnauthorizedException.class) {
 url="admin/common/unauth";
 System.out.println("--------毛有权限---------");
 }
 String header=req.getHeader("X-Requested-With");
 boolean isAjax="XMLHttpRequest".equalsIgnoreCase(header);
 String method=req.getMethod();
 boolean isPost="POST".equalsIgnoreCase(method);
 if (isAjax || isPost) {
 return Message.error(msg);
 } else {
 ModelAndView view=new ModelAndView(url);
 view.addObject("error", msg);
 view.addObject("class", ex.getClass());
 view.addObject("method", request.getRequestURI());
 return view;
 }
 } catch (Exception exception) {
 logger.error(exception.getMessage(), exception);
 return resultModel;
 } finally {
 logger.error(msg, ex);
 ex.printStackTrace();
 }
 }
  • *在web.xml中处理异常 *
<!-- 默认的错误处理页面 -->
<error-page>
 <error-code>403</error-code>
 <location>/403.html</location>
</error-page>
<error-page>
 <error-code>404</error-code>
 <location>/404.html</location>
</error-page>
<!-- 仅仅在调试的时候注视掉,在正式部署的时候不能注释 -->
<!-- 这样配置也是可以的,表示发生500错误的时候,转到500.jsp页面处理。 -->
<error-page> 
 <error-code>500</error-code> 
 <location>/500.html</location> 
</error-page> 
<!-- 这样的配置表示如果jsp页面或者servlet发生java.lang.Exception类型(当然包含子类)的异常就会转到500.jsp页面处理。 -->
<error-page> 
 <exception-type>java.lang.Exception</exception-type> 
 <location>/500.jsp</location> 
</error-page> 
<error-page> 
 <exception-type>java.lang.Throwable</exception-type> 
 <location>/500.jsp</location> 
</error-page>
<!-- 当error-code和exception-type都配置时,exception-type配置的页面优先级高及出现500错误,发生异常Exception时会跳转到500.jsp--> 
  • 来一个问题:HandlerExceptionResolver和web.xml中配置的error-page会有冲突吗?

解答:如果resolveException返回了ModelAndView,会优先根据返回值中的页面来显示。不过,resolveException可以返回null,此时则展示web.xml中的error-page的500状态码配置的页面。

当web.xml中有相应的error-page配置,则可以在实现resolveException方法时返回null。

API文档中对返回值的解释:

** return a corresponding ModelAndView to forward to, or null for default processing.**

写在最后

欢迎大家关注,后期文章一定更加精彩


上一篇:Nginx
下一篇:JavaScript Document 对象