springMVC-程序员宅基地

技术标签: spring  java  servlet  

简介

MVC模型
MVC全称Model View Controller,是一种设计创建Web应用程序的模式。这三个单词分别代表Web应用程序的三个部分:

Model(模型):指数据模型。用于存储数据以及处理用户请求的业务逻辑。在Web应用中,JavaBean对象,业务模型等都属于Model。

View(视图):用于展示模型中的数据的,一般为jsp或html文件。

Controller(控制器):是应用程序中处理用户交互的部分。接受视图提出的请求,将数据交给模型处理,并将处理后的结果交给视图显示。
在这里插入图片描述
SpringMVC
SpringMVC是一个基于MVC模式的轻量级Web框架,是Spring框架的一个模块,和Spring可以直接整合使用。SpringMVC代替了Servlet技术,它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。

springMVC 入门

  1. 使用maven创建web项目,补齐包结构。
    引入相关依赖和tomcat插件
<dependencies>
  <!-- Spring核心模块 -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.12.RELEASE</version>
  </dependency>
  <!-- SpringWeb模块 -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.12.RELEASE</version>
  </dependency>
  <!-- SpringMVC模块 -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.12.RELEASE</version>
  </dependency>
  <!-- Servlet -->
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
  </dependency>
  <!-- JSP -->
  <dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
  </dependency>
</dependencies>


<build>
  <plugins>
    <!-- tomcat插件 -->
    <plugin>
      <groupId>org.apache.tomcat.maven</groupId>
      <artifactId>tomcat7-maven-plugin</artifactId>
      <version>2.1</version>
      <configuration>
        <port>8080</port>
        <path>/</path>
        <uriEncoding>UTF-8</uriEncoding>
        <server>tomcat7</server>
        <systemProperties>
          <java.util.logging.SimpleFormatter.format>%1$tH:%1$tM:%1$tS %2$s%n%4$s: %5$s%6$s%n
          </java.util.logging.SimpleFormatter.format>
        </systemProperties>
      </configuration>
    </plugin>
  </plugins>
</build>

  1. 在web.xml中配置前端控制器DispatcherServlet
<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <!--SpringMVC前端控制器,本质是一个Servlet,接收所有请求,在容器启动时就会加载-->
  <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>
  </servlet-mapping>
</web-app>

SpringMVC执行流程

SpringMVC的组件
DispatcherServlet:前端控制器,接受所有请求,调用其他组件。
HandlerMapping:处理器映射器,根据配置找到方法的执行链。
HandlerAdapter:处理器适配器,根据方法类型找到对应的处理器。
ViewResolver:视图解析器,找到指定视图。
在这里插入图片描述

组件的工作流程
客户端将请求发送给前端控制器。
前端控制器将请求发送给处理器映射器,处理器映射器根据路径找到方法的执行链,返回给前端控制器。
前端控制器将方法的执行链发送给处理器适配器,处理器适配器根据方法类型找到对应的处理器。
处理器执行方法,将结果返回给前端控制器。
前端控制器将结果发送给视图解析器,视图解析器找到视图文件位置。
视图渲染数据并将结果显示到客户端。

springMVC参数获取

封装简单数据类型

getParameter(name)
请求参数较多时会出现代码冗余。
与容器紧耦合。
而SpringMVC支持参数注入的方式用于获取请求数据,即将请求参数直接封装到方法的参数当中。用法如下:
编写控制器方法:

// 获取简单类型参数
@RequestMapping("/c1/param1")
public void simpleParam(String username,int age){
  System.out.println(username);
  System.out.println(age);
}

访问该方法时,请求参数名和方法参数名相同,即可完成自动封装。

http://localhost:8080/c1/param1?username=zs&age=10

封装对象类型

单个对象

SpringMVC支持将参数直接封装为对象,写法如下

  1. 编写实体类
public class Student {
  private int id;
  private String name;
  private String sex;
  // 省略getter/setter/tostring
}
  1. 编写控制器方法
// 获取对象类型参数
@RequestMapping("/c1/param2")
public void objParam(Student student){
  System.out.println(student);
}

访问该方法时,请求参数名和方法参数的属性名相同,即可完成自动封装。

http://localhost:8080/c1/param2?id=1&name=bz&sex=female

 

关联对象

编写实体类

public class Address {
  private String info; //地址信息
  private String postcode; //邮编
  // 省略getter/setter/tostring
}
public class Student {
  private int id;
  private String name;
  private String sex;
  private Address address; // 地址对象
  // 省略getter/setter/tostring
}

编写控制器方法

// 获取关联对象类型参数
@RequestMapping("/c1/param3")
public void objParam2(Student student){  
  System.out.println(student);
}

访问该方法时,请求参数名和方法参数的属性名相同,即可完成自动封装。

http://localhost:8080/c1/param3?id=1&name=bz&sex=female&address.info=beijing&address.postcode=030000

我们也可以使用表单发送带有参数的请求:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
 <head>
   <title>表单提交</title>
 </head>
 <body>
   <form action="/c1/param3" method="post">
     id:<input name="id">
     姓名:<input name="name">
     性别:<input name="sex">
     住址:<input name="address.info">
     邮编:<input name="address.postcode">
     <input type="submit">
   </form>
 </body>
</html>

封装集合类型

简单数据类型集合

SpringMVC支持将参数封装为List或Map集合,写法如下:
封装为List集合
封装为简单数据类型集合
编写控制器方法

// 绑定简单数据类型List参数,参数前必须添加@RequestParam注解
@RequestMapping("/c1/param4")
public void listParam(@RequestParam List<String> users){ 
  System.out.println(users);
}

该方式也可以绑定数组类型:

@RequestMapping("/c1/param5")
public void listParam2(@RequestParam String[] users){ 
  System.out.println(users[0]); 
  System.out.println(users[1]);
}

请求的参数写法

http://localhost:8080/c1/param5?users=bj&users=sxt

封装为对象类型集合

SpringMVC不支持将参数封装为对象类型的List集合,但可以封装到有List属性的对象中。

public class Student {
  private int id;
  private String name;
  private String sex;
  private List<Address> address; // 地址集合
  // 省略getter/setter/tostring
}

控制器

// 对象中包含集合属性
@RequestMapping("/c1/param6")
public void listParam3(Student student){
  System.out.println(student);
}

封装MAP集合

public class Student {
  private int id;
  private String name;
  private String sex;
  private Map<String,Address> address; // 地址集合
    // 省略getter/setter/tostring
}

// 对象中包含Map属性
@RequestMapping("/c1/param7")
public void mapParam(Student student){  
  System.out.println(student);
}

http://localhost:8080/c1/param7?id=1&name=bz&sex=female&address[‘one’].info=bj&address[‘one’].postcode=100010&address[‘two’].info=sh&address[‘two’].postcode=100011

使用Servlert原生对象获取参数

SpringMVC也支持使用Servlet原生对象,在方法参数中定义HttpServletRequest、HttpServletResponse、HttpSession等类型的参数即可直接在方法中使用。

// 使用Servlet原生对象
@RequestMapping("/c1/param8")
public void servletParam(HttpServletRequest request, HttpServletResponse response, HttpSession session){ 
  // 原生对象获取参数                           
  System.out.println(request.getParameter("name")); 
    System.out.println(response.getCharacterEncoding());  
    System.out.println(session.getId());
}
访问该方法即可:http://localhost:8080/c1/param8?name=bjsxt

一般情况下,在SpringMVC中都有对Servlet原生对象的方法的替代,推荐使用SpringMVC的方式代替Servlet原生对象。

自定义 参数类型转换器

