image.png

Author:Shine

Version:9.0.2

一、SpringMVC


1.1 引言

java开源框架,Spring Framework的一个独立模块。

MVC框架,在项目中开辟MVC层次架构

对控制器中的功能 包装 简化 扩展践行工厂模式,功能架构在工厂之上

1.2 MVC架构

1.2.1 概念
名称 职责
Model 模型:即业务模型,负责完成业务中的数据通信处理,对应项目中的 service和dao
View 视图:渲染数据,生成页面。对应项目中的Jsp
Controller 控制器:直接对接请求,控制MVC流程,调度模型,选择视图。对应项目中的Servlet

1.2.2 好处
  • MVC是现下软件开发中的最流行的代码结构形态;
  • 人们根据负责的不同逻辑,将项目中的代码分成 M V C 3个层次;
  • 层次内部职责单一,层次之间耦合度低;
  • 符合低耦合 高内聚的设计理念。也实际有利于项目的长期维护。

二、开发流程


2.1 导入依赖

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-webmvc</artifactId>
  4. <version>5.1.6.RELEASE</version>
  5. </dependency>

2.2 配置核心(前端)控制器 struts2

https://struts.apache.org/getting-started/hello-world-using-struts2.html

作为一个MVC框架,首先要解决的是:如何能够收到请求!

所以MVC框架大都会设计一款前端控制器,选型在 Servlet 或 Filter两者之一,在框架最前沿率先工作,接收所有请求。

此控制器在接收到请求后,还会负责springMVC的核心的调度管理,所以既是前端又是核心。

  1. <servlet>
  2. <servlet-name>mvc</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. <!-- 局部参数:声明配置文件位置 -->
  5. <init-param>
  6. <param-name>contextConfigLocation</param-name>
  7. <param-value>classpath:mvc.xml</param-value>
  8. </init-param>
  9. <!-- Servlet启动时刻:可选 -->
  10. <load-on-startup>1</load-on-startup>
  11. </servlet>
  12. <servlet-mapping>
  13. <servlet-name>mvc</servlet-name>
  14. <url-pattern>/</url-pattern>
  15. </servlet-mapping>

2.3 后端控制器

等价于之前定义的Servlet

  1. @Controller //声明这是一个控制器
  2. @RequestMapping("/hello") //访问路径 ,等价于url-pattern
  3. public class HelloController {
  4. @RequestMapping("/test1") //访问路径
  5. public String hello1(){
  6. System.out.println("hello world");
  7. return "index"; // 跳转:/index.jsp
  8. }
  9. @RequestMapping("/test2") //访问路径
  10. public String hello2(){
  11. System.out.println("hello c9");
  12. return "views/users";// 跳转:/views/user.jsp
  13. }
  14. }

2.4 配置文件

默认名称:核心控制器名-servet.xml 默认位置:WEB-INF

随意名称:mvc.xml 随意位置:resources 但需要配置在核心控制器中

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:context="http://www.springframework.org/schema/context"
  3. xmlns:mvc="http://www.springframework.org/schema/mvc"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd
  9. http://www.springframework.org/schema/mvc
  10. http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  11. <!-- 告知springmvc 哪些包中 存在 被注解的类 -->
  12. <context:component-scan base-package="com.qf.controller"></context:component-scan>
  13. <!-- 注册注解开发驱动 -->
  14. <mvc:annotation-driven></mvc:annotation-driven>
  15. <!-- 视图解析器
  16. 作用:1.捕获后端控制器的返回值="index"
  17. 2.解析: 在返回值的前后 拼接 ==> "/index.jsp"
  18. -->
  19. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  20. <!-- 前缀 -->
  21. <property name="prefix" value="/"></property>
  22. <!-- 后缀 -->
  23. <property name="suffix" value=".jsp"></property>
  24. </bean>
  25. </beans>

2.5 访问

  1. http://localhost:8989/hello/test1
  2. http://localhost:8989/hello/test2

三、接收请求参数


3.1 基本类型参数

请求参数和方法的形参 同名即可

springMVC默认可以识别的日期字符串格式为: YYYY/MM/dd HH:mm:ss
通过@DateTimeFormat可以修改默认日志格式

  1. // id name gender
  2. // http://localhost:8989/xxx/../test1?id=1&name=zzz&gender=false&birth=2018-12-12 12:20:30
  3. @RequestMapping("/test1")
  4. public String testParam1(Integer id,
  5. String name,
  6. Boolean gender,
  7. @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")Date birth){
  8. System.out.println("test param1");
  9. return "index";
  10. }

3.2 实体收参【重点

请求参数和实体的属性 同名即可

  1. public class User {
  2. private Integer id;
  3. private String name;
  4. @DateTimeFormat(pattern="yyyy-MM-dd")
  5. private Date birth;
  6. private Boolean gender;
  7. //set/get ...
  8. }
  9. //http://localhost:8989/.../test2?id=1&name=zzz&gender=false&birth=2018-12-12 12:20:30
  10. @RequestMapping("/test2")
  11. public String testParam2(User user){
  12. System.out.println("test param2");
  13. System.out.println("user:"+user);
  14. return "index";
  15. }

