SpringMVC | 基础(一)

一、简介

SpringMVC 通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无需实现任何接口。支持 REST 风格的 URL 请求

二、搭建环境及HelloWorld

2.1 加入架包
  • commons-logging-1.2.jar
  • spring-aop-5.0.7.RELEASE.jar
  • spring-beans-5.0.7.RELEASE.jar
  • spring-context-5.0.7.RELEASE.jar
  • spring-core-5.0.7.RELEASE.jar
  • spring-expression-5.0.7.RELEASE.jar
  • spring-web-5.0.7.RELEASE.jar
  • spring-webmvc-5.0.7.RELEASE.jar

2.2 配置web.xml

配置 DispatcherServlet:默认加载 /WEB-INF/<servlet-name>-servlet.xml 的 Spring 配置文件来启动 WEB 层的容器。同时也可以通过 contextConfigLocation 初始化参数自定义配置文件的位置和名称

<!-- 配置 dispatcherServlet -->
<servlet>
    <servlet-name>springDispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 配置 DispatcherServlet 的一个初始化参数: 配置 SpringMVC 配置文件的位置和名称 -->
    <!-- 
        实际上也可以不通过  contextConfigLocation 来配置 SpringMVC 的配置文件, 而使用默认的 
        默认的配置文件为: /WEB-INF/<servlet-name>-servlet.xml
    -->
    
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    
    <!-- 当前 Servlet 在 WEB 应用被加载的时候就被创建, 而不是在第一次请求的时候创建  -->
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>springDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

DispatcherServlet 的任务就是拦截请求发送给 Spring MVC 控制器

2.3 创建 SpringMVC 视图解析器
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="edu.just.springmvc"></context:component-scan>
    
<!-- 配置视图解析器: 如何把 handler 方法的返回值解析为实际的物理视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

将视图逻辑名解析为:/WEB-INF/views/<viewName>.jsp

2.4 创建请求处理器类
  1. 使用 @RequestMapping 注解来映射请求的 URL
  2. 返回值会通过视图解析器解析为实际的物理视图,对于 InternalResourceViewResolver 视图解析器,会做如下的解析:
    prefix + returnVal + suffix 这样的方式得到实际的物理视图,然后会做转发操作
@Controller     //请求处理器
public class HelloWorld {
    
    @RequestMapping("/helloworld")
    public String hello() {
        System.out.println("hello world");
        return "success";
    }

}

以上转发到 /WEB-INF/views/success.jsp 这个页面

三、具体使用

3.1 使用@RequestMapping映射请求

3.1.1

@RequestMapping 注解为控制器指定可以处理哪些 URL 请求,在类和方法都可以标注

  • 类定义:提供初步的请求映射信息。相对于 WEB 应用的根目录
  • 方法处:进一步细分映射信息。相对于类定义处的 URL,若类上面没有 @RequestMapping 注解,则方法处标记的 URL 相对于 WEB 应用的根目录

DispatcherServlet 截取请求后,就通过 @RequestMapping 提供的映射信息确认请求所对应的处理方法

@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
    
    private static final String SUCCESS = "success";

    @RequestMapping("/testRequestMapping")
    public String testRequestMapping() {
        System.out.println("testRequestMapping");
        return SUCCESS;
    }

}

访问到此方法的地址是:WEB容器的根路径/springmvc/testRequestMapping,同时由于配置了视图解析器,因此直接转到 /WEB-INF/views/ 下的 success.jsp 页面

@RequestMapping 的 value、method、params 以及 heads 分别标识请求 URL、请求方法、请求参数以及请求头的映射条件,他们之间是与的关系

@RequestMapping(..., method=RequestMethod.POST)                //使用 method 属性来指定请求方式
@RequestMapping(..., params={"username", "age!=10"}, headers="Content-Type=text/html")          //使用 params 和 headers 

PS:用的比较少

3.1.2 使用 Ant 风格来写地址

?:匹配文件名中的一个字符
*:匹配文件名中的任意字符
**:匹配多层路径

/springmvc/test??? ——> /springmvc/testaaa
/springmvc/*/test ——> /springmvc/asv/test
/springmvc/**/test ——> /springmvc/aaa/bbb/test

PS:用的比较少

3.1.3 ⭐使用@PathVariable映射URL绑定的占位符

通过 @PathVariable 可以将 URL 占位符参数绑定到控制器处理方法的入参中

