Loading... # SSM-回炉重造-Spring MVC ---- Spring MVC: Spring的web模块,用于简化Java web开发 Spring MVC通过一套MVC注解,让pojo成为处理请求的控制器,无需实现任何接口 --- ## HelloWorld ### 1. 导包 web工程,导包需要将包放至webapp目录的指定路径下,如下图 <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20200920140922046.png" alt="image-20200920140922046" style="zoom:50%;" /> **或者使用maven导入相关依赖(推荐)** ### 2. 配置web.xml ````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 <?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. 写控制器 ````java 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 <?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 ````java @Controller public class HelloController { @RequestMapping("/hello") public String hello(){ System.out.println("收到请求"); return "hello"; } } ```` 视图解析器将自动拼接相应的路径 --- ## `@RequestMapping`注解 - 标注在方法上 表示某一个请求的具体路径 - 标注在类上 表示该类下所有的请求的前缀 ````java @Controller @RequestMapping("/test") public class HelloController { @RequestMapping("/hello") public String hello(){ System.out.println("收到请求"); return "hello"; } } ```` 如上面的请求,由于类上标注了`/test`,所以访问`hello`就必须使用`/test/hello` ### method属性 ````java public enum RequestMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE } ```` `method`属性的值的定义如上所示,我们可以用`method`属性来限定请求方式,假如我们指定了*GET*,那么该对应的url只能用指定的方式请求,才会被接受,而如果留空,即不指定方法,则任何请求方式都能被接收 ````java @Controller public class HelloController { @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello(){ System.out.println("收到请求"); return "hello"; } } ```` ### params属性 `params`属性用来规定请求的参数,如果指定了参数,在请求时没有传参的话,就会异常 ````java @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中,获取请求参数时,可以直接使用与参数同名的变量即可自动获取,如下: ````java @Controller public class HelloController { @RequestMapping(value = "/hello/") public String hello(String id){ System.out.println("收到请求, id:"+id); return "hello"; } } ```` 访问时: <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20200920211908703.png" alt="image-20200920211908703" style="zoom:50%;" /> 运行结果: <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20200920211933450.png" alt="image-20200920211933450" style="zoom:67%;" /> 同时也可以指定参数名字,如下: ````java @Controller public class HelloController { @RequestMapping(value = "/hello") public String hello(@RequestParam(value = "id") String num) { System.out.println("收到请求, num:" + num); return "hello"; } } ```` 运行结果: <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20200920212131513.png" alt="image-20200920212131513" style="zoom:67%;" /> **同时,标注了`@RequestParam`注解的属性,默认是必须要有值的,否则无法请求**,除非单独设置其`required`属性,如下: ````java @RequestParam(value = "id", required = false) String num ```` 同时还能通过其属性`defaultValue`来设置默认值 --- ## `@RequestHeader`注解 类似`@RequestParam`注解,只要在形参列表中添加请求头的key,即可获取指定的请求头的值 ````java @Controller public class HelloController { @RequestMapping(value = "/hello") public String hello(@RequestHeader(value = "User-Agent") String userAgent) { System.out.println("请求头:" + userAgent); return "hello"; } } ```` 运行结果如下: <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20200920213027155.png" alt="image-20200920213027155" style="zoom:50%;" /> 同样拥有`required`属性和`defaultValue`属性 --- ## `@CookieValue`注解 类似`@RequestHeader`注解,用于通过key获取请求中附带的cookie的值 --- ## 实体类的自动赋值 如果传入多个参数,且在spring mvc中使用实体类接收,spring mvc将会通过对应变量名的方式去自动封装实体类. 对于复杂属性,可以级联封装,通过类似`car.engine,car.color`来封装即可 --- ## 乱码问题 ### 1. 请求乱码 在`web.xml`中配置`CharacterEncodingFilter` ````xml <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. 响应乱码 设置返回体 ````java response.setContentType("text/html;charset=utf-8"); ```` --- ## 输出数据到页面 ### 1. 在方法处传入Map,Model,ModelMap 在方法中加入以上类型的参数,如下 ````java @RequestMapping("/output01") public String test(Map<String, Object> map) { map.put("msg", "hello"); return "output"; } ```` Model: ````java @Controller public class DataController { @RequestMapping("/output01") public String test(Model model) { model.addAttribute("msg", "哈哈"); return "output"; } } ```` ModelMap: ````java @Controller public class DataController { @RequestMapping("/output01") public String test(ModelMap modelMap) { modelMap.addAttribute("msg", "哈哈"); return "output"; } } ```` 以上三种方法,在*JSP*页面中都可以通过`request`作用域取值,如下 ````html <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>output test</title> </head> <body> request: ${requestScope.msg} <br/> </body> </html> ```` 运行结果: <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20200921201914811.png" alt="image-20200921201914811" style="zoom:50%;" /> 这三个类,最终实现的都是一个类`BindingAwareModelMap` <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20200921202423266.png" alt="image-20200921202423266" style="zoom:50%;" /> <img src="C:\Users\OyLong\AppData\Roaming\Typora\typora-user-images\image-20200921202433860.png" alt="image-20200921202433860" style="zoom:50%;" /> --- ### 2. ModelAndView 如下代码,通过使用`ModelAndView`类去进行数据传递与页面返回,用法如下: ````java @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** ````java @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; } } ```` ````html <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>output test</title> </head> <body> <%--pageContext: ${pageScope.msg} <br/>--%> request: ${requestScope.msg} <br/> session: ${sessionScope.msg} <br/> <%--application: ${applicationScope.msg} <br/>--%> </body> </html> ```` 运行结果: <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20200921204340741.png" alt="image-20200921204340741" style="zoom:50%;" /> --- ### 4.`@ModelAttribute`注解 **貌似使用不多,用到了再来作补充** --- ## 视图解析器 如果配置视图解析器后,对应的页面不在指定的页面里面,而在上一层,如下: ````xml <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/`下,这时候直接返回页面名字则会找不到 解决办法: 1. 使用相对路径,`Controller`返回时,使用`../`这样的格式 2. 使用`forward`进行转发 ````java @RequestMapping(value = "/hello") public String hello() { return "forward:/hello.jsp"; //一定要加上 '/' 否则就是相对路径 } ```` 3. 使用`redirect`重定向 ````java @RequestMapping(value = "/hello") public String hello() { return "redirect:/hello.jsp"; } ```` --- ## 区分请求和静态资源 SpringMVC中如果`dispatcherServlet`指定的是所有*url*,那么静态资源也会无法访问,解决方法如下: 在springmvc的配置文件中添加下面两个注解: ````xml <mvc:default-servlet-handler/> <mvc:annotation-driven/> ```` ## JSR303 数据校验 ### 1.使用方式 1. 在java bean的属性上添加相应的注解 2. 在Spring MVC封装对象时,添加`@Valid`表示改对象需要被校验 ### 2.校验结果 在封装的对象后面添加BindingResult属性的变量即可获取相应的校验结果的封装 ### 3. 校验注解 <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/3145530-8ae74d19e6c65b4c.webp" alt="3145530-8ae74d19e6c65b4c.webp (654×454)" style="zoom:80%;" /> **Hibernate Validator 附加的 constraint**  --- ## Json格式的数据 ### 1. 返回Json格式的数据 只需要在原来的`Controller`方法的基础上,添加一个`@ResponseBody即可` ````java @Controller public class DataController { @ResponseBody @RequestMapping("/output03") public String test02(){ return "hello"; } ... } ```` ### 2. 接收Json格式的数据 在`Controller`方法的参数上加上`@RequestBody`注解即可 ````java @ResponseBody @RequestMapping("/output03") public String test02(@RequestBody Teacher teacher){ return "hello"; } ```` 同时接收和返回数据还可以使用带泛型的`HttpEntity`和`ResponseEntity`进行更复杂的操作 --- ## 拦截器 SpringMVC提供了拦截器机制,能在目标方法之前或者之后进行一些处理工作 拦截器的`preHandle`处理方法返回`true`表示放行,否则不放行,不放行则不会往后面继续执行 拦截器接口:`HandlerInterceptor` <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20201005102445775.png" alt="image-20201005102445775" style="zoom:67%;" /> 此接口,有三个方法: - preHandle: 目标方法运行前调用 - postHandle: 目标方法运行后调用 - afterCompletion: 整个请求完成后,调用 ### 使用方法 #### 1. 实现接口 ````java 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的配置文件中配置拦截器,如下 ````xml <mvc:interceptors> <bean class="com.oylong.interceptor.MyInterceptor"></bean> </mvc:interceptors> ```` 这种不指定路径的配置方式,表示将所有的请求都进行拦截 ````xml <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`去捕获指定的异常类型,如下: ````java @ExceptionHandler(NullPointerException.class) public String exceptionHandler(Exception exception){ return exception.getMessage(); } ```` 上面的写法是将异常的处理写在某个`Controller`里面,也可以用一个`@ControllerAdvice`注解了来处理所有的`Controller`的异常,如下: ````java @ControllerAdvice public class MyExceptionController { @ExceptionHandler(NullPointerException.class) public String nullPointerException(Exception e){ return e.getMessage(); } } ```` ### 更改返回状态码以及返回信息 可以通过注解`@ResponseStatus`来指定返回的状态,需要注意的是,**不太建议把这个注解标注在正常的Controller方法上**,而是标注在异常处理的方法上,如下: ````java @ResponseStatus(reason = "错误了", value = HttpStatus.SERVICE_UNAVAILABLE) @ExceptionHandler(NullPointerException.class) public String exceptionHandler(Exception exception){ return exception.getMessage(); } ```` 标注此注解后,会将响应的原因和状态码进行一个更改,如下图 <img src="https://oylong-blog-pic.oss-cn-shenzhen.aliyuncs.com/blog/img/image-20201005183159861.png" alt="image-20201005183159861" style="zoom: 50%;" /> 也可以标注在自定义异常上,那么抛出相应的自定义异常时就会将响应的消息和响应的状态码进行更改 最后修改:2020 年 10 月 05 日 08 : 08 PM © 允许规范转载 赞赏 如果觉得我的文章对你有用,请随意赞赏 ×Close 赞赏作者 扫一扫支付 支付宝支付 微信支付