SSM-回炉重造-Spring MVC
Spring MVC: Spring的web模块,用于简化Java web开发
Spring MVC通过一套MVC注解,让pojo成为处理请求的控制器,无需实现任何接口
HelloWorld
1. 导包
web工程,导包需要将包放至webapp目录的指定路径下,如下图
或者使用maven导入相关依赖(推荐)
2. 配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/springmvc.xml</param-value>
</init-param>
<!-- 启动时自动加载,值越小优先级越高,越先创建 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern> <!-- 拦截所有请求 -->
<!--
/*和/都会拦截所有请求,但是/不会拦截jsp (jsp和servlet不是静态资源)
-->
</servlet-mapping>
</web-app>
contextConfigLocation
的默认文件是/WEB-INF/xxx-servlet.xml
,其中xxx为DispatcherServlet
这个类的名字,即上方的servlet-name
标签的值- /和/*都会拦截静态资源,tomcat默认的
DefaultServlet
用于处理静态资源,而由于我们写的dispatcherServlet
覆盖了tomcat的,所以/
和/*
都会拦截静态资源 /*
不管是否重写,都是拦截所有的资源,通常写/
,可以可迎合rest风格
3. 创建springmvc.xml
在resource目录下,创建 spring 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.oylong.controller"/>
</beans>
4. 写页面
5. 写控制器
package com.oylong.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @ProjectName: springmvc
* @Description:
* @Author: OyLong
* @Date: 2020/9/20 13:24
*/
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(){
System.out.println("收到请求");
return "/WEB-INF/page/hello.jsp";
}
}
将@Controller
注解标注在类上,表示这是一个控制器,使用@RequestMapping
注解表示在指定url访问时,使用这个方法去处理
配置视图解析器
由于每次都要写完整路径,所以为了简化,可以在spring配置文件中,配置一个视图解析器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.oylong.controller"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
视图解析器简化后的controller
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(){
System.out.println("收到请求");
return "hello";
}
}
视图解析器将自动拼接相应的路径
@RequestMapping
注解
标注在方法上
表示某一个请求的具体路径
标注在类上
表示该类下所有的请求的前缀
@Controller
@RequestMapping("/test")
public class HelloController {
@RequestMapping("/hello")
public String hello(){
System.out.println("收到请求");
return "hello";
}
}
如上面的请求,由于类上标注了/test
,所以访问hello
就必须使用/test/hello
method属性
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
method
属性的值的定义如上所示,我们可以用method
属性来限定请求方式,假如我们指定了GET,那么该对应的url只能用指定的方式请求,才会被接受,而如果留空,即不指定方法,则任何请求方式都能被接收
@Controller
public class HelloController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(){
System.out.println("收到请求");
return "hello";
}
}
params属性
params
属性用来规定请求的参数,如果指定了参数,在请求时没有传参的话,就会异常
@RequestMapping(value = "/hello", params = {"haha"})
public String hello(){
System.out.println("收到请求");
return "hello";
}
上面这个方法,若想请求,则必须要在hello后面带上参数,如/hello?haha
,同时规定多个,可以直接在后面加逗号补充即可
同时params属性还支持一些特殊表达式,比如:
- params = {"!haha"}表示不能带有haha参数.
- params = {"haha=123"}表示必须要带有参数为haha,且haha的值为123才能访问
headers属性
规定请求头,类似params属性,也可以使用一些特殊表达式,使用方式也类似params属性
consumes属性
规定接收内容的类型,即请求头中的Content-Type
produces属性
告诉浏览器返回内容的类型,给响应头加上Content-Type
模糊匹配
?
: 替代任意的1个字符*
: 替代任意多个字符或一层路径**
: 替代多层路径
@RequestParam
注解
SpringMVC中,获取请求参数时,可以直接使用与参数同名的变量即可自动获取,如下:
@Controller
public class HelloController {
@RequestMapping(value = "/hello/")
public String hello(String id){
System.out.println("收到请求, id:"+id);
return "hello";
}
}
访问时:
运行结果:
同时也可以指定参数名字,如下:
@Controller
public class HelloController {
@RequestMapping(value = "/hello")
public String hello(@RequestParam(value = "id") String num) {
System.out.println("收到请求, num:" + num);
return "hello";
}
}
运行结果:
同时,标注了@RequestParam
注解的属性,默认是必须要有值的,否则无法请求,除非单独设置其required
属性,如下:
@RequestParam(value = "id", required = false) String num
同时还能通过其属性defaultValue
来设置默认值
@RequestHeader
注解
类似@RequestParam
注解,只要在形参列表中添加请求头的key,即可获取指定的请求头的值
@Controller
public class HelloController {
@RequestMapping(value = "/hello")
public String hello(@RequestHeader(value = "User-Agent") String userAgent) {
System.out.println("请求头:" + userAgent);
return "hello";
}
}
运行结果如下:
同样拥有required
属性和defaultValue
属性
@CookieValue
注解
类似@RequestHeader
注解,用于通过key获取请求中附带的cookie的值
实体类的自动赋值
如果传入多个参数,且在spring mvc中使用实体类接收,spring mvc将会通过对应变量名的方式去自动封装实体类.
对于复杂属性,可以级联封装,通过类似car.engine,car.color
来封装即可
乱码问题
1. 请求乱码
在web.xml
中配置CharacterEncodingFilter
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!--解决post乱码-->
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<!--解决response乱码-->
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
字符编码的CharacterEncodingFilter
通常需要配置在其他的Filter
之前,否则可能无法解决乱码问题
2. 响应乱码
设置返回体
response.setContentType("text/html;charset=utf-8");
输出数据到页面
1. 在方法处传入Map,Model,ModelMap
在方法中加入以上类型的参数,如下
@RequestMapping("/output01")
public String test(Map<String, Object> map) {
map.put("msg", "hello");
return "output";
}
Model:
@Controller
public class DataController {
@RequestMapping("/output01")
public String test(Model model) {
model.addAttribute("msg", "哈哈");
return "output";
}
}
ModelMap:
@Controller
public class DataController {
@RequestMapping("/output01")
public String test(ModelMap modelMap) {
modelMap.addAttribute("msg", "哈哈");
return "output";
}
}
以上三种方法,在JSP页面中都可以通过request
作用域取值,如下
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>output test</title>
</head>
<body>
request: ${requestScope.msg}
</body>
</html>
运行结果:
这三个类,最终实现的都是一个类BindingAwareModelMap
2. ModelAndView
如下代码,通过使用ModelAndView
类去进行数据传递与页面返回,用法如下:
@RequestMapping("/output02")
public ModelAndView test01() {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "haha");
mv.setViewName("output");
return mv;
}
- 通过
addObject
方法添加属性 - 通过
setViewName
设置需要返回的页面 - 同样是在
request
域中可以获取到传递的数据
3.@SessionAttributes
注解
- 这个注解只能标注在类上
- 注解的值表示会将注解指定的
value
在保存在BindingAwareModelMap
的同时,也会保存在session
中去 - 注解的值也可以存入一个数组
- 可以使用
type
属性指定类型,如果不符合相应的类型,将无法保存 - 不推荐使用,若实在要添加数据至session,建议使用原生api
@SessionAttributes(value = "msg")
@Controller
public class DataController {
@RequestMapping("/output02")
public ModelAndView test01() {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "haha");
mv.setViewName("output");
return mv;
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>output test</title>
</head>
<body>
<%--pageContext: ${pageScope.msg}
--%>
request: ${requestScope.msg}
session: ${sessionScope.msg}
<%--application: ${applicationScope.msg}
--%>
</body>
</html>
运行结果:
4.@ModelAttribute
注解
貌似使用不多,用到了再来作补充
视图解析器
如果配置视图解析器后,对应的页面不在指定的页面里面,而在上一层,如下:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/page/"/>
<property name="suffix" value=".jsp"/>
</bean>
此时配置在了/WEB-INF/page/
这个目录下,如果我们的hello.jsp
页面在/WEB-INF/
下,这时候直接返回页面名字则会找不到
解决办法:
- 使用相对路径,
Controller
返回时,使用../
这样的格式 使用
forward
进行转发@RequestMapping(value = "/hello") public String hello() { return "forward:/hello.jsp"; //一定要加上 '/' 否则就是相对路径 }
使用
redirect
重定向@RequestMapping(value = "/hello") public String hello() { return "redirect:/hello.jsp"; }
区分请求和静态资源
SpringMVC中如果dispatcherServlet
指定的是所有url,那么静态资源也会无法访问,解决方法如下:
在springmvc的配置文件中添加下面两个注解:
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
JSR303 数据校验
1.使用方式
- 在java bean的属性上添加相应的注解
- 在Spring MVC封装对象时,添加
@Valid
表示改对象需要被校验
2.校验结果
在封装的对象后面添加BindingResult属性的变量即可获取相应的校验结果的封装
3. 校验注解
Hibernate Validator 附加的 constraint
Json格式的数据
1. 返回Json格式的数据
只需要在原来的Controller
方法的基础上,添加一个@ResponseBody即可
@Controller
public class DataController {
@ResponseBody
@RequestMapping("/output03")
public String test02(){
return "hello";
}
...
}
2. 接收Json格式的数据
在Controller
方法的参数上加上@RequestBody
注解即可
@ResponseBody
@RequestMapping("/output03")
public String test02(@RequestBody Teacher teacher){
return "hello";
}
同时接收和返回数据还可以使用带泛型的HttpEntity
和ResponseEntity
进行更复杂的操作
拦截器
SpringMVC提供了拦截器机制,能在目标方法之前或者之后进行一些处理工作
拦截器的preHandle
处理方法返回true
表示放行,否则不放行,不放行则不会往后面继续执行
拦截器接口:HandlerInterceptor
此接口,有三个方法:
- preHandle: 目标方法运行前调用
- postHandle: 目标方法运行后调用
- afterCompletion: 整个请求完成后,调用
使用方法
1. 实现接口
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("处理前");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("处理后");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("请求后");
}
}
2. 配置文件
在Spring MVC的配置文件中配置拦截器,如下
<mvc:interceptors>
<bean class="com.oylong.interceptor.MyInterceptor"></bean>
</mvc:interceptors>
这种不指定路径的配置方式,表示将所有的请求都进行拦截
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/output05"/>
<bean class="com.oylong.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
这种表示只拦截指定的请求url
注:只要放行了,一定会执行afterCompletion方法
拦截器与过滤器
- filter更通用
- interceptor可以配合Spring ioc容器使用,更加强大
异常处理
SpringMVC提供了一套异常处理的机制,在Controller
中出现异常时,可以通过@ExceptionHandler
去捕获指定的异常类型,如下:
@ExceptionHandler(NullPointerException.class)
public String exceptionHandler(Exception exception){
return exception.getMessage();
}
上面的写法是将异常的处理写在某个Controller
里面,也可以用一个@ControllerAdvice
注解了来处理所有的Controller
的异常,如下:
@ControllerAdvice
public class MyExceptionController {
@ExceptionHandler(NullPointerException.class)
public String nullPointerException(Exception e){
return e.getMessage();
}
}
更改返回状态码以及返回信息
可以通过注解@ResponseStatus
来指定返回的状态,需要注意的是,不太建议把这个注解标注在正常的Controller方法上,而是标注在异常处理的方法上,如下:
@ResponseStatus(reason = "错误了", value = HttpStatus.SERVICE_UNAVAILABLE)
@ExceptionHandler(NullPointerException.class)
public String exceptionHandler(Exception exception){
return exception.getMessage();
}
标注此注解后,会将响应的原因和状态码进行一个更改,如下图
也可以标注在自定义异常上,那么抛出相应的自定义异常时就会将响应的消息和响应的状态码进行更改