3.3 数组收参

简单类型的 数组

  1. <form action="${pageContext.request.contextPath}/param/test3">
  2. <input type="checkbox" name="hobby" value="fb"/>足球
  3. <input type="checkbox" name="hobby" value="bb"/>篮球
  4. <input type="checkbox" name="hobby" value="vb"/>排球
  5. <input type="submit" value="提交"/>
  6. </form>
  1. //http://localhost:8989/.../test3?hobby=football&hobby=basketball
  2. @RequestMapping("/test3")
  3. public String testParam3(String[] hobby){
  4. for(String h:hobby){
  5. System.out.print(h+" ");
  6. }
  7. return "index";
  8. }

3.4 集合收参 【了解】分布式

  1. <form action="${pageContext.request.contextPath}/param/test4" method="post">
  2. id:<input type="text" name="users[0].id"/>
  3. name:<input type="text" name="users[0].name"/>
  4. gender:<input type="text" name="users[0].gender"/>
  5. <br/>
  6. id:<input type="text" name="users[1].id"/>
  7. name:<input type="text" name="users[2].name"/>
  8. gender:<input type="text" name="users[3].gender"/>
  9. <input type="submit" value="提交"/>
  10. </form>
  1. public class UserList {
  2. //private User[] users;
  3. private List<User> users;
  4. //set/get..
  5. }
  6. // <input type="text" name="users[0].id"/>
  7. // post请求:http://...?users[0].id=1&users[0].name=zhangsan&users[0].birth=2018-12-12&users[1].id=2&....
  8. @RequestMapping("/test4")
  9. public String testParam4(UserList userList){
  10. for(User user:userList.getUsers()){
  11. System.out.println(user);
  12. }
  13. return "index";
  14. }

3.5 路径参数

  1. // {id} 定义名为id的路径;【/hello/{id}】的匹配能力和【/hello/*】等价
  2. // http://localhost:8989/.../hello/10 {id}匹配到10
  3. @RequestMapping("/hello/{id}")
  4. // @PathVariable将{id}路径匹配到值赋给id参数
  5. // 路径名和参数名相同则@PathVariable("id")可简写为 @PathVariable
  6. public String testParam5(@PathVariable("id") Integer id){
  7. System.out.println("id:"+id);
  8. return "index";
  9. }
  10. // http://localhost:8989/.../hello/tom {username}匹配到tom
  11. @RequestMapping("/hello/{username}")
  12. public String testParam6(@PathVariable("username") String name){//将{username}路径匹配到的值赋给name参数
  13. System.out.println("username:"+name);
  14. return "index";
  15. }

3.6 中文乱码

首先,页面中字符集统一

  1. JSP : <%@page pageEncoding="utf-8" %>
  2. HTML : <meta charset="UTF-8">

其次,tomcat中字符集设置,对get请求中,中文参数乱码有效

  1. Tomcat配置:URIEncoding=utf-8