前端传来的参数全部为字符串类型,SpringMVC使用自带的转换器将字符串参数转为需要的类型。如:

// 获取简单类型参数
@RequestMapping("/c1/param1")
public void simpleParam(String username,int age){ 
  System.out.println(username);  
  System.out.println(age);
}
请求路径:http://localhost:8080/c1/param1?username=bz&age=10

但在某些情况下,无法将字符串转为需要的类型,如

@RequestMapping("/c1/param9")
public void dateParam(Date birthday){ 
  System.out.println(birthday);
}

由于日期数据有很多种格式,SpringMVC没办法把所有格式的字符串转换成日期类型。比如参数格式为birthday=2025-01-01时,SpringMVC就无法解析参数。此时需要自定义参数类型转换器。

  1. 定义类型转换器类,实现Converter接口
    // 类型转换器必须实现Converter接口,两个泛型代表转换前的类型,转换后的类型
// 类型转换器必须实现Converter接口,两个泛型代表转换前的类型,转换后的类型
public class DateConverter implements Converter<String, Date> {
  /**
   * 转换方法
   * @param source 转换前的数据
   * @return 转换后的数据
   */
  @Override
  public Date convert(String source) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    Date date = null;
    try {
      date = sdf.parse(source);
     } catch (ParseException e) {
      e.printStackTrace();
     }
    return date;
   }
}

注册类型转换器对象

<!-- 配置转换器工厂 -->
<bean id="dateConverter" class="org.springframework.context.support.ConversionServiceFactoryBean">
  <!-- 转换器集合 -->
  <property name="converters">
    <set>
      <!-- 自定义转换器 -->
      <bean class="com.itbaizhan.converter.DateConverter"></bean>
    </set>
  </property>
</bean
  
<!-- 使用转换器工厂 -->
<mvc:annotation-driven conversion-service="converterFactory"></mvc:annotation-driven>

此时再访问http://localhost:8080/c1/param9?birthday=2025-01-01时,SpringMVC即可将请求参数封装为Date类型的参数。

编码过滤器

在传递参数时,tomcat8以上能处理get请求的中文乱码,但不能处理post请求的中文乱码
编写jsp表单

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>编码过滤器</title>
  </head>
  <body>
    <form action="/cn/code" method="post">
       姓名:<input name="username">
      <input type="submit">
    </form>
  </body>
</html>

编写控制器方法

@RequestMapping("/cn/code")
public void code(String username){
  System.out.println(username);
}

SpringMVC提供了处理中文乱码的过滤器,在web.xml中配置该过滤器即可解决中文乱码问题:

<!--SpringMVC中提供的字符编码过滤器,放在所有过滤器的最上方-->
<filter>
  <filter-name>encFilter</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>encFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

springMVC处理响应

配置视图解析器

SpringMVC默认情况下会在控制器执行完成后跳转到视图页面,视图解析器能找到相应的视图,之前的404异常就是由于没有配置视图解析器导致找不到视图。
在SpringMVC中提供了13个视图解析器,用于支持不同的视图技术。InternalResourceViewResolver是SpringMVC的默认视图解析器,用来解析JSP视图。

<!-- 视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <!-- 视图前缀 -->
  <property name="prefix" value="/" />
  <!-- 视图后缀 -->
  <property name="suffix" value=".jsp" />
</bean>

控制器方法返回值

我们可以通过控制器方法的返回值设置跳转的视图,控制器方法支持以下返回值类型:
返回值为void
此时会跳转到名字是 前缀+方法路径名+后缀 的jsp页面
编写控制器方法

// 路径是helloMVC,方法执行完后会跳转到/helloMVC.jsp
@RequestMapping("/helloMVC")
public void helloMVC(){
  System.out.println("hello SpringMVC!");
}

编写helloMVC.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>MVC</title>
  </head>
  <body>
    <h1>欢迎来到SpringMVC</h1>
  </body>
</html>

返回值为String
此时会跳转到名字是 前缀+返回值+后缀 的jsp页面

编写控制器方法

// 返回值为String
@RequestMapping("/c2/hello1")
public String helloMVC1(){
  System.out.println("hello SpringMVC!");
  // 方法执行完后会跳转到/helloMVC.jsp
  return "helloMVC";
}

返回值为ModelAndView
这是SpringMVC提供的对象,该对象可以向request域设置数据并指定跳转的页面。该对象中包含Model对象和View对象。
Model:向request域中设置数据。
View:指定跳转的页面。
编写控制器方法

// 返回值为ModelAndView
@RequestMapping("/c2/hello2")
public ModelAndView useMAV(){
  System.out.println("返回值类型为ModelAndView");
  // 1.创建ModelAndView对象
  ModelAndView modelAndView = new ModelAndView();
  // 2.获取Model对象,本质是一个Map
  Map<String, Object> model = modelAndView.getModel();
  // 3.使用Model对象向request域设置数据
  model.put("name","百战程序员");
  // 4.使用View对象设置跳转的路径为/baizhan.jsp
  modelAndView.setViewName("baizhan");
  return modelAndView;
}

编写jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<html>
  <head>
    <title>百战</title>
  </head>
  <body>
    <h1>你好!${requestScope.name}</h1>
  </body>
</html>

修改web.xml命名空间,让jsp页面默认支持el表达式

<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_3_1.xsd"
     version="3.1">
</web-app>

request域设置数据

SpringMVC可以使用HttpServletRequest,Map,Model,ModelMap向request域设置数据
当控制器返回值为ModelAndView时我们可以向request域设置数据,我们还有以下方法可以向request域设置数据:
使用原生的HttpServletRequest

@RequestMapping("/c2/hello3")
public String setRequestModel(HttpServletRequest request){
  request.setAttribute("username","尚学堂");
  return "baizhan";
}

使用Model、ModelMap
SpringMVC提供了Model接口和ModelMap类,控制器方法添加这两个类型的参数,使用该参数设置数据,该数据就会存到request域中。

@RequestMapping("/c2/hello4")
public String setRequestModel2(Model model, ModelMap modelMap){
  // 使用Model将数据存入request域
  // model.addAttribute("username","尚学堂");
  // 使用ModelMap将数据存入request域
  modelMap.addAttribute("username","尚学堂");
  return "baizhan";
}

使用Map集合
Model接口底层就是一个Map集合,我们可以给控制器方法设置Map类型的参数,向Map中添加键值对,数据也会存到request域中。

@RequestMapping("/c2/hello5")
public String setRequestModel3(Map map){
  map.put("username","尚学堂");
  return "baizhan";
}

session域设置数据

Session作用域表示在当前会话中有效。在SpringMVC中对于Session作用域传值,只能使用HttpSession对象来实现。

编写控制器方法

@RequestMapping("/c2/hello6")
public String setSeesionModel(HttpSession session){
  session.setAttribute("address","北京");
  return "baizhan";
}

编写jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<html>
  <head>
    <title>百战</title>
  </head>
  <body>
     <h1>你好!${requestScope.name}</h1>
        <h1>地址是!${sessionScope.address}</h1>
  </body>
</html>

context域设置数据

context作用域表示在整个应用范围都有效。在SpringMVC中对context作用域传值,只能使用ServletContext对象来实现。但是该对象不能直接注入到方法参数中,需要通过HttpSession对象获取。
编写控制器方法

@RequestMapping("/c2/hello7")
public String setContextModel(HttpSession session){
  ServletContext servletContext = session.getServletContext();
  servletContext.setAttribute("age",10);
  return "baizhan";
}