@RequestMapping("/testPathVariable/id={id123}")
public String testPathVariable(@PathVariable("id123") String id) {
    System.out.println(id);
    return SUCCESS;
}

URL 中的 {xxx} 占位符可以通过 @PathVariable("xxx") 绑定到操作方法的入参中。这个时候 @PathVariable 中的参数必须与占位符 {} 中的参数相同

@RequestMapping("/testPathVariable/id={id12}")
public String testPathVariable(@PathVariable String id12) {
    System.out.println(id12);
    return SUCCESS;
}

当然,也可以不用指定 @PathVariable 后面的参数,此时 {xxx} 占位符里面的参数可以直接绑定到控制器处理方法的入参中,但是两个参数必须相同

3.2 映射请求参数和请求参数

3.2.1 使用@RequestParam绑定请求参数值

@RequestParam 可以在后台获取从前台传过来的数据。
value:请求参数的参数名
required:该参数是否必须。默认为 true
defaultValue:请求参数的默认值

@RequestMapping(value="/testRequestParam")
public String testRequestParam(@RequestParam(value="username") String un,
        @RequestParam(value="age", required=false, defaultValue="0") Integer age) {
    System.out.println("testRequestParam: " + un + ",age: " + age);
    return SUCCESS;
}

上图获取从前台传过来的参数名为 usernameage 的值,其中 age 是可选的,如前台的地址是 ...?username=5&age=5

如果参数为 int 类型,且没有加默认的值,则会报错。两种修改的方式:1. 将 int 类型改为 Integer;2. 如果参数类型必须为 int,则添加默认值

3.2.2 使用POJO对象绑定请求参数值

SpringMvc 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值,支持级联属性。

前台代码

<form action="springmvc/testpojo" method="post">
    username: <input type="text" name="username"/>
    <br>
    password: <input type="password" name="password"/>
    <br>
    email: <input type="text" name="email"/>
    <br>
    city: <input type="text" name="address.city"/>
    <br>
    <input type="submit" value="Submit"/>
</form>

从后台获取前台传过来的数据

@RequestMapping("/testpojo")
public String testPojo(User user) {
    System.out.println("^^" + user);
    return SUCCESS;
}


3.2.3 使用Servlet API作为入参

包括HttpServletRequestHttpServletResponseHttpSessionPrincipalLocaleInputStreamOutputStreamReaderWriter

@RequestMapping("/testServletAPI")
public void testServletAPI(HttpServletRequest request, HttpServletResponse response, Writer out) throws IOException {
    System.out.println("testServletAPI, " + request + ", " + response);
    out.write("hello qorld");
}


3.3 处理模型数据(常用于回显操作)

3.3.1 ModelAndView

1.
springmvc 会把 ModelAndView 的 model 中数据放在 request 域对象中

/**
  * 目标方法的返回值可以是 ModelAndView 类型
  * 其中包含视图和模型信息
  * SpringMVC 会把 ModelAndView 的 model 中的数据放入到 request 域对象中
*/
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
    String viewName = SUCCESS;
    ModelAndView modelAndView = new ModelAndView(viewName);
        
    //添加模型数据到 ModelAndView 中
    modelAndView.addObject("time", new Date());
        
    return modelAndView;
}

success.jsp

time: ${requestScope.time }

这里从 request 作用域中获取参数 time 对应的值

3.3.2 Map和Model
  1. SpringMVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器
  2. 如果方法的入参为 Map 或 Model 类型,SpringMVC 会将隐含模型的引用传递给这些入参

Map 方式

/**
  * 目标方法可以添加 Map(实际上也可以是 Model 类型或者 ModelMap 类型) 类型的参数
  */
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
    map.put("names", Arrays.asList("Tom", "Jerry", "Mike"));

    return SUCCESS;
}

success.jsp

map: ${requestScope.names }


Model 方式:比较常用

@RequestMapping("value")
public String test(Model model) {
    model.addAttribute("time", "hello");
    return SUCCESS;
}

success.jsp

time: ${requestScope.time }


3.3.3 @SessionAttributes

将模型中的某个属性暂存到 HttpSession 中,以便多个请求之间可以共享这个属性

@SessionAttribute 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中

  1. @SessionAttributes(types={User.class}) 会将隐含模型中类型为 User.class 的属性放到会话中
  2. @SessionAttributes(value={"user"}) 将名为 user 的模型属性添加到会话中
  3. @SessionAttribute(values={"user1", "user2"})