最后,设置此filter,对post请求中,中文参数乱码有效

  1. <!-- 此过滤器会进行:request.setCharactorEncoding("utf-8"); -->
  2. <filter>
  3. <filter-name>encoding</filter-name>
  4. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  5. <init-param>
  6. <param-name>encoding</param-name>
  7. <param-value>utf-8</param-value>
  8. </init-param>
  9. </filter>
  10. <filter-mapping>
  11. <filter-name>encoding</filter-name>
  12. <url-pattern>/*</url-pattern>
  13. </filter-mapping>

响应乱码

  1. <mvc:annotation-driven>
  2. <!--设置响应输出字符集-->
  3. <mvc:message-converters>
  4. <bean class="org.springframework.http.converter.StringHttpMessageConverter">
  5. <property name="supportedMediaTypes">
  6. <list>
  7. <value>text/html;charset=utf-8</value>
  8. </list>
  9. </property>
  10. </bean>
  11. </mvc:message-converters>
  12. </mvc:annotation-driven>

四、跳转


4.1 转发

  1. @RequestMapping("/forw")
  2. class ForwardController{
  3. @RequestMapping("/test1")
  4. public String testForward(){
  5. System.out.println("test forward1");
  6. // 转发跳转 /views/users.jsp
  7. // return "views/users";//和下一行等价
  8. return "forward:/views/users.jsp";
  9. }
  10. @RequestMapping("/test2")
  11. public String testForward2(){
  12. System.out.println("test forward2");
  13. //转发到 /forw/test1
  14. //return "forward:test1";//相对路径(转发到本类中的test1)
  15. //转发到 /forw/test1
  16. return "forward:/forw/test1"; //绝对路径
  17. }
  18. }

4.2 重定向

  1. @RequestMapping("/redir")
  2. class RedirectController{
  3. @RequestMapping("/test1")
  4. public String testRedirect1(){
  5. System.out.println("test redirect1");
  6. //重定向到 /redir/test1
  7. //return "redirect:test1"; //相对路径(转发到本类中的test1)
  8. return "redirect:/redir/test1";//绝对路径
  9. }
  10. @RequestMapping("/test2")
  11. public String testRedirect2(){
  12. System.out.println("test redirect2");
  13. //重定向到 /views/users.jsp
  14. return "redirect:/view/user.jsp";
  15. }
  16. }

4.3 跳转细节

  • 在增删改之后,为了防止请求重复提交,重定向跳转
  • 在查询之后,可以做转发跳转

五、响应


C得到数据后,跳转到V,并向V传递数据。进而V中可以渲染数据,让用户看到含有数据的页面

转发跳转:Request作用域

重定向跳转:Session作用域

5.1 Request和Session

  1. ${requestScope.age}
  2. ${sessionScope.name}
  1. //形参中 即可获得 request 和 session对象
  2. @RequestMapping("/test1")
  3. public String testData(HttpSession session,HttpServletRequest reqInteger id){
  4. session.setAttribute("user",new User());
  5. req.setAttribute("age", 18);
  6. req.setAttribute("users",Arrays.asList(new User(),new User()));
  7. //return "test2";
  8. return "forward:/WEB-INF/test2.jsp";
  9. }

5.2 JSP中取值

建议:重点复习 EL JSTL

  1. //jsp中用EL表达式 取值即可
  2. <fmt:formatDate value="${sessionScope.user.birth}" pattern="yyyy-MM-dd"/> <br/>
  3. ${sessionScope.user.birth} <br>
  4. ${requestScope.age}

5.3 Model

  1. //model中的数据,会在V渲染之前,将数据复制一份给request
  2. @RequestMapping("/test")
  3. public String testData(Model model){
  4. model.addAttribute("name", "张三");
  5. return "index";
  6. }
  7. //jsp中用EL表达式 取值即可
  8. ${requestScope.name}

5.4 ModelAndView

  1. //modelandview 可以集中管理 跳转和数据
  2. @RequestMapping("/test")
  3. public ModelAndView testData(){//返回值类型为ModelAndView
  4. //新建ModelAndView对象
  5. ModelAndView mv = new ModelAndView();
  6. // 设置视图名,即如何跳转
  7. mv.setViewName("forward:/index.jsp");
  8. // 增加数据
  9. mv.addObject("age",18);
  10. return mv;
  11. }
  12. //jsp中用EL表达式 取值即可
  13. ${requestScope.age}

5.5 @SessionAttributes (了解)

  • @SessionAttributes({“gender”,”name”}) :model中的 name和gender 会存入session中
  • SessionStatus 移除session
  1. @Controller
  2. @SessionAttributes({"gender","name"}) // model中的 name和gender 会存入session中
  3. public class UserController {
  4. @RequestMapping("/hello")
  5. public String hello(Model m){
  6. m.addAttribute("gender",true); // 会存入session
  7. mv.addObject("name","zhj"); // 会存入session
  8. return "index";
  9. }
  10. @RequestMapping("/hello2")
  11. public String hello(SessionStatus status){
  12. // 移除通过SessionAttributes存入的session
  13. status.setComplete();
  14. return "index";
  15. }
  16. }

六、静态资源


6.1 静态资源问题

静态资源:html,js文件,css文件,图片文件

静态文件没有url-pattern,所以默认是访问不到的,之所以可以访问,是因为,tomcat中有一个全局的servlet:org.apache.catalina.servlets.DefaultServlet,它的url-pattern是 “/“,是全局默认的Servlet. 所以每个项目中不能匹配的静态资源的请求,有这个Servlet来处理即可。

但,在SpringMVC中DispatcherServlet也采用了 “/” 作为url-pattern, 则项目中不会再使用全局的Serlvet,则静态资源不能完成访问。

6.2 解决方案1

DispathcerServlet采用其他的url-pattern

此时,所有访问handler的路径都要以 action结尾!!

  1. <servlet>
  2. <servlet-name>mvc9</servlet-name>
  3. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>mvc9</servlet-name>
  7. <url-pattern>*.action</url-pattern>
  8. </servlet-mapping>

6.3 解决方案2

DispathcerServlet的url-pattern依然采用 “/“,但追加配置

  1. <!--
  2. 额外的增加一个handler,且其requestMapping: "/**" 可以匹配所有请求,但是优先级最低
  3. 所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
  4. 处理方式:将请求转会到tomcat中名为default的Servlet
  5. -->
  6. <mvc:default-servlet-handler/>

6.4 解决方案3

  • mapping是访问路径,location是静态资源存放的路径
  • 将/html/ 中 /匹配到的内容,拼接到 /hhh/后
    http://..../html/a.html 访问 /hhh/a.html
  1. <mvc:resources mapping="/html/**" location="/hhh/"/>

七、Json处理(了解)


7.1 导入依赖

  1. <!-- Jackson springMVC默认的Json解决方案选择是 Jackson,所以只需要导入jackson的jar,即可使用。-->
  2. <dependency>
  3. <groupId>com.fasterxml.jackson.core</groupId>
  4. <artifactId>jackson-databind</artifactId>
  5. <version>2.9.8</version>
  6. </dependency>

7.2 使用@ResponseBody

  1. @Controller
  2. public class JsonController{
  3. @RequestMapping("/test1")
  4. @ResponseBody //将handler的返回值,转换成json(jackson),并将json响应给客户端。
  5. public User hello1(){
  6. System.out.println("hello world");
  7. User user = new User();
  8. return user;
  9. }
  10. // @ResponseBody还可以用在handler的返回值上
  11. @RequestMapping("/test2")
  12. public @ResponseBody List<User> hello2(){
  13. System.out.println("hello world");
  14. List<User> users = Arrays.asList(new User(),new User());
  15. return users;
  16. }
  17. // 如果返回值已经是字符串,则不需要转json,直接将字符串响应给客户端
  18. @RequestMapping(value="/test3",produces = "text/html;charset=utf-8") //produces 防止中文乱码
  19. @ResponseBody
  20. public String hello2(){
  21. System.out.println("hello world");
  22. return "你好";
  23. }
  24. }

7.3 使用@RestController

Controller类上加了@RestController注解,等价于在类中的每个方法上都加了@ResponseBody

  1. @Controller
  2. @RestController
  3. public class JsonController{
  4. @RequestMapping("/test1")
  5. public User hello1(){
  6. System.out.println("hello world");
  7. User user = new User();
  8. return user;
  9. }
  10. //@ResponseBody还可以用在handler的返回值上
  11. @RequestMapping("/test2")
  12. public List<User> hello2(){
  13. System.out.println("hello world");
  14. List<User> users = Arrays.asList(new User(),new User());
  15. return users;
  16. }
  17. }

7.4 使用@RequestBody

@RequestBody , 接收Json参数

7.4.1 定义Handler
  1. class User{
  2. private Integer id;
  3. private String name;
  4. private Boolean gender;
  5. //set get
  6. }
  1. @RequestMapping("/users")
  2. public String addUser(@RequestBody User user){//@RequestBody将请求体中的json数据转换为java对象
  3. System.out.println("cap2");
  4. System.out.println("Post user :"+user);
  5. return "index";
  6. }

注意 :1、 采用@RequestBody 首先得设置 请求头content-type:application/json
2、你的参数必须是json格式

7.4.2 Ajax发送json
  1. var xhr = new XMLHttpRequest();
  2. xhr.open("post","${pageContext.request.contextPath}/users?"+new Date().getTime());
  3. xhr.setRequestHeader("content-type","application/json");//设置请求头
  4. xhr.send('{"id":1,"name":"shine","gender":"true"}');//传递json串
  1. //ajax
  2. var user = {id:1,name:"shine"};
  3. $.ajax({
  4. url:'${pageContext.request.contextPath}/json2/test4',
  5. type:'post',
  6. contentType:"application/json",//声明请求参数类型为 json
  7. data:JSON.stringify(user),// 转换js对象成json
  8. success:function(ret){
  9. console.log(ret);
  10. }
  11. });

7.4.3 @RequestParam

1、支持三种请求方式:parms、from-data,x-www-form-urlencoded;

prams: url?username=admin&pwd=123456
from-data: username=admin,pwd=123456

x-www-form-urlencoded:{username:”admin”,pwd:”123456”}

7.5 Jackson常用注解 (了解)

7.5.1 日期格式化

@JsonFormat(pattern=”yyyy-MM-dd HH:mm:ss”,timezone = “GMT+8”)

  1. public class User{
  2. private Integer id;
  3. private String name;
  4. @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
  5. private Date birth;
  6. ....
  7. get/set
  8. }

7.5.2 属性名修改

@JsonProperty(“new_name”)

  1. public class User{
  2. @JsonProperty("new_id") //不再使用原属性名,而是 "new_id"
  3. private Integer id;
  4. private String name;
  5. ....
  6. get/set
  7. }
  8. 输出的json:{“new_id”:xx,"name":"xx"}

7.5.3 属性忽略

@JsonIgnore

  1. public class User{
  2. private Integer id;
  3. @JsonIgnore // 生成json时,忽略此属性
  4. private String name;
  5. ....
  6. get/set
  7. }
  8. 输出json时: {"id":xx}

7.5.4 null和empty属性排除

Jackson 默认会输出null值的属性,如果不需要,可以排除。

@JsonInclude(JsonInclude.Include.NON_NULL) //null值 属性不输出
@JsonInclude(value= JsonInclude.Include.NON_EMPTY) // empty属性不输出( 空串,长度为0的集合,null值)

  1. public class User{
  2. private Integer id;
  3. @JsonInclude(JsonInclude.Include.NON_NULL) // 若"name==null" 忽略此属性
  4. private String name;
  5. @JsonInclude(value= JsonInclude.Include.NON_EMPTY) // 若hobby长度为0或==null 忽略此属性
  6. private List<String> hobby;
  7. ....
  8. get/set
  9. }
  10. 如果name=null,且 hobby长度为0,则输出json时:{"id":xx}

7.5.5 自定义序列化

@JsonSerialize(using = MySerializer.class) // 使用MySerializer输出某属性

  1. public class User {
  2. private Integer id;
  3. private String name;
  4. @JsonSerialize(using = MySerializer.class)
  5. private Double salary = 10000.126;//在输出此属性时,使用MySerializer输出
  6. ....
  7. get/set
  8. }
  9. 则输出json时:{"id":xx,"name":"xxx","salary":10000.13}
  1. public class MySerializer extends JsonSerializer<Double> {
  2. // value即 Double salary的值
  3. @Override
  4. public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
  5. // 将Double salary的值 四舍五入
  6. String number = BigDecimal.valueOf(value).setScale(2, BigDecimal.ROUND_HALF_UP).toString();
  7. // 输出 四舍五入后的值
  8. gen.writeNumber(number);
  9. }
  10. }

7.6 FastJson

7.6.1 导入依赖
  1. <!-- FastJson -->
  2. <dependency>
  3. <groupId>com.alibaba</groupId>
  4. <artifactId>fastjson</artifactId>
  5. <version>1.2.54</version>
  6. </dependency>

7.6.2 安装FastJson
  1. <mvc:annotation-driven>
  2. <!-- 安装FastJson,转换器 -->
  3. <mvc:message-converters>
  4. <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
  5. <!-- 声明转换类型:json -->
  6. <property name="supportedMediaTypes">
  7. <list>
  8. <value>application/json</value>
  9. </list>
  10. </property>
  11. </bean>
  12. </mvc:message-converters>
  13. </mvc:annotation-driven>
  1. // 获取子页面的iframe
  2. var iframe = window['layui-layer-iframe' + index];
  3. // 向子页面的全局函数child传参
  4. iframe.child(data);
  5. window.child = function (e){
  6. var data = e;
  7. console.log(e)
  8. $("#id").val(e.id);
  9. $("#name").val(e.name);
  10. $("#password").val(e.password)
  11. }

7.6.3 使用

@ResponseBody @RequestBody @RestController 使用方法不变

7.6.4 常用注解
  • 日期格式化:@JSONField(format=”yyyy/MM/dd”)
  • 属性名修改:@JSONField(name=”birth”)
  • 忽略属性:@JSONField(serialize = false)
  • 包含null值:@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue) 默认会忽略所有null值,有此注解会输出null
  1. public class User implements Serializable{
  2. @JSONField(serialize = false)
  3. private Integer id;
  4. @JSONField(name="NAME",serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty)
  5. private String name;
  6. @JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue)
  7. private String city;
  8. @JSONField(format="yyyy/MM/dd")
  9. private Date birth;
  10. @JSONField(serializeUsing = MySerializer2.class)
  11. private Double salary;
  12. ...
  13. }
  1. public class MySerializer2 implements ObjectSerializer {
  2. @Override
  3. public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType,
  4. int features) throws IOException {
  5. Double value = (Double) object; // salary属性值
  6. String text = value + "元";// 在salary后拼接 “元”
  7. serializer.write(text); // 输出拼接后的内容
  8. }
  9. }
  1. new User(1nullnullnew Date(),100.5);
  2. // 如上对象,转换json:
  3. {NAME:""city:null"birth":"2020/12/12""salary":"100.5元"}

八、异常解析器


8.1 现有方案,分散处理

Controller中的每个Handler自己处理异常

此种处理方案,异常处理逻辑,分散在各个handler中,不利于集中管理

  1. public String xxx(){
  2. try{
  3. ...
  4. }catch(Exception1 e){
  5. e.printStackTrace();
  6. return "redirect:/xx/error1";
  7. }catch(Exception2 e){
  8. e.printStackTrace();
  9. return "redirect:/xx/error2";
  10. }
  11. }

8.2 异常解析器,统一处理(重点)

Controller中的每个Handler不再自己处理异常,而是直接throws所有异常。

定义一个“异常解析器” 集中捕获处理 所有异常

此种方案,在集中管理异常方面,更有优势!

  1. public class MyExResolver implements HandlerExceptionResolver{
  2. /**
  3. * 异常解析器:主体逻辑
  4. * 执行时刻:当handler中抛出异常时,会执行:捕获异常,并可以跳到错误页面
  5. */
  6. @Override
  7. public ModelAndView resolveException(HttpServletRequest request,
  8. HttpServletResponse response, Object handler, Exception ex) {
  9. ex.printStackTrace();//打印异常栈
  10. //创建一个ModelAndView
  11. ModelAndView mv = new ModelAndView();
  12. //识别异常
  13. if (ex instanceof Exception1) {
  14. mv.setViewName("redirect:/xxx/error1");
  15. }else if(ex instanceof Exception2){
  16. mv.setViewName("redirect:/xxx/error2");
  17. }else{
  18. mv.setViewName("redirect:/xxx/error");
  19. }
  20. return mv;
  21. }
  22. }
  1. <!-- 声明异常解析器 -->
  2. <bean class="com.baizhi.exception.resolver.MyExResolver"></bean>