编写jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<html>
  <head>
    <title>百战</title>
  </head>
  <body>
    <h1>你好!${requestScope.name}</h1>
    <h1>地址是!${sessionScope.address}</h1>
    <h1>年纪是!${applicationScope.age}</h1>
  </body>
</html>

请求转发&重定向

之前的案例,我们发现request域中的值可以传到jsp页面中,也就是通过视图解析器跳转到视图的底层是请求转发。
如果我们跳转时不想使用视图解析器,可以使用原生HttpServletRequest进行请求转发或HttpServletResponse进行重定向:

@RequestMapping("/c2/hello8")
public void myForward1(HttpServletRequest request, HttpServletResponse response) throws Exception{
  request.setAttribute("name","尚学堂");
  // 请求转发
  //     request.getRequestDispatcher("/c2/hello9").forward(request,response);
  // 原生重定向
  response.sendRedirect("/c2/hello9");
}


@RequestMapping("/c2/hello9")
public void myForward2(HttpServletRequest request){
  System.out.println("hello");
  System.out.println(request.getAttribute("name"));
}

SpringMVC还提供了一种更简单的请求转发和重定向的写法

@RequestMapping("/c2/hello10")
public String myForward3(HttpServletRequest request){
  request.setAttribute("name","尚学堂");
  // 请求转发
  return "forward:/c2/hello9";
  // 重定向
  // return "redirect:/c2/hello9";
}

springMVC注解

Controller

作用:标记控制器,将控制器交给Spring容器管理。

位置:类上方

RequestMapping作用:给控制器方法设置请求路径

位置:方法或类上方。用于类上,表示类中的所有控制器方法都是以该地址作为父路径。

属性:

value/path:请求路径
method:指定请求方式
params:规定必须发送的请求参数
headers:规定请求必须包含的请求头

@Controller
@RequestMapping("/c3")
public class MyController3 {
   /*
    访问路径为 /c3/annotation1
    支持post和get请求
    请求时必须带有age参数
    请求时必须带有User-agent请求头
   */
  @RequestMapping(path = "/annotation1",method = {RequestMethod.GET,RequestMethod.POST},params = {"age"},headers = {"User-agent"})
  public String annotation1(String username){
    System.out.println(username);
    return "baizhan";
   }
}

RequestParam

作用:在控制器方法中获取请求参数
位置:方法参数前
属性:
name:指定请求参数名称
defaultValue: 为参数设置默认值
required:设置是否是必须要传入的参数

/*
  定义请求的参数名为username,默认值为sxt,不是必须的参数
 */
@RequestMapping("/annotation2")
public String annotation2(@RequestParam(name = "username",defaultValue = "sxt",required = false) String name){
  System.out.println(name);
  return "baizhan";
}

请求URL的写法:http://localhost:8080/c3/annotation2?username=bz

RequestHeader

@RequestHeader

作用:在控制器方法中获取请求头数据

位置:方法参数前

CookieValue

作用:在控制器方法中获取Cookie数据

位置:方法参数前

/*
    获取User-Agent请求头
    获取JSESSIONID的Cookie值
   */
@RequestMapping("/annotation3")
public String annotation3(@RequestHeader("User-Agent") String userAgent, @CookieValue("JSESSIONID") String jSessionId){
  System.out.println(userAgent);
  System.out.println(jSessionId);
  return "baizhan";
}

SessionAttributes

作用:将Model模型中的数据存到session域中
位置:类上方

@Controller
@RequestMapping("/c4")
// 将模型中的name数据保存到session中
@SessionAttributes("name")
public class MyController4 {
  @RequestMapping("/t1")
  public String t1(Model model){
    // model中保存name数据
    model.addAttribute("name","北京尚学堂");
    return "baizhan";
   }


  @RequestMapping("/t2")
  public String t2(HttpSession session){
    // 从session中获取name数据
    System.out.println(session.getAttribute("name"));
    return "baizhan";
   }
}

ModelAttribute

作用1:设置指定方法在控制器其他方法前执行

位置:方法上方

@Controller
@RequestMapping("/c5")
public class MyController5 {
  @ModelAttribute
  public void before(){
    System.out.println("前置方法");
   }


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



作用2:从Model模型中获取数据给参数赋值

位置:方法参数前

@Controller
@RequestMapping("/c6")
public class MyController6 {
  // 前置方法向Model中设置数据
  @ModelAttribute
  public void before(Model model){
    model.addAttribute("name","尚学堂");
   }


  // 该参数不是从请求中获取,而是从Model中获取
  @RequestMapping("/t1")
  public String t1(@ModelAttribute("name") String name){
    System.out.println(name);
    return "baizhan";
   }
}

Restful风格支持

RESTful风格是一种URL路径的设计风格。在RESTful风格的URL路径中,网络上的任意数据都可以看成一个资源,它可以是一段文本、一张图片,也可以是一个Java对象。而每个资源都会占据一个网络路径,无论对该资源进行增删改查,访问的路径是一致的。

传统URL:

查找id为1的学生:

http://localhost:8080/student/findById?id=30

删除id为1的学生:

http://localhost:8080/student/deleteById?id=30

RESTful风格URL:

查找id为30的学生:

http://localhost:8080/student/30

删除id为30的学生:

http://localhost:8080/student/30

那么如何区分对该资源是哪一种操作?通过请求方式不同,判断进行的是什么操作。

之前我们学过两种请求方式,GET请求和POST请求,而访问RESTful风格的URL一共有四种请求方式:

GET请求:查询操作
POST请求:新增操作
DELETE请求:删除操作
PUT请求:修改操作

RESTful风格URL:

查找id为30的学生:

http://localhost:8080/student/30 GET方式请求

删除id为30的学生:

http://localhost:8080/student/30 DELETE方式请求

RESTful风格的优点:
结构清晰、符合标准、易于理解、扩展方便。

Postman使用

默认情况下浏览器是无法发送DELETE请求和PUT请求的,我们可以使用Postman工具发送这些请求。

双击安装包安装Postman

点击new-collection创建请求集合

@PathVariable

作用:在RESTful风格的URL中获取占位符的值
位置:方法参数前
属性:value:获取哪个占位符的值作为参数值,如果占位符和参数名相同,可以省略该属性。

@Controller
@RequestMapping("/student")
// 模拟学生的增删改查控制器
public class StudentController {
  // 路径中的{id}表示占位符,最后会封装到方法的参数中使用
  // 删除学生
  @RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
  public String deleteStudent(@PathVariable("id") int id){
    System.out.println("删除id为"+id+"的学生");
    return "baizhan";
   }


  // 如果占位符和参数名相同,可以省略@PathVariable的value属性
  // 根据id查询学生
  @RequestMapping(value = "/{id}",method = RequestMethod.GET)
  public String findStudentById(@PathVariable int id){
    System.out.println(id);
    System.out.println("根据id查询学生");
    return "baizhan";
   }


    // 新增学生
  @RequestMapping(value = "/{id}",method = RequestMethod.POST)
  public String addStudent(@PathVariable int id, Student student){
    System.out.println(id);
    System.out.println(student);
    System.out.println("新增学生");
    return "baizhan";
   }


  // 修改学生
  @RequestMapping(value = "/{id}",method = RequestMethod.PUT)
  public String updateStudent(@PathVariable int id, Student student){
    System.out.println(id);
    System.out.println(student);
    System.out.println("修改学生");
    return "baizhan";
   }
}

访问方式:

新增学生:POST http://localhost:8080/student/1?name=尚学堂&sex=男
修改学生:PUT http://localhost:8080/student/1?name=尚学堂&sex=男
删除学生:DELETE http://localhost:8080/student/1
查询学生:GET http://localhost:8080/student/1

@PostMapping、@GetMapping、@PutMapping、@DeleteMapping

作用:简化设置请求方式的@RequestMapping写法
位置:方法上方

@Controller
@RequestMapping("/student")
public class StudentController {
  // 删除学生
  @DeleteMapping("/{id}")
  public String deleteStudent(@PathVariable("id") int id){
    System.out.println("删除id为"+id+"的学生");
    return "baizhan";
   }