@SessionAttributes(value={"user"}, types={User.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {

    private static final String SUCCESS = "success";
  
    @RequestMapping("/testSessionAttributes")
    public String testSessionAttributes(Map<String, Object> map) {
        User user = new User(null, "luwenhe", "lwh011305", "123@126.com");
        
        map.put("user", user);
        map.put("user1", "ni hao shi jie");
        return SUCCESS;
    }
}

PS:该注解只能放在类的上面,而不能修饰方法

session: ${sessionScope.user }
session1: ${sessionScope.user1 }


3.3.4 @ModelAttribute

方法上使用 @ModelAttribute 注解,SpringMVC 在调用目标方法之前,会先逐个调用在方法上标注了 @ModelAttribute 的方法

index.jsp:用于从表单中输入数据,模拟从数据库中获取数据

<form action="springmvc/testModelAttribute" method="post">
    <input type="hidden" name="id" value="1"/>
    username: <input type="text" name="username" value="Tom"/>
    <br>
    email: <input type="text" name="email" value="tom@126.com"/>
    <br>
    <input type="submit" value="Submit"/>
</form>

如果没用 @ModelAttribute 注解,则会输出 User [Id=1, username=Tom, password=null, email=tom@126.com]

@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
    System.out.println("修改: " + user);
    return SUCCESS;
}

如果在上述代码之前加入标注了 @ModelAttribute 注解的方法,模拟修改了对象 User 的信息

@ModelAttribute
public void getUser(@RequestParam(value="id", required=false) Integer id,
            Map<String, Object> map) {      //从后台获取前台参数名字为 id 的元素的值
    System.out.println("id: " + id);
    if(id != null) {
        User user = new User(1, "Tom", "123", "tom@126.com");
        System.out.println("从数据库中获取一个对象: " + user);
        
        map.put("user", user);
    }
}

最终的输出是:

id: 1
从数据库中获取一个对象: User [Id=1, username=Tom, password=123, email=tom@126.com]
修改: User [Id=1, username=Tom, password=123, email=tom@126.com]

可见 SpringMVC 从 Map 中取出 User 对象,并把对象传入了目标方法的参数中

注意:在 @ModelAttribute 修饰的方法中, 放入到 Map 时的键需要和目标方法参数的第一个字母小写的字符串一致, 如参数为 User, 则 Map 的键为 user

SpringMVC 确定目标方法 POJO 类型入参的过程:
1. 确定一个 key: 
   1). 若目标方法的 POJO 类型的参数没有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母的小写
   2). 若使用了 @ModelAttribute 来修饰, 则 key 为 @ModelAttribute 注解的属性值

2. 在 implicitModel 中查找 key 对应的对象, 若存在, 则作为入参传入
   1). 若在 @ModelAttribute 标记的方法在 Map 中保存过, 且 key 和 1 确定的 key 一致, 则会获取到.

3. 若 implicitModel 不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰, 若使用了该注解, 
   且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所对应的 value 值, 
   若存在则直接传到目标方法的入参中, 若不存在则将抛出异常

4. 若 Handler 没有标识 @SessionAttribute 注解或 @SessionAttribute 注解的 value 
   值中不包含 key, 则会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数, 此时只能获取从前台传过来的值

5. SpringMVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中, 进而保存到 request 中


3.4 视图和视图解析器

  • 请求方法执行完成后,最后返回一个 ModelAndView 对象。对于那些返回 String,View 或者 ModelMap 类型的方法,SpringMVC 会在内部将他们装配成一个 ModelAndView 对象
  • SpringMVC 接主视图解析器(ViewResolver)得到最终的视图对象 View,最终的视图可以是 JSP,也可以是其他类型

视图

视图的作用是渲染模型数据,将模型数据以某种形式呈现给客户。常见的视图实现类有:

  • InternalResourceView:将 JSP 或其他资源封装成一个视图,是 InternalResouceViewResolver 默认使用的视图实现类
  • JstlView:如果 JSP 文件中使用了 JSTL 国际化标签的功能,则需要使用该试图类
  • AbstractExcelView:Excel 文档视图的抽象类,用于构造 Excel 文档

视图解析器

所有视图解析器必须实现 ViewResovler 接口。常见的视图解析器类有:

  • BeanNameViewResolver:将逻辑视图名解析为一个 Bean,Bean 的 id 等于逻辑视图名
  • InternalResourceViewResolver:将视图名解析为一个 URL 文件
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="edu.just.springmvc"></context:component-scan>
    