九、拦截器(非重点)


9.1 作用

作用:抽取handler中的冗余功能

9.2 定义拦截器

执行顺序: preHandle—postHandle—afterCompletion

  1. public class MyInter1 implements HandlerInterceptor{
  2. //主要逻辑:在handler之前执行:抽取handler中的冗余代码
  3. @Override
  4. public boolean preHandle(HttpServletRequest request,
  5. HttpServletResponse response, Object handler) throws Exception {
  6. System.out.println("pre~~~");
  7. /*
  8. response.sendRedirect("/springMVC_day2/index.jsp");//响应
  9. return false;//中断请求
  10. */
  11. return true;//放行,后续的拦截器或handler就会执行
  12. }
  13. //在handler之后执行:进一步的响应定制
  14. @Override
  15. public void postHandle(HttpServletRequest request,
  16. HttpServletResponse response, Object handler,
  17. ModelAndView modelAndView) throws Exception {
  18. System.out.println("post~~");
  19. }
  20. //在页面渲染完毕之后,执行:资源回收
  21. @Override
  22. public void afterCompletion(HttpServletRequest request,
  23. HttpServletResponse response, Object handler, Exception ex)
  24. throws Exception {
  25. System.out.println("after~~");
  26. }
  27. }

9.3 配置拦截路径

  1. <mvc:interceptors>
  2. <mvc:interceptor>
  3. <mvc:mapping path="/inter/test1"/>
  4. <mvc:mapping path="/inter/test2"/>
  5. <mvc:mapping path="/inter/test*"/> <!-- test开头 -->
  6. <mvc:mapping path="/inter/**"/> <!-- /** 任意多级任意路径 -->
  7. <mvc:exclude-mapping path="/inter/a/**"/> <!--不拦截此路径-->
  8. <bean class="com.baizhi.interceptor.MyInter1"></bean> <!--拦截器类-->
  9. </mvc:interceptor>
  10. </mvc:interceptors>

十、上传


10.1 导入jar

  1. <dependency>
  2. <groupId>commons-io</groupId>
  3. <artifactId>commons-io</artifactId>
  4. <version>2.4</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>commons-fileupload</groupId>
  8. <artifactId>commons-fileupload</artifactId>
  9. <version>1.3.3</version>
  10. <exclusions>
  11. <exclusion>
  12. <groupId>javax.servlet</groupId>
  13. <artifactId>servlet-api</artifactId>
  14. </exclusion>
  15. </exclusions>
  16. </dependency>

10.2 表单

  1. <form action="${pageContext.request.contextPath }/upload/test1" method="post"
  2. enctype="multipart/form-data">
  3. file: <input type="file" name="source"/> <br>
  4. <input type="submit" value="提交"/>
  5. </form>

10.3 上传解析器

  1. <!-- 上传解析器
  2. id必须是:“multipartResolver”
  3. -->
  4. <bean id="multipartResolver"
  5. class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  6. <!-- 最大可上传的文件大小 单位:byte 超出后会抛出MaxUploadSizeExceededException异常,可以异常解析器捕获 -->
  7. <property name="maxUploadSize" value="1048576"></property>
  8. </bean>

10.4 Handler

  1. @RequestMapping("/test1")
  2. public String hello1(String username,MultipartFile source,HttpSession session) {
  3. //文件的原始名称
  4. String filename = source.getOriginalFilename();
  5. //定制全局唯一的命名
  6. String unique = UUID.randomUUID().toString();
  7. //获得文件的后缀
  8. String ext = FilenameUtils.getExtension(filename);
  9. //abc.txt txt hello.html html
  10. //定制全局唯一的文件名
  11. String uniqueFileName = unique+"."+ext;
  12. System.out.println("唯一的文件名:"+uniqueFileName);
  13. //文件的类型 String type = source.getContentType();
  14. System.out.println("filename:"+filename+" type:"+type);
  15. //获得 upload_file的磁盘路径 ==> 在webapp目录下创建一个目录"upload_file",且此目录初始不要为空,否则编译时被忽略
  16. String real_path = session.getServletContext().getRealPath("/upload_file");
  17. System.out.println("real_path:"+real_path);
  18. //将上传的文件,存入磁盘路径中
  19. //source.transferTo(new File("d:/xxxx/xxxx/xx.jpg"))
  20. source.transferTo(new File(real_path+"\\"+uniqueFileName));
  21. return "index";
  22. }

十一、下载


11.1 超链

  1. <a href="${pageContext.request.contextPath}/download/test1?name=Koala.jpg">下载</a>

11.2 Handler

  1. @RequestMapping("/test1")
  2. public void hello1(String name,HttpSession session,HttpServletResponse response){
  3. System.out.println("name:"+name);
  4. //获得要下载文件的绝对路径
  5. String path = session.getServletContext().getRealPath("/upload_file");
  6. //文件的完整路径
  7. String real_path = path+"\\"+name;
  8. //中文文件名的乱码解决
  9. String encode = URLEncoder.encode(fileName, "utf-8");
  10. //设置响应头 告知浏览器,要以附件的形式保存内容 filename=浏览器显示的下载文件名
  11. response.setHeader("content-disposition","attachment;filename="+name);
  12. //读取目标文件,写出给客户端
  13. IOUtils.copy(new FileInputStream(real_path), response.getOutputStream());
  14. //上一步,已经是响应了,所以此handler直接是void
  15. }

十二、验证码


12.1 作用

防止暴力攻击,前端安全保障

12.2 导入jar

  1. <!-- Kaptcha -->
  2. <dependency>
  3. <groupId>com.github.penggle</groupId>
  4. <artifactId>kaptcha</artifactId>
  5. <version>2.3.2</version>
  6. <exclusions>
  7. <exclusion>
  8. <groupId>javax.servlet</groupId>
  9. <artifactId>javax.servlet-api</artifactId>
  10. </exclusion>
  11. </exclusions>
  12. </dependency>

12.3 声明验证码组件

  1. <servlet>
  2. <servlet-name>cap</servlet-name>
  3. <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
  4. <init-param>
  5. <param-name>kaptcha.border</param-name>
  6. <param-value>no</param-value>
  7. </init-param>
  8. <init-param>
  9. <param-name>kaptcha.textproducer.char.length</param-name>
  10. <param-value>4</param-value>
  11. </init-param>
  12. <init-param>
  13. <param-name>kaptcha.textproducer.char.string</param-name>
  14. <param-value>abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</param-value>
  15. </init-param>
  16. <init-param>
  17. <param-name>kaptcha.background.clear.to</param-name>
  18. <param-value>211,229,237</param-value>
  19. </init-param>
  20. <init-param>
  21. <!-- session.setAttribute("captcha","验证码") -->
  22. <param-name>kaptcha.session.key</param-name>
  23. <param-value>captcha</param-value>
  24. </init-param>
  25. </servlet>
  26. <servlet-mapping>
  27. <servlet-name>cap</servlet-name>
  28. <url-pattern>/captcha</url-pattern>
  29. </servlet-mapping>

12.4 Page

  1. <img src="${pageContext.request.contextPath}/captcha" style="width:85px" id="cap"/>
  2. <script>
  3. $(function(){
  4. $("#cap").click(function(){
  5. //刷新验证码
  6. path = $(this).attr("src")+"?"+new Date().getTime(); // f
  7. $(this).attr("src",path);
  8. });
  9. });
  10. </script>

十三、REST(重要点)


13.1 开发风格

是一种开发风格,遵从此风格开发软件,符合REST风格,则RESTFUL。

两个核心要求:

  • 每个资源都有唯一的标识(URL)
  • 不同的行为,使用对应的http-method
访问标识 资源
http://localhost:8989/xxx/users 所有用户
http://localhost:8989/xxx/users/1 用户1 pathvarbale
http://localhost:8989/xxx/users/1/orders 用户1的所有订单
请求方式 标识 意图
GET http://localhost:8989/xxx/users 查询所有用户
POST http://localhost:8989/xxx/users 在所有用户中增加一个
PUT http://localhost:8989/xxx/users 在所有用户中修改一个
DELETE http://localhost:8989/xxx/users/1 删除用户1
GET http://localhost:8989/xxx/users/1 查询用户1
GET http://localhost:8989/xxx/users/1/orders 查询用户1的所有订单
POST http://localhost:8989/xxx/users/1/orders 在用户1的所有订单中增加一个

13.2 优点

  • **输出json:

13.3 使用

13.3.1 定义Rest风格的 Controller

@RequestMapping(value=”/users”,method = RequestMethod.GET)

等价

@GetMapping(“/users”)

  1. @RestController
  2. public class RestController {
  3. @GetMapping("/users")
  4. public List<User> queryAllUsers(){
  5. System.out.println("get");
  6. List<User> users = ....
  7. return users;
  8. }
  9. @PostMapping("/users")
  10. public String addUser(@RequestBody User user){
  11. System.out.println("Post user :"+user);
  12. return "{status:1}";
  13. }
  14. @PutMapping("/users")
  15. public String updateUser(@RequestBody User user){
  16. System.out.println("Put user" user:"+user);
  17. return "{status:1}";
  18. }
  19. @GetMapping("/users/{id}")
  20. public String queryOneUser(@PathVariable Integer id){//@PathVariable 接收路径中的值
  21. System.out.println("Get user id:"+id);
  22. return "{status:1}";
  23. }
  24. @DeleteMapping("/users/{id}")
  25. public String deleteOneUser(@PathVariable Integer id){//@PathVariable 接收路径中的值
  26. System.out.println("delete user id:"+id);
  27. return "{status:1}";
  28. }
  29. }

13.3.2 Ajax请求
  1. <script>
  2. function putUser(){ // 发送更新请求 (增加请求发送方式也是如此)
  3. var xhr = new XMLHttpRequest();
  4. //定义 put,delete,get,post方式 即可,不用定义_method
  5. xhr.open("put","${pageContext.request.contextPath}/rest04/users");
  6. // 设置请求头
  7. xhr.setRequestHeader("content-type","application/json");
  8. // 设置请求参数
  9. var user = {id:1NAME:"shine"city:"bj""birth":"2020/12/12""salary":100.5};
  10. xhr.send(JSON.stringify(user));
  11. xhr.onreadystatechange=function(){
  12. if(xhr.readyState==4 && xhr.status==200){
  13. var ret = xhr.responseText;
  14. // 解析json,并输出
  15. console.log(JSON.parse(ret));
  16. }
  17. }
  18. /*$.ajax({
  19. url:'${pageContext.request.contextPath}/rest04/users',
  20. type:'put',
  21. contentType:"application/json",//声明请求参数类型为 json
  22. data:JSON.stringify(user),// 转换js对象成json
  23. success:function(ret){
  24. console.log(JSON.parse(ret));
  25. }
  26. });*/
  27. }
  28. function delUser(){ // 发送删除请求
  29. var xhr = new XMLHttpRequest();
  30. //定义 put,delete,get,post方式 即可,不用定义_method
  31. xhr.open("delete","${pageContext.request.contextPath}/rest04/users/1");
  32. xhr.send();
  33. xhr.onreadystatechange=function(){
  34. if(xhr.readyState==4 && xhr.status==200){
  35. var ret = xhr.responseText;
  36. console.log(JSON.parse(ret));
  37. }
  38. }
  39. }
  40. </script>