  // 根据id查询学生
  @GetMapping("/{id}")
  public String findStudentById(@PathVariable int id){
    System.out.println(id);
    System.out.println("根据id查询学生");
    return "baizhan";
   }


  // 新增学生
  @PostMapping("/{id}")
  public String addStudent(@PathVariable int id, Student student){
    System.out.println(id);
    System.out.println(student);
    System.out.println("新增学生");
    return "baizhan";
   }


  // 修改学生
  @PutMapping("/{id}")
  public String updateStudent(@PathVariable int id, Student student){
    System.out.println(id);
    System.out.println(student);
    System.out.println("修改学生");
    return "baizhan";
   }
}

HiddentHttpMethodFilter

由于浏览器form表单只支持GET与POST请求,而DELETE、PUT请求并不支持,SpringMVC有一个过滤器,可以将浏览器的POST请求改为指定的请求方式,发送给的控制器方法。

用法如下:
在web.xml中配置过滤器

<!-- 请求方式过滤器 -->
<filter>
  <filter-name>httpMethodFilter</filter-name>
  <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>httpMethodFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

编写控制器方法

@Controller
@RequestMapping("/c7")
public class MyController7 {
  @DeleteMapping("/delete")
  public String testDelete(){
    System.out.println("删除方法");
    return "baizhan";
   }


  @PutMapping("/put")
  public String testPut(){
    System.out.println("修改方法");
    return "baizhan";
   }
}

在jsp中编写表单

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>DELETE、PUT提交</title>
  </head>
  <body>
    <!-- 删除 -->
     <%-- 提交DELETE、PUT请求,表单必须提交方式为post --%>
     <%-- 表单中有一个隐藏域,name值为_method,value值为提交方式 --%>
    <form action="/c7/delete" method="post">
      <input type="hidden" name="_method" value="DELETE">
      <input type="submit" value="删除">
    </form>
    <hr/>
    <!-- 修改 -->
    <form action="/c7/put" method="post">
      <input type="hidden" name="_method" value="PUT">
      <input type="submit" value="修改">
    </form>
  </body>
</html>

ResponseBody

作用:方法返回的对象转换为JSON格式,并将JSON数据直接写入到输出流中,使用此注解后不会再经过视图解析器。使用该注解可以处理Ajax请求。

位置:方法上方或方法返回值前

编写jsp页面,发送ajax请求

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>ajax请求</title>
    <script src="/js/jquery-2.1.1.min.js"></script>
    <script>
      $(function () {
        $("#btn").click(function () {
          var name = $("#name").val();
          var sex = $("#sex").val();
          $.get("/c8/addStudent",{"name":name,"sex":sex},function (data){
            console.log(data);
           });
         });
       });
    </script>
  </head>
  <body>
     姓名:<input id="name"/><br/>
     性别:<input id="sex"/><br/>
    <input type="button" value="提交" id="btn"/>
  </body>
</html>

由于jsp页面中引入jQuery的js文件,而SpringMVC会拦截所有资源,造成jquery.js失效,需要在SpringMVC核心配置文件中放行静态资源。

<!-- 放行静态资源 -->
<mvc:default-servlet-handler />

编写结果实体类,该实体类会封装一个请求的结果

// 请求的结果对象
public class Result {  
  private boolean flag; // 请求是否成功  
  private String message; // 请求提示信息   
  // 省略getter/setter/构造方法
}

编写控制器

@PostMapping("/addStudent")
@ResponseBody
public Result addStudent(String name, String sex) {
  // 输出接受到的参数,模拟添加学生
  System.out.println(name+":"+sex);
  // 返回添加结果
  Result result = new Result(true, "添加学生成功!");
  return result;
}

SpringMVC会将Result对象转为JSON格式写入输出流,而SpringMVC默认使用的JSON转换器是jackson,需要在pom中添加jackson依赖。

<!-- jackson -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.9.0</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.0</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.9.0</version>
</dependency>

RestController

如果一个控制器类下的所有控制器方法都返回JSON格式数据且不进行跳转,可以使用@RestController代替@Controller,此时每个方法上的@ResponseBody都可以省略。

@RestController
@RequestMapping("/c8")
public class MyController8 {  
  @PostMapping("/addStudent")  
  public Result addStudent(String name, String sex) { 
    // 输出接受到的参数,模拟添加学生   
    System.out.println(name+":"+sex);    
    // 返回结果    
    Result result = new Result(true, "添加学生成功!"); 
    return result;  
   }
}

静态支援映射

当在DispatcherServlet的中配置拦截 “/” 时,除了jsp文件不会拦截以外,其他所有的请求都会经过前端控制器进行匹配。此时静态资源例如css、js、jpg等就会被前端控制器拦截,导致不能访问,出现404问题。想要正常映射静态资源共有三种方案:
配置静态资源筛查器
在SpringMVC的配置文件中配置<mvc:default-servlet-handler />后,会在Spring容器中创建一个资源检查器,它对进入DispatcherServlet的URL进行筛查,如果不是静态资源,才由DispatcherServlet处理。
修改SpringMVC核心配置文件:

<mvc:default-servlet-handler/>

配置静态资源映射器
SpringMVC模块提供了静态资源映射器组件,通过mvc:resources标签配置静态资源映射器,配置后的路径不会由DispatcherServlet处理。

修改SpringMVC核心配置文件:

**<!--配置静态资源映射器-->**
<!-- mapping:配置请求的URL location:资源路径-->
<mvc:resources mapping="/img/" location="/img/"/><mvc:resources mapping="/js/" location="/js/"/>

配置默认Servlet处理静态资源
在web.xml可以配置默认Servlet处理静态资源,该Servlet由tomcat提供,它会直接访问静态资源不进行其他操作。这样就避免了使用DispatcherServlet对静态资源的拦截:

修改web.xml:

<servlet-mapping>  
  <servlet-name>default</servlet-name>  
  <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>  
  <servlet-name>default</servlet-name> 
  <url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping>  
  <servlet-name>default</servlet-name>  
  <url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>  
  <servlet-name>default</servlet-name>  
  <url-pattern>*.png</url-pattern>
</servlet-mapping>

RequestBody

作用:将请求中JSON格式的参数转为JAVA对象

位置:写在方法参数前
AJAX请求发送JSON格式的参数


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>ajax请求</title>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
    <script>
        $(function () {
            $("#btn").click(function () {
                var name = $("#name").val();
                var sex = $("#sex").val();
                $.get("/c8/addStudent",{"name":name,"sex":sex},function (data){
                    console.log(data);
                });
            });
        });
    </script>
</head>
<body>
姓名:<input id="name"/><br/>
性别:<input id="sex"/><br/>
<input type="button" value="提交" id="btn"/>
</body>
</html>

编写控制器

 @RequestMapping("/addStudent")
@ResponseBody
public Result addStudent2(@RequestBody Student student) {
  System.out.println(student);
  // 返回添加结果
  Result result = new Result(true, "添加学生成功!");
  return result;
}

springMVC上传

原生上传

原生上传1)
上传是Web工程中很常见的功能,SpringMVC框架简化了文件上传的代码,我们首先使用JAVAEE原生方式上传文件:

