SpringMVC学习笔记(一)
内容包括:
-
SpringMVC执行流程
-
注解开发
@controller
,@RequestMapping
-
请求参数处理
-
数据输出处理
-
视图解析
参考视频: B站 尚硅谷雷丰阳大神的Spring、Spring MVC、MyBatis课程
1. SpringMVC概述
MVC:
-
Model(模型): 数据模型,提供要展示的数据,:Value Object(数据Dao) 和 服务层(行为Service),提供数据和业务。
-
View(视图): 负责进行模型的展示,即用户界面
-
Controller(控制器): 调度员,接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。
1、发请求获取到请求需要的数据
2、直接调用业务逻辑处理这些数据(转service)
3、处理结果转发或者重定向
分层为了解耦,解耦为了维护方便和分工合作
POJO:Plain Ordinary Java Object 简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。
SpringMVC的特点:
-
Spring为展现层提供的基于MVC设计理念的Web框架
-
SpirngMVC通过一套MVC注解,让POJO成为处理请求的控制器,而无须实现任何接口
-
支持REST风格的URL请求
-
采用了松散耦合可拔插组件结构,扩展性和灵活性
-
多了个前端控制器(分诊台),
2. HelloWorld
1) 导入依赖
spring-webmvc的maven依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency> 12345
2) 配置web.xml , 注册DispatcherServlet
DispatcherServlet
:前端控制器,负责请求分发。
要绑定Spring的配置文件
<?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"> <!--注册DispatcherServlet,请求分发器(前端控制器)--> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--绑定Spring配置文件--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-config.xml</param-value> </init-param> <!--启动级别为1,即服务器启动后就启动--> <!--值越小优先级越高,越先创建对象--> <load-on-startup>1</load-on-startup> </servlet> <!-- / 拦截所有的请求;(不包括.jsp)--> <!-- /* 拦截所有的请求;(包括.jsp,一旦拦截jsp页面就不能显示了)--> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> 12345678910111213141516171819202122232425262728
3) Spring配置文件
Spring的配置文件Springmvc-config.xml。
-
开启了包扫描,让指定包下的注解生效,由IOC容器统一管理
-
配置了视图解析器
InternalResourceViewResolver
,这里可以设置前缀和后缀,拼接视图名字
<?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"> <!--开启包扫描,让指定包下的注解生效,由IOC容器统一管理--> <context:component-scan base-package="com.xiao.controller"/> <!--配置视图解析器,拼接视图名字,找到对应的视图--> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--前缀--> <property name="prefix" value="/WEB-INF/page/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean> </beans> 123456789101112131415161718192021
4) 编写controller层
HelloController类:
-
@Controller:告诉Spirng这是一个控制器,交给IOC容器管 理
-
@RequestMapping("/hello01"):/ 表示项目地址,当请求项目中的hello01时,返回一个/WEB-INF/page/success.jsp页面给前端
@Controller public class HelloController { @RequestMapping("/hello01") public String toSuccess(){ System.out.println("请求成功页面"); return "success"; } @RequestMapping("/hello02") public String toError() { System.out.println("请求错误页面"); return "error"; } } 1234567891011121314
5) 编写跳转的jsp页面
项目首页 index.jsp,两个超链接,分别发出hello01和hello02的请求
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> <a href="hello01">点这里去成功页面</a> <a href="hello02">点这里去失败页面</a> </body> </html> 123456789101112
成功页面success.jsp和失败页面error.jsp,要注意文件的路径/WEB-INF/page/…jsp,与上面的保持一致
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>成功页面</title> </head> <body> <h1>这里是成功页面</h1> </body> </html> 1234567891011 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>错误页面</title> </head> <h1>这里是错误页面</h1> <body> </body> </html> 1234567891011
6) 访问
启动项目:
点击去成功页面,可以看到发出了/hello01请求,页面转发到/WEB-INF/page/success.jsp,控制台输出了请求成功页面。
3. 实现细节
3.1 运行流程
-
客户端点击链接发送请求:http://localhost:8080/hello01;
-
来到tomcat服务器;
-
SpringMVC的前端控制器收到所有请求;
-
看请求地址和@RequestMapping标注的哪个匹配,来找到底使用哪个类的哪个方法来处理;
-
前端控制器找到目标处理器类和目标方法,直接利用反射执行目标方法;
-
方法执行完后有一个返回值,SpringMVC认为这个返回值就是要去的页面地址;
-
拿到方法返回值后,视图解析器进行拼串得到完整的页面地址
-
得到页面地址,前端控制器帮我们转发到页面
3.2 @RequestMapping
01 标注在方法上
告诉SpringMVC这个方法用来处理什么请求。
@RequestMapping("/hello01")
中的 /
可以省略,就是默认从当前项目下开始。
02 标注在类上
表示为当前类中的所有方法的请求地址,指定一个基准路径。toSuccess()方法处理的请求路径是/haha/hello01
。
@Controller @RequestMapping("/haha") public class HelloController { @RequestMapping(value = "/hello01") public String toSuccess(){ System.out.println("请求成功页面"); return "success"; } }
默认位置是 /WEB-INF/xxx-servlet.xml,其中xxx是自己在web.xml文件中配置的servlet-name属性。
当然也可以手动指定文件位置。
WebContent/WEB-INF/DispatcherServlet-servlet.xml,前端控制器
03 规定请求方式 method
method属性规定请求方式,默认是所求请求方式都行。method = RequestMethod.GET,只接受get请求,默认是全接收;
method = RequestMethod.POST:
点链接是get方式;请求方式不对就会报错:405 客户端错误(4XX)
如果方法不匹配会报:HTTP Status 405 错误 – 方法不被允许
@RequestMapping(value = "/hello01",method = RequestMethod.GET) public String toSuccess(){ System.out.println("请求成功页面"); return "success"; }
组合用法
-
@GetMapping 等价于 @RequestMapping(method =RequestMethod.GET)
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
04 规定请求参数 params
params属性规定请求参数。会造成错误:HTTP Status 400 – 错误的请求
不携带该参数,表示参数值为null;携带了不给值表示参数值是
带上参数:/hello03?username=111; ? 表示带参
//必须携带username参数 @RequestMapping(value = "/hello03",params ={"username"}) //必须不携带username参数 @RequestMapping(value = "/hello03",params ={"!username"}) //必须携带username参数,且值必须为123 @RequestMapping(value = "/hello03",params ={"username=123"}) //username参数值必须不为123,不携带或者携带了不是123都行 @RequestMapping(value = "/hello03",params ={"username=!123"}) //username!=123,pwd,不能 有age @RequestMapping(value = "/hello03",params ={"username=!123","pwd","!age")
05 规定请求头
headers属性规定请求头。其中User-Agent:浏览器信息
谷歌浏览器:User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.3
06 Ant风格URL
URL地址可以写模糊的通配符,模糊和精确多个匹配情况下精确优先。
?:替代任意一个字符
@RequestMapping( "/hello0?") / 1
*:替代任意多个字符或一层路径
@RequestMapping( "/hello0*") //任意多个字符 @RequestMapping( "/a/*/hello01") //一层路径 12
**:替代任意多层路径
@RequestMapping( "/a/*/*/hello01") //任意多层路径 1
3.3 Spring配置文件的默认位置
默认位置是 /WEB-INF/xxx-servlet.xml,其中xxx是自己在web.xml文件中配置的servlet-name属性。
当然也可以手动指定文件位置。
3.4 url-pattern
/ 拦截所有的请求,不拦截jsp
/* 拦截所有的请求,包括*.jsp,一旦拦截jsp页面就不能显示了。. jsp是tomcat处理的事情
看Tomcat的配置文件web.xml中,有DefaultServlet和JspServlet,
-
DefaultServlet是Tomcat中处理静态资源的,Tomcat会在服务器下找到这个资源并返回。如果我们自己配置
url-pattern=/
,相当于禁用了Tomcat服务器中的DefaultServlet,这样如果请求静态资源,就会去找前端控制器找@RequestMapping,这样静态资源就不能访问了。解决办法:<!-- 告诉Spring MVC自己映射的请求就自己处理,不能处理的请求直接交给tomcat --> <mvc:default-servlet-handler /> <!--开启MVC注解驱动模式,保证动态请求和静态请求都能访问--> <mvc:annotation-driven/> 1234
-
JspServlet,保证了jsp可以正常访问
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet> <servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.jspx</url-pattern> </servlet-mapping> 123456789101112131415161718192021222324252627282930313233343536373839
4. REST风格
4.1 概述
REST就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。其强调HTTP应当以资源为中心,并且规范了URI的风格;规范了HTTP请求动作(GET/PUT/POST/DELETE/HEAD/OPTIONS)的使用,具有对应的语义。
-
资源(Resource):网络上的一个实体,每种资源对应一个特定的URI,即URI为每个资源的独一无二的识别符;
-
表现层(Representation):把资源具体呈现出来的形式,叫做它的表现层。比如txt、HTML、XML、JSON格式等;
-
状态转化(State Transfer):每发出一个请求,就代表一次客户端和服务器的一次交互过程。GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
在参数上使用 @PathVariable 注解,可以获取到请求路径上的值,也可以写多个
@RequestMapping(value = "/hello04/username/{id}") public String test2(@PathVariable("id") int id){ System.out.println(id); return "success"; } 12345
4.2 页面上发出PUT请求
对一个资源的增删改查用请求方式来区分:
资源名/资源标识符
-
/book/1 GET:查询1号图书
-
/book/1 DELETE:删除1号图书
-
/book/1 PUT:修改1号图书
-
/book POST:新增图书
页面上只能发出GET请求和POST请求。将POST请求转化为put或者delete请求的步骤:
-
把前端发送方式改为post 。
-
在web.xml中配置一个filter:HiddenHttpMethodFilter过滤器
-
必须携带一个键值对,key=_method, value=put或者delete
<!--这个过滤器的作用 :就是将post请求转化为put或者delete请求--> <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> 123456789 <form action="hello03" method="post"> <input type="hidden" name="_method" value="delete"> <input type="submit" name="提交"> </form> 1234
高版本Tomcat会出现问题:JSPs only permit GET POST or HEAD,在页面上加上异常处理即可
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %> 1
5 请求参数处理
5.1 传入参数
1. 如果提交的参数名称和处理方法的参数名一致,则无需处理,直接使用
提交数据 : http://localhost:8080/hello05?username=zhangsan,控制台会输出zhangsan
@RequestMapping("/hello05") public String test03(String username) { System.out.println(username); return "success"; }
2. 提交的参数名称和处理方法的参数名不一致,使用@RequestParam注解
注解@RequestParam
可以获取请求参数,默认必须携带该参数,也可以指定required=false
,和没携带情况下的默认值defaultValue
@RequestMapping("/hello05") public String test03(@RequestParam(value = "username",required = false, defaultValue ="hehe" ) String name) { System.out.println(name); return "success"; } 12345
还有另外两个注解:
-
@RequestHeader
:获取请求头中的信息,比如User-Agent:浏览器信息@RequestMapping("/hello05") public String test03(@RequestHeader("User-Agent" ) String name) { System.out.println(name); return "success"; }
-
@CookieValue
:获取某个cookie的值@RequestMapping("/hello05") public String test03(@CookieValue("JSESSIONID" ) String name) { System.out.println(name); return "success"; }
5.2 传入一个对象
传入POJO,SpringMVC会自动封装,提交的表单域参数必须和对象的属性名一致,否则就是null,请求没有携带的字段,值也会是null。同时也还可以级联封装。
新建两个对象User和Address:
public class User { private String username; private Integer age; private Address address; //.... } 123456 public class Address { private String name; private Integer num; //.... } 12345
前端请求:
<form action="hello06" method="post"> 姓名: <input type="text" name="username"> <br> 年龄: <input type="text" name="age"><br> 地址名:<input type="text" name="address.name"><br> 地址编号:<input type="text" name="address.num"><br> <input type="submit" name="提交"> </form> 1234567
后端通过对象名也能拿到对象的值,没有对应的值则为null
@RequestMapping("/hello06") public String test03(User user) { System.out.println(user); return "success"; } 12345
5.3 乱码问题
一定要放在在其他Filter前面。
<filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 123456789101112
5.4 传入原生ServletAPI
处理方法还可以传入原生的ServletAPI:
@RequestMapping("/hello07") public String test04(HttpServletRequest request, HttpSession session) { session.setAttribute("sessionParam","我是session域中的值"); request.setAttribute("reqParam","我是request域中的值"); return "success"; } 123456
通过EL表达式获取到值,${requestScope.reqParam}
:
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %> <html> <head> <title>成功页面</title> </head> <body> <h1>这里是成功页面</h1> ${requestScope.reqParam} ${sessionScope.sessionParam} </body> </html> 12345678910111213
6. 数据输出
6.1 Map、Model、ModerMap
实际上都是调用的 BindingAwareModelMap(隐含模型),将数据放在请求域(requestScope)中进行转发,用EL表达式可以取出对应的值。
@RequestMapping("/hello01") public String test01 (Map<String,Object> map){ map.put("msg","HelloWorld!"); return "success"; } 12345 <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> pageScope: ${pageScope.msg} requestScope : ${requestScope.msg} sessionScope: ${sessionScope.msg} applicationScope: ${applicationScope.msg} </body> </html> 1234567891011121314151617
【补充】jsp的4个作用域 pageScope、requestScope、sessionScope、applicationScope的区别:
-
page指当前页面有效。在一个jsp页面里有效
-
request 指在一次请求的全过程中有效,即从http请求到服务器处理结束,返回响应的整个过程,存放在HttpServletRequest对象中。在这个过程中可以使用forward方式跳转多个jsp。在这些页面里都可以使用这个变量。
-
Session是用户全局变量,在整个会话期间都有效。只要页面不关闭就一直有效(或者直到用户一直未活动导致会话过期,默认session过期时间为30分钟,或调用HttpSession的invalidate()方法)。存放在HttpSession对象中
-
application是程序全局变量,对每个用户每个页面都有效。存放在ServletContext对象中。它的存活时间是最长的,如果不进行手工删除,它们就一直可以使用
6.2 ModerAndView
返回一个模型视图对象ModerAndView, 既包含视图信息(页面地址),也包含模型数据(给页面带的数据)
@RequestMapping("/hello04") public ModelAndView test04 (){ //新建一个模型视图对象,也可以直接传入名字 ModelAndView mv = new ModelAndView(); //封装要显示到视图中的数据 //相当于req.setAttribute("msg",HelloWorld!); mv.addObject("msg","HelloWorld!"); //设置视图的名字,相当于之前的return "success"; mv.setViewName("success"); return mv; } 1234567891011
6.3 @SessionAttributes
给Session域中携带数据使用注解@SessionAttributes
,只能标在类上,value属性指定key,type可以指定保存类型。这个注解会引发异常一般不用,就用原生API
@SessionAttributes(value = "msg")
:表示给BindingAwareModelMap中保存key为msg的数据时,在session中也保存一份;
@SessionAttributes(types = {String.class})
:表示只要保存String类型的数据时,给session中也放一份。
//表示给BindingAwareModelMap中保存key为msg的数据时,在session中也保存一份 @SessionAttributes(value = "msg") @Controller public class outputController { @RequestMapping("/hello01") public String test01 (Map<String,Object> map){ map.put("msg","HelloWorld!"); return "success"; } }
6.4 @ModelAttribute
方法入参标注该注解后,入参的对象就会放到数据模型中,会提前于控制方法先执行,并发方法允许的结果放在隐含模型中。
处理这样的场景:
前端传来数据,SpringMVC自动封装成对象,实际上是创建了一个对象,每个属性都有默认值,然后将请求参数中对应是属性设置过来,但是如果没有的值将会是null,如果拿着这个数据去更新数据库,会造成其他字段也变为null。因此希望使用@ModelAttribute
,会在目标方法执行前先做一些处理
@ModelAttribute public void myModelAttribute(ModelMap modelMap){ System.out.println("modelAttribute方法执行了"); //提前做一些处理 User user = new User("zhangsan",20); //保存一个数据到BindingAwareModelMap中,目标方法可以从中取出来 modelMap.addAttribute("user",user); } @RequestMapping("/hello05") public void test05(@ModelAttribute("user") User user){ System.out.println("目标方法执行了"); //在参数上加上@ModelAttribute注解,可以拿到提前存入的数据 System.out.println(user); }
6.5 @ResponseBody
在控制器类中,在方法上使用@ResponseBody注解可以不走视图解析器,如果返回值是字符串,那么直接将字符串写到客户端;如果是一个对象,会将对象转化为JSON串,然后写到客户端。
或者在类上加 @RestController注解,可以让类中的所有方法都不走视图解析器,直接返回JSON字符串
7. 再谈SpringMVC执行流程
7.1 前端控制器DisatcherServlet
7.2 SpringMVC执行流程
-
用户发出请求,DispatcherServlet接收请求并拦截请求。
-
调用doDispatch()方法进行处理:
1)getHandler():根据当前请求地址,在HandlerMapping(处理器映射器)中找到能处理这个请求的目标处理器类(处理器);
2)getHandlerAdapter():根据当前处理器类找到当前类的HandlerAdapter(处理器适配器)
3)使用刚才获取到的适配器(AnnotationMethodHandlerAdapter)执行目标方法;
4)目标方法执行后,会返回一个ModerAndView对象
5)根据ModerAndView的信息转发到具体页面,并可以在请求域中取出ModerAndView中的模型数据
HandlerMapping为处理器映射器,保存了每一个处理器能处理哪些请求的映射信息,handlerMap
HandlerAdapter为处理器适配器,能解析注解方法的适配器,其按照特定的规则去执行Handler
7.3 SpringMVC的九大组件
-
multipartResolver:文件上传解析器
-
localeResolver:区域信息解析器,和国际化有关
-
themeResolver:主题解析器
-
handlerMappings:handler的映射器
-
handlerAdapters:handler的适配器
-
handlerExceptionResolvers:异常解析功能
-
viewNameTranslator:请求到视图名的转换器
-
flashMapManager:SpringMVC中允许重定向携带数据的功能
-
viewResolvers:视图解析器
@Nullable private MultipartResolver multipartResolver; @Nullable private LocaleResolver localeResolver; @Nullable private ThemeResolver themeResolver; @Nullable private List<HandlerMapping> handlerMappings; @Nullable private List<HandlerAdapter> handlerAdapters; @Nullable private List<HandlerExceptionResolver> handlerExceptionResolvers; @Nullable private RequestToViewNameTranslator viewNameTranslator; @Nullable private FlashMapManager flashMapManager; @Nullable private List<ViewResolver> viewResolvers; 123456789101112131415161718
8. 视图解析
通过SpringMVC来实现转发和重定向。
-
直接 return “success”,会走视图解析器进行拼串
-
转发:return “forward:/succes.jsp”;直接写绝对路径,/表示当前项目下,不走视图解析器
-
重定向:return “redirect:/success.jsp”;不走视图解析器
@Controller public class ResultSpringMVC { @RequestMapping("/hello01") public String test1(){ //转发 //会走视图解析器 return "success"; } @RequestMapping("/hello02") public String test2(){ //转发二 //不走视图解析器 return "forward:/success.jsp"; } @RequestMapping("/hello03") public String test3(){ //重定向 //不走视图解析器 return "redirect:/success.jsp"; } }
使用原生的ServletAPI时要注意,/路径需要加上项目名才能成功
@RequestMapping("/result/t2") public void test2(HttpServletRequest req, HttpServletResponse resp) throwsIOException { //重定向 resp.sendRedirect("/index.jsp"); } @RequestMapping("/result/t3") public void test3(HttpServletRequest req, HttpServletResponse resp) throwsException { //转发 req.setAttribute("msg","/result/t3"); req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,resp); }
mvc:view-controller
:直接将请求映射到某个页面,不需要写方法了:
<mvc:view-controller path="/toLogin" view-name="login"/> <!--开启MVC注解驱动模式--> <mvc:annotation-driven/>