十四、跨域请求


14.1 域

域:协议+IP+端口

14.2 Ajax跨域问题

  • Ajax发送请求时,不允许跨域,以防用户信息泄露。
  • 当Ajax跨域请求时,响应会被浏览器拦截(同源策略),并报错。即浏览器默认不允许ajax跨域得到响应内容。
  • 互相信任的域之间如果需要ajax访问,(比如前后端分离项目中,前端项目和后端项目之间),则需要额外的设置才可正常请求。

14.3 解决方案

  • 允许其他域访问
  • 在被访问方的Controller类上,添加注解
  1. @CrossOrigin("http://localhost:8080") //允许此域发请求访问
  2. public class SysUserController {
  3. ....
  4. }
  • 携带对方cookie,使得session可用
  • 在访问方,ajax中添加属性:withCredentials: true
  1. $.ajax({
  2. type: "POST",
  3. url: "http://localhost:8989/web/sys/login",
  4. ...,
  5. xhrFields: {
  6. // 跨域携带cookie
  7. withCredentials: true
  8. }
  9. });
  10. var xhr = new XMLHttpRequest();
  11. // 跨域携带cookie
  12. xhr.withCredentials=true;

十五、SpringMVC执行流程


image.png

十六、Spring整合


16.1 整合思路

此时项目中有两个工厂

  • DispatcherServlet 启动的springMVC工厂==负责生产C及springMVC自己的系统组件
  • ContextLoaderListener 启动的spring工厂==负责生产其他所有组件
  • springMVC的工厂会被设置为spring工厂的子工厂,可以随意获取spring工厂中的组件
  • 整合过程,就是累加:代码+依赖+配置。然后将service注入给controller即可

16.2 整合技巧

两个工厂不能有彼此侵入,即,生产的组件不能有重合。

  1. <!-- 告知SpringMVC 哪些包中 存在 被注解的类
  2. use-default-filters=true 凡是被 @Controller @Service @Repository注解的类,都会被扫描
  3. use-default-filters=false 默认不扫描包内的任何类, 只扫描include-filter中指定的类
  4. 只扫描被@Controller注解的类
  5. -->
  6. <context:component-scan base-package="com.zhj" use-default-filters="false">
  7. <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  8. </context:component-scan>
  1. <!-- 告知Spring
  2. 唯独不扫描@Controller注解的类 -->
  3. <context:component-scan base-package="com.zhj" use-default-filters="true">
  4. <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
  5. </context:component-scan>