创建新的SpringMVC项目,在web.xml中将项目从2.3改为3.1,即可默认开启el表达式

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="http://xmlns.jcp.org/xml/ns/javaee"
     xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
     id="WebApp_ID" version="3.1">

编写上传表单

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>上传</title>
  </head>
  <body>
    <h3>文件上传</h3>
     <%-- 上传表单的提交方式必须是post --%>
     <%-- enctype属性为multipart/form-data,意思是不对表单数据进行编码 --%>
    <form action="/fileUpload" method="post" enctype="multipart/form-data">
       <%-- 文件选择控件,类型是file,必须要有name属性--%>
       选择文件:<input type="file" name="upload"/>
      <input type="submit" value="上传"/>
    </form>
  </body>
</html>

接收请求体数据:

@RequestMapping("/fileUpload")
public String upload(HttpServletRequest request) throws Exception {
  // 获取输入流
  ServletInputStream is = request.getInputStream();
  // 从输入流获取请求体数据
  int i = 0;
  while ((i=is.read())!=-1){
    System.out.println((char)i);
   }
  return "index";
}

原生上传2)
接下来需要分析请求体中的文件项,并将数据写入磁盘,此时需要借助文件上传工具
引入文件上传依赖:

<!-- 文件上传 -->
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.1</version>
</dependency>
<dependency>
  <groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.4</version>
</dependency>

编写控制器接收上传请求,控制器进行三步操作:

创建文件夹,存放上传文件。
分析请求体,找到上传文件数据。
将文件数据写入文件夹。

@RequestMapping("/fileUpload")
public String upload(HttpServletRequest request) throws Exception {
  // 创建文件夹,存放上传文件
  // 1.设置上传文件夹的真实路径
  String realPath = request.getSession().getServletContext().getRealPath("/upload");


  // 2.判断该目录是否存在,如果不存在,创建该目录
  File file = new File(realPath);
  if(!file.exists()){
    file.mkdirs();
   }


  // 分析请求体,找到上传文件数据
  // 1.创建磁盘文件工厂
  DiskFileItemFactory factory = new DiskFileItemFactory();
  // 2.创建上传数据分析器对象
  ServletFileUpload servletFileUpload = new ServletFileUpload(factory);
  // 3.利用分析器对象解析请求体,返回所有数据项
  List<FileItem> fileItems = servletFileUpload.parseRequest(request);
  // 4.遍历所有数据,找到文件项(非表单项)
  for (FileItem fileItem:fileItems){
    if(!fileItem.isFormField()){
      // 将文件数据写入文件夹
      // 1.获取文件名
      String name = fileItem.getName();
      // 2.将文件写入磁盘
      fileItem.write(new File(file,name));
      // 3.删除内存中的临时文件
      fileItem.delete();
     }
   }
  return "index";
}

springMVC方式上传

SpringMVC使用框架提供的文件解析器对象,可以直接将请求体中的文件数据转为MultipartFile对象,从而省略原生上传中分析请求体的步骤。
在SpringMVC核心配置文件配置文件解析器

<!-- 文件解析器对象,id名称必须是multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  <!-- 支持一次上传文件的总容量。单位:字节 100M = 100*1024*1024-->
  <property name="maxUploadSize" value="104857600"/>
  <!-- 文件名的编码方式-->
  <property name="defaultEncoding" value="utf-8"/>
</bean>

创建JSP表单

<form action="/fileUpload2" method="post" enctype="multipart/form-data">
  <input type="file" name="file"/>
  <input type="submit" value="上传"/>
</form>

编写控制器接收上传请求

// MultipartFile参数名必须和JSP文件空间的name属性一致
@RequestMapping("/fileUpload2")
public String upload2(MultipartFile file,HttpServletRequest request) throws IOException {
  // 创建文件夹,存放上传文件
  String realPath = request.getSession().getServletContext().getRealPath("/upload");
  File dir = new File(realPath);
  if(!dir.exists()){
    dir.mkdirs();
   }


  // 将上传的数据写到文件夹的文件中
  // 1.拿到上传的文件名
  String filename = file.getOriginalFilename();
  filename = UUID.randomUUID()+"_"+filename;
  // 2.创建空文件
  File newFile = new File(dir,filename);
  // 3.将数据写入空文件中
  file.transferTo(newFile);


  return "index";
}

上传多文件

SpringMVC支持一次性上传多个文件,写法如下:
创建jsp表单

<form action="/fileUpload3" method="post" enctype="multipart/form-data">
   用户名:<input name="username"/>
   文件1:<input type="file" name="files"/>
   文件2:<input type="file" name="files"/>
  <input type="submit" value="上传"/>
</form>

编写控制器接收上传请求

@RequestMapping("/fileUpload3")
public String upload3(MultipartFile files[],String username,HttpServletRequest request) throws Exception {
  System.out.println(username);
  //1.设置上传文件保存的文件夹
  String realPath = request.getSession().getServletContext().getRealPath("/upload");
  File dir = new File(realPath);
  if (!dir.exists()){
    dir.mkdirs();
   }
  //2.遍历数组,将上传文件保存到文件夹
  for(MultipartFile file:files){
    String filename = file.getOriginalFilename();
    filename = UUID.randomUUID()+"_"+filename;
    File newFile = new File(dir, filename);
    file.transferTo(newFile);
   }
  return "index";
}
在这里插入代码片

异步上传

之前的上传方案,在上传成功后都会跳转页面。而在实际开发中,很多情况下上传后不进行跳转,而是进行页面的局部刷新,比如:上传头像成功后将头像显示在网页中。这时候就需要使用异步文件上传。
编写JSP页面,引入jQuery和jQuery表单上传工具jquery.form.js

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>上传</title>
  <script src="/js/jquery-2.1.1.min.js"></script>
  <script src="/js/jquery.form.js"></script>
</head>
<body>
<h3>文件上传</h3>
<form id="ajaxForm" enctype="multipart/form-data" >
  <input type="file" name="file"/>
   <%-- 按钮类型不能是submit,否则会刷新页面  --%>
  <input type="button" value="上传头像" id="btn"/>
</form>
<%-- 上传头像后展示的位置 --%>
<img src="/" width="100" id="img">
<script>
  $(function () {
    $("#btn").click(function () {
      // 异步提交表单
      $("#ajaxForm").ajaxSubmit({
        url:"/fileUpload4",
        type:"post",
        success:function (data) {
          $("#img").attr("src",data);
         }
       })
     })
   })
</script>
</body>
</html>

编写控制器接收异步上传请求

@RequestMapping("/fileUpload4")
//不进行页面跳转
@ResponseBody
public String upload3(HttpServletRequest request, MultipartFile file) throws Exception {
  // 创建文件夹,存放上传文件。
  String realPath = request.getSession().getServletContext().getRealPath("/upload");
  File dir = new File(realPath);
  if (!dir.exists()){
    dir.mkdirs();
   }


  // 拿到上传文件名
  String filename = file.getOriginalFilename();
  filename = UUID.randomUUID()+"_"+filename;
  // 创建空文件
  File newFile = new File(dir, filename);
  // 将上传的文件写到空文件中
  file.transferTo(newFile);
  // 返回文件的路径
  return "/upload/"+filename;
}

跨服务器上传

在这里插入图片描述
由于文件占据磁盘空间较大,在实际开发中往往会将文件上传到其他服务器中,此时需要使用跨服务器上传文件。
解压tomcat作为图片服务器,在tomcat的webapps下创建upload目录作为文件上传目录。
修改tomcat的conf/web.xml文件,支持跨服上传。