<!-- 配置视图解析器: 如何把 handler 方法的返回值解析为实际的无力视图 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>


重定向

如果字符串中带有 forward: 或者 redirect,SpringMVC 会对他们做特殊处理,前者为转发操作,后者
为重定向

@RequestMapping("/testRedirect")
public String testRedirect() {
    System.out.println("testRedirect...");
    return "redirect:/index.jsp";
}

此代码会重定向到根目录下的 index.jsp 页面

3.5 使用RESTFul

REST 即 Representational State Tranfer,即表现层状态转化。
表现层(Representation):把资源具体呈现出来的形式。
状态转化(State Tranfer):每发出一个请求,就代表了客户端和服务器的一次交互。

如何发送 PUT 请求和 DELETE 请求?

  1. 需要配置 HiddenHttpMethodFilter
  2. 需要发送 POST 请求
  3. 需要在发送 POST 请求时携带一个 name="_method" 的隐藏域, 值为 DELETE 或 PUT

配置文件

<!-- 
    配置 org.springframework.web.filter.HiddenHttpMethodFilter: 
    可以把 POST 请求转为 DELETE 或者 POST 请求 
-->
<filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
    
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>   
</filter-mapping>

HiddenHttpMethodFilter:由于浏览器只支持 GET 和 POST,不支持 DELETE 和 PUT,因此此过滤器可以将请求转换为支持四种请求

前台页面:

<form action="springmvc/testRest/1" method="post">
    <input type="hidden" name="_method" value="DELETE"/>
    <input type="submit" value="TestRest DELETE"/>
</form>

从后台获取

@RequestMapping(value="/testRest/{id}", method=RequestMethod.DELETE)    //delete 方法
@ResponseBody
public String testRestDelete(@PathVariable Integer id) {
    System.out.println("testRest DELETE: " + id);
    return SUCCESS;
}

@ResponseBody 一开始没有加,会报错,百度之后由此加上

3.5 处理静态资源

若将 DispatcherServlet 请求映射配置为 /,则 SpringMVC 将捕获 WEB 容器的所有请求,包括静态资源的请求,SpringMVC 会将他们当成一个普通请求来处理,因此会找不到对应处理器而报错

解决方案:可以在 SpringMVC 的配置文件中配置 <mvc:default-servlet-handler></mvc:default-servlet-handler> 的方式

原理:
<mvc:default-servlet-handler/> 将在 SpringMVC 上下文定义一个 DefaultServletHttpRequestHandler,他会对进入 DispatcherServlet 的请求进行筛选,如果发现是没有经过映射的请求(@RequestMapping),就将请求交给 WEB 应用服务器的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理

此时还需要在配置文件中加入 <mvc:annotation-driven/> 才可以运行

<mvc:default-servlet-handler></mvc:default-servlet-handler>
    
<!-- 标准配置 -->
<mvc:annotation-driven></mvc:annotation-driven>


关于<mvc:annotation-driven>

  1. <mvc:annotation-driven/> 会自带注册 RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver 三个 Bean

  2. 同时提供以下支持:
    ①. 支持使用 ConversionService 实例对表单进行类型转换
    ②. 支持使用 @NumberFormat annotation@DateTimeFormat 注解完成数据类型的格式化
    ③. 支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
    ④. 支持使用 @RequestBody@ResponseBody 注解

PS:一般情况下,都会配置 <mvc:annotation-driven/> 这个说明

3.6 数据绑定

数据绑定流程

核心是 DataBinder

  • DataBinder:调用装配在 SpringMVC 上下文的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
  • Validator :对已经绑定了请求信息的入参对象进行数据合法性校验
  • BindingResult:SpringMVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参

@InitBinder注解
  • @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的字类,用于完成由表单字段到 JavaBean 属性的绑定
  • @InitBinder 方法不能有返回值,必须声明为 void
  • @InitBinder 方法的参数通常是 WebDataBinder
@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.setDisallowedFields("lastName");
}

以上代码表示不能绑定对象中的名为 lastName 的属性,造成的结果就是最后不能输出属性名为 lastName 字段的值

格式化操作

可以在定义的属性上面使用 @NumberFormat@DateTimeFormat 注解进行数据格式化

对数字进行格式化

@NumberFormat(pattern="###.#")
private Float salary;

对日期进行格式化

@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;

最后别忘了加上注解

<mvc:annotation-driven/>


五、参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335