<servlet>  
  <init-param>    
    <param-name>readonly</param-name>   
    <param-value>false</param-value>  
  </init-param>
</servlet>

修改tomcat的conf/server.xml文件,修改tomcat端口,修改完开启tomcat服务器

<Connector port="8081" protocol="HTTP/1.1"        connectionTimeout="20000" redirectPort="8443" />

编写JSP上传表单

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>上传</title>
    <script src="/js/jquery-2.1.1.min.js"></script>
    <script src="/js/jquery.form.js"></script>
  </head>
  <body>
    <h3>文件上传</h3>
    <form id="ajaxForm" enctype="multipart/form-data" >
      <input type="file" name="file"/>
      <input type="button" value="上传头像" id="btn"/>
    </form>
    <img src="/" width="100" id="img">
    <script>
      $(function () {
        $("#btn").click(function () {
          $("#ajaxForm").ajaxSubmit({
            url:"/fileUpload5",
            type:"post",
            success:function (data) {
              $("#img").attr("src",data);
             }
           })
         })
       })
    </script>
  </body>
</html>

添加跨服上传依赖

<!-- 跨服上传 -->
<dependency>
  <groupId>com.sun.jersey</groupId>
  <artifactId>jersey-core</artifactId>
  <version>1.18.1</version>
</dependency>
<dependency>
  <groupId>com.sun.jersey</groupId>
  <artifactId>jersey-client</artifactId>
  <version>1.18.1</version>
</dependency>

创建控制器方法,该方法在接受到上传请求后将文件保存到其他服务器上。

@RequestMapping("/fileUpload5")
@ResponseBody
public String upload4(HttpServletRequest request, MultipartFile file) throws Exception {
  // 设置跨服上传的服务器路径
  String path = "http://localhost:8081/upload/";
  // 获取上传的文件名
  String filename = file.getOriginalFilename();
  filename = UUID.randomUUID()+"_"+filename;


  // 跨服上传:
  // 1.创建客户端对象
  Client client = Client.create();
  // 2.使用客户端对象连接图片服务器
  WebResource resource = client.resource(path + filename);
  //3.传输数据
  resource.put(file.getBytes());


  return path+filename;
}

文件下载

将文件上传到服务器后,有时我们需要让用户下载上传的文件,接下来我们编写文件下载功能:

查询所有可下载的文件
编写控制器方法,查询所有可下载的文件,并跳转到下载页面

// 查询可下载的文件
@RequestMapping("/showFiles")
public String showFileDown(HttpServletRequest request, Model model){
  //1.获取下载文件路径集合。注:跨服务器上传中,网络路径无法获取文件列表。
  String path = request.getSession().getServletContext().getRealPath("/upload");
  File file = new File(path);
  String[] files = file.list();
  //2.将路径放入模型中,跳转到JSP页面
  model.addAttribute("files",files);
  return "download";
}

添加JSTL依赖

<!-- JSTL -->
<dependency>
  <groupId>org.apache.taglibs</groupId>
  <artifactId>taglibs-standard-spec</artifactId>
  <version>1.2.5</version>
</dependency>
<dependency>
  <groupId>org.apache.taglibs</groupId>
  <artifactId>taglibs-standard-impl</artifactId>
  <version>1.2.5</version>
</dependency>

编写下载页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
  <head>
    <title>下载</title>
  </head>
  <body>
    <h3>文件下载</h3>
     <%-- 遍历文件集合 --%>
    <c:forEach items="${files}" var="file">
      <a href="/download?fileName=${file}">${file}</a><br/>
    </c:forEach>
  </body>
</html>

编写下载控制器

// 文件下载
@RequestMapping("/download")
public void fileDown(HttpServletRequest request, HttpServletResponse response,String fileName) throws IOException {
  // 设置响应头
  response.setHeader("Content-Disposition","attachment;filename="+fileName);
  // 获取文件路径
  String path = request.getSession().getServletContext().getRealPath("/upload");
  File file = new File(path,fileName);
  // 获取字节输出流
  ServletOutputStream os = response.getOutputStream();
  // 使用输出流写出文件
  os.write(FileUtils.readFileToByteArray(file));
  os.flush();
  os.close();
}

将文件上传到服务器后,有时我们需要让用户下载上传的文件,接下来我们编写文件下载功能:

查询所有可下载的文件
编写控制器方法,查询所有可下载的文件,并跳转到下载页面

// 查询可下载的文件
@RequestMapping("/showFiles")
public String showFileDown(HttpServletRequest request, Model model){
  //1.获取下载文件路径集合。注:跨服务器上传中,网络路径无法获取文件列表。
  String path = request.getSession().getServletContext().getRealPath("/upload");
  File file = new File(path);
  String[] files = file.list();
  //2.将路径放入模型中,跳转到JSP页面
  model.addAttribute("files",files);
  return "download";
}

添加JSTL依赖

<!-- JSTL -->
<dependency>
  <groupId>org.apache.taglibs</groupId>
  <artifactId>taglibs-standard-spec</artifactId>
  <version>1.2.5</version>
</dependency>
<dependency>
  <groupId>org.apache.taglibs</groupId>
  <artifactId>taglibs-standard-impl</artifactId>
  <version>1.2.5</version>
</dependency>

编写下载页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
  <head>
    <title>下载</title>
  </head>
  <body>
    <h3>文件下载</h3>
     <%-- 遍历文件集合 --%>
    <c:forEach items="${files}" var="file">
      <a href="/download?fileName=${file}">${file}</a><br/>
    </c:forEach>
  </body>
</html>

编写下载控制器

// 文件下载
@RequestMapping("/download")
public void fileDown(HttpServletRequest request, HttpServletResponse response,String fileName) throws IOException {
  // 设置响应头
  response.setHeader("Content-Disposition","attachment;filename="+fileName);
  // 获取文件路径
  String path = request.getSession().getServletContext().getRealPath("/upload");
  File file = new File(path,fileName);
  // 获取字节输出流
  ServletOutputStream os = response.getOutputStream();
  // 使用输出流写出文件
  os.write(FileUtils.readFileToByteArray(file));
  os.flush();
  os.close();
}

springMVC异常处理

单个控制器异常处理

在系统当中, Dao、Service、Controller层代码出现都可能抛出异常。如果哪里产生异常就在哪里处理,则会降低开发效率。所以一般情况下我们会让异常向上抛出,最终到达DispatcherServlet中,此时SpringMVC提供了异常处理器进行异常处理,这样可以提高开发效率。
在这里插入图片描述
处理单个Controller的异常:

@Controller
public class MyController {
  @RequestMapping("/t1")
  public String t1(){
    String str = null;
    //     str.length();
    //     int flag = 1/0;
    int[] arr = new int[1];
    arr[2] = 10;
    return "index";
   }


  /**
   * 异常处理方法
   * @param ex 异常对象
   * @param model 模型对象
   * @return
   */
  // 添加@ExceptionHandler,表示该方法是处理异常的方法,属性为处理的异常类
  @ExceptionHandler({java.lang.NullPointerException.class,java.lang.ArithmeticException.class})
  public String exceptionHandle1(Exception ex, Model model){
    // 向模型中添加异常对象
    model.addAttribute("msg",ex);
    // 跳转到异常页面
    return "error";
   }


  // 方法一不能处理的异常交给方法二处理
  @ExceptionHandler(java.lang.Exception.class)
  public String exceptionHandle2(Exception ex, Model model){
    model.addAttribute("msg",ex);
    return "error2";
   }
}

异常页面error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>出错了!</title>
  </head>
  <body>
    <h3>ERROR 发生异常!${msg}</h3>
  </body>
</html>

异常页面error2.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>出错了!</title>
  </head>
  <body>
    <h3>ERROR2 发生严重异常!${msg}</h3>
  </body>
</html>

全局异常处理

在控制器中定义异常处理方法只能处理该控制器类的异常,要想处理所有控制器的异常,需要定义全局异常处理类。
编写另一个有异常的控制器类

@Controller
public class MyController2 {
  @RequestMapping("/t2")
  public String t2(){
    int[] arr = new int[1];
    arr[2] = 10;
    return "index";
   }
}

编写全局异常处理器类

// 全局异常处理器类,需要添加@ControllerAdvice
@ControllerAdvice
public class GlobalExceptionHandler {
  /**
   * 异常处理方法
   *
   * @param ex   异常对象
   * @param model 模型对象
   * @return
   */
  // 添加@ExceptionHandler,表示该方法是处理异常的方法,属性为处理的异常类
  @ExceptionHandler({java.lang.NullPointerException.class, java.lang.ArithmeticException.class})
  public String exceptionHandle1(Exception ex, Model model) {
    // 向模型中添加异常对象
    model.addAttribute("msg", ex);
    // 跳转到异常页面
    return "error";
   }


  // 方法一不能处理的异常交给方法二处理
  @ExceptionHandler(java.lang.Exception.class)
  public String exceptionHandle2(Exception ex, Model model) {
    model.addAttribute("msg", ex);
    return "error2";
   }
}

自定义异常处理器

以上方式都是使用的SpringMVC自带的异常处理器进行异常处理,我们还可以自定义异常处理器处理异常:

// 自定义异常处理器实现HandlerExceptionResolver接口,并放入Spring容器中
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {
  @Override
  public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
    ModelAndView modelAndView = new ModelAndView();
    if (e instanceof NullPointerException) {
      modelAndView.setViewName("error");
     } else {
      modelAndView.setViewName("error2");
     }
    modelAndView.addObject("msg", e);
    return modelAndView;
   }
}

springMVC拦截器

在这里插入图片描述

SpringMVC的拦截器(Interceptor)也是AOP思想的一种实现方式。它与Servlet的过滤器(Filter)功能类似,主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。
拦截器和过滤器的区别
拦截器是SpringMVC组件,而过滤器是Servlet组件。
拦截器不依赖Web容器,过滤器依赖Web容器。
拦截器只能对控制器请求起作用,而过滤器则可以对所有的请求起作用。
拦截器可以直接获取IOC容器中的对象,而过滤器就不太方便获取。

拦截器使用

使用maven创建SprinMVC的web项目

创建控制器方法

@RequestMapping("/m1")
public String m1(){
  System.out.println("控制器方法");
  return "result";
}

创建拦截器类,该类实现HandlerInterceptor接口,需要重写三个方法:

preHandle:请求到达Controller前执行的方法,返回值为true通过拦截器,返回值为false被拦截器拦截。
postHandle:跳转到JSP前执行的方法
afterCompletion:跳转到JSP后执行的方法

// 拦截器类
public class MyInterceptor implements HandlerInterceptor {
  // 请求到达Controller前执行
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    System.out.println("请求到达Controller前");
    // 如果return false则无法到达Controller
    return true;
   }


  // 跳转到JSP前执行,此时可以向Request域添加数据
  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
    System.out.println("跳转到JSP前");
    request.setAttribute("name","百战");
   }


  // 跳转到JSP后执行,此时已经不能向Request域添加数据
  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    System.out.println("跳转到JSP后");
    request.setAttribute("age",10);
   }
}

编写JSP页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>结果</title>
  </head>
  <body>
    <h3>name:${requestScope.name}</h3>
    <h3>age:${requestScope.age}</h3>
  </body>
</html>

在SpringMVC核心配置文件中配置拦截器

<!-- 配置拦截器-->
<mvc:interceptors>  
  <mvc:interceptor>    
    <!-- 配置拦截器的作用路径-->    
    <mvc:mapping path="/**"/>    
    <!-- 拦截器对象 -->    
    <bean class="com.itbaizhan.interceptor.MyInterceptor"/> 
  </mvc:interceptor>
</mvc:interceptors>

全局拦截器

<!-- 配置拦截器 -->
<mvc:interceptors>  
  <!-- 全局拦截器 -->  
  <bean class="com.itbaizhan.interceptor.MyInterceptor">
  </bean>
</mvc:interceptors>

拦截器链与执行顺序

在这里插入图片描述
如果一个URL能够被多个拦截器所拦截,全局拦截器最先执行,其他拦截器根据配置文件中配置的从上到下执行,接下来我们再配置一个拦截器:

编写拦截器类

public class MyInterceptor2 implements HandlerInterceptor {
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    System.out.println("拦截器2:请求到达Controller前");
    return true;
   }


  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
    System.out.println("拦截器2:跳转到JSP前");
   }


  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    System.out.println("拦截器2:跳转到JSP后");
   }
}

配置拦截器链

<!-- 配置拦截器 -->
<mvc:interceptors>
  <!-- 拦截器1 -->
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="com.itbaizhan.interceptor.MyInterceptor"/>
  </mvc:interceptor>
  <!-- 拦截器2 -->
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="com.itbaizhan.interceptor.MyInterceptor2"/>
  </mvc:interceptor>
</mvc:interceptors>

在这里插入图片描述
结论:

preHandle()顺序执行,postHandle()、afterComletion()逆序执行。
只要有一个preHandle()拦截,后面的preHandle(),postHandle()都不会执行。
只要相应的preHandle()放行,afterComletion()就会执行。

拦截器过滤敏感词案例

接下来我们编写一个拦截器案例,需求如下:

在系统中,我们需要将所有响应中的一些敏感词替换为***,此时可以使用拦截器达到要

@RequestMapping("/m2")
public String m2(Model model){
  model.addAttribute("name","大笨蛋");
  return "result";
}

编写敏感词拦截器

// 敏感词拦截器
public class SensitiveWordInterceptor implements HandlerInterceptor {
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    return true;
   }


  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
    // 敏感词列表
    String[] sensitiveWords = {"坏人","暴力","笨蛋"};
    // 获取model中所有数据
    Map<String, Object> model = modelAndView.getModel();
    Set<Map.Entry<String, Object>> entries = model.entrySet();


    // 遍历model
    for (Map.Entry<String, Object> entry : entries) {
      String key = entry.getKey();
      String value = entry.getValue().toString();
      // 将model值和敏感词列表遍历比对
      for (String sensitiveWord : sensitiveWords) {
        // 如果model值包含敏感词,则替换
        if(value.contains(sensitiveWord)){
          String newStr = value.replaceAll(sensitiveWord, "***");
          model.put(key, newStr);
         }
       }
     }
   }
}

配置拦截器

<!-- 配置拦截器-->
<mvc:interceptors>
  <!-- 敏感词拦截器 -->
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <bean class="com.itbaizhan.interceptor.SensitiveWordInterceptor"></bean>
  </mvc:interceptor>
</mvc:interceptors>

SpringMVC跨域请求_同源策略

同源策略是浏览器的一个安全功能。同源,指的是两个URL的协议,域名,端口相同。浏览器出于安全方面的考虑,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。

哪些不受同源策略限制:

页面中的跳转、表单提交不会受到同源策略限制的。
静态资源引入也不会受到同源策略限制。如嵌入到页面中的

SpringMVC跨域请求_跨域请求

当请求URL的协议、域名、端口三者中任意一个与当前页面URL不同时即为跨域。浏览器执行JavaScript脚本时,会检查当前请求是否同源,如果不是同源资源,就不会被执行。

在这里插入图片描述

SpringMVC跨域请求_控制器接收跨域请求

SpringMVC提供了注解@CrossOrigin解决跨域问题。用法如下:

@RequestMapping("/m3")
@ResponseBody
// 如果请求从http://localhost:8080发出,则允许跨域访问
@CrossOrigin("http://localhost:8080")
public String m3(){
  System.out.println("测试跨域请求");
  return "success";
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_68027145/article/details/126908900

智能推荐

hive使用适用场景_大数据入门:Hive应用场景-程序员宅基地

文章浏览阅读5.8k次。在大数据的发展当中,大数据技术生态的组件,也在不断地拓展开来,而其中的Hive组件,作为Hadoop的数据仓库工具,可以实现对Hadoop集群当中的大规模数据进行相应的数据处理。今天我们的大数据入门分享,就主要来讲讲,Hive应用场景。关于Hive,首先需要明确的一点就是,Hive并非数据库,Hive所提供的数据存储、查询和分析功能,本质上来说,并非传统数据库所提供的存储、查询、分析功能。Hive..._hive应用场景

zblog采集-织梦全自动采集插件-织梦免费采集插件_zblog 网页采集插件-程序员宅基地

文章浏览阅读496次。Zblog是由Zblog开发团队开发的一款小巧而强大的基于Asp和PHP平台的开源程序,但是插件市场上的Zblog采集插件,没有一款能打的,要么就是没有SEO文章内容处理,要么就是功能单一。很少有适合SEO站长的Zblog采集。人们都知道Zblog采集接口都是对Zblog采集不熟悉的人做的,很多人采取模拟登陆的方法进行发布文章,也有很多人直接操作数据库发布文章,然而这些都或多或少的产生各种问题,发布速度慢、文章内容未经严格过滤,导致安全性问题、不能发Tag、不能自动创建分类等。但是使用Zblog采._zblog 网页采集插件

Flink学习四:提交Flink运行job_flink定时运行job-程序员宅基地

文章浏览阅读2.4k次,点赞2次,收藏2次。restUI页面提交1.1 添加上传jar包1.2 提交任务job1.3 查看提交的任务2. 命令行提交./flink-1.9.3/bin/flink run -c com.qu.wc.StreamWordCount -p 2 FlinkTutorial-1.0-SNAPSHOT.jar3. 命令行查看正在运行的job./flink-1.9.3/bin/flink list4. 命令行查看所有job./flink-1.9.3/bin/flink list --all._flink定时运行job

STM32-LED闪烁项目总结_嵌入式stm32闪烁led实验总结-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏6次。这个项目是基于STM32的LED闪烁项目,主要目的是让学习者熟悉STM32的基本操作和编程方法。在这个项目中,我们将使用STM32作为控制器,通过对GPIO口的控制实现LED灯的闪烁。这个STM32 LED闪烁的项目是一个非常简单的入门项目,但它可以帮助学习者熟悉STM32的编程方法和GPIO口的使用。在这个项目中,我们通过对GPIO口的控制实现了LED灯的闪烁。LED闪烁是STM32入门课程的基础操作之一,它旨在教学生如何使用STM32开发板控制LED灯的闪烁。_嵌入式stm32闪烁led实验总结

Debezium安装部署和将服务托管到systemctl-程序员宅基地

文章浏览阅读63次。本文介绍了安装和部署Debezium的详细步骤,并演示了如何将Debezium服务托管到systemctl以进行方便的管理。本文将详细介绍如何安装和部署Debezium,并将其服务托管到systemctl。解压缩后,将得到一个名为"debezium"的目录,其中包含Debezium的二进制文件和其他必要的资源。注意替换"ExecStart"中的"/path/to/debezium"为实际的Debezium目录路径。接下来,需要下载Debezium的压缩包,并将其解压到所需的目录。

Android 控制屏幕唤醒常亮或熄灭_android实现拿起手机亮屏-程序员宅基地

文章浏览阅读4.4k次。需求:在诗词曲文项目中,诗词整篇朗读的时候,文章没有读完会因为屏幕熄灭停止朗读。要求:在文章没有朗读完毕之前屏幕常亮,读完以后屏幕常亮关闭;1.权限配置:设置电源管理的权限。

随便推点

目标检测简介-程序员宅基地

文章浏览阅读2.3k次。目标检测简介、评估标准、经典算法_目标检测

记SQL server安装后无法连接127.0.0.1解决方法_sqlserver 127 0 01 无法连接-程序员宅基地

文章浏览阅读6.3k次,点赞4次,收藏9次。实训时需要安装SQL server2008 R所以我上网上找了一个.exe 的安装包链接:https://pan.baidu.com/s/1_FkhB8XJy3Js_rFADhdtmA提取码:ztki注:解压后1.04G安装时Microsoft需下载.NET,更新安装后会自动安装如下:点击第一个傻瓜式安装,唯一注意的是在修改路径的时候如下不可修改:到安装实例的时候就可以修改啦数据..._sqlserver 127 0 01 无法连接

js 获取对象的所有key值,用来遍历_js 遍历对象的key-程序员宅基地

文章浏览阅读7.4k次。1. Object.keys(item); 获取到了key之后就可以遍历的时候直接使用这个进行遍历所有的key跟valuevar infoItem={ name:'xiaowu', age:'18',}//的出来的keys就是[name,age]var keys=Object.keys(infoItem);2. 通常用于以下实力中 <div *ngFor="let item of keys"> <div>{{item}}.._js 遍历对象的key

粒子群算法(PSO)求解路径规划_粒子群算法路径规划-程序员宅基地

文章浏览阅读2.2w次,点赞51次,收藏310次。粒子群算法求解路径规划路径规划问题描述    给定环境信息,如果该环境内有障碍物,寻求起始点到目标点的最短路径, 并且路径不能与障碍物相交,如图 1.1.1 所示。1.2 粒子群算法求解1.2.1 求解思路    粒子群优化算法(PSO),粒子群中的每一个粒子都代表一个问题的可能解, 通过粒子个体的简单行为,群体内的信息交互实现问题求解的智能性。    在路径规划中,我们将每一条路径规划为一个粒子,每个粒子群群有 n 个粒 子,即有 n 条路径,同时,每个粒子又有 m 个染色体,即中间过渡点的_粒子群算法路径规划

量化评价:稳健的业绩评价指标_rar 海龟-程序员宅基地

文章浏览阅读353次。所谓稳健的评估指标,是指在评估的过程中数据的轻微变化并不会显著的影响一个统计指标。而不稳健的评估指标则相反,在对交易系统进行回测时,参数值的轻微变化会带来不稳健指标的大幅变化。对于不稳健的评估指标,任何对数据有影响的因素都会对测试结果产生过大的影响,这很容易导致数据过拟合。_rar 海龟

IAP在ARM Cortex-M3微控制器实现原理_value line devices connectivity line devices-程序员宅基地

文章浏览阅读607次,点赞2次,收藏7次。–基于STM32F103ZET6的UART通讯实现一、什么是IAP,为什么要IAPIAP即为In Application Programming(在应用中编程),一般情况下,以STM32F10x系列芯片为主控制器的设备在出厂时就已经使用J-Link仿真器将应用代码烧录了,如果在设备使用过程中需要进行应用代码的更换、升级等操作的话,则可能需要将设备返回原厂并拆解出来再使用J-Link重新烧录代码,这就增加了很多不必要的麻烦。站在用户的角度来说,就是能让用户自己来更换设备里边的代码程序而厂家这边只需要提供给_value line devices connectivity line devices