SpringMVC_Day2_常用注解、放行、JSON支持、文件上传、异常处理

@RestController                 // @Controller+/@ResponseBody
@RequestMapping("/test")
public class TestController {
​
    @GetMapping("/hello")
    public String str() {
        return "hello @ResponseBody...";
    }
}

SpringMVC常用注解

springmvc.xml常用配置

扫包、静态资源放行、开启注解驱动

 <!--扫描包-->
<context:component-scan base-package="cn.xxx"/>
​
<!--静态资源放行-->
<mvc:default-servlet-handler/>
​
<!--开启注解驱动-->
<mvc:annotation-driven />

@PathVariable

用于接收Rest风格请求的路径参数;将方法形参和url的占位符进行绑定

源码:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
    @AliasFor("name")
    String value() default "";
​
    @AliasFor("value")
    String name() default "";
​
    boolean required() default true;
}
  • name/value:当url中的占位符和方法形参名称不一致时,可以使用name/value属性进行绑定

  • required:是否必须提供占位符,默认为true

测试:

@Controller
@RequestMapping("/demo01")
public class Demo01Controller_PathVariable {
​
    @GetMapping("/{id}/{name}")
    public void test(
            @PathVariable Integer id,
            @PathVariable(value = "name") String cityName,                  // 把url上的name值赋值给cityName
            HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("id: "+id+"<hr />");
        response.getWriter().write("cityName: "+cityName+"<hr />");
    }
​
    @GetMapping("/{id}")
    public void test2(
            @PathVariable Integer id,
            @PathVariable(value = "aaa",required = false) String cityName,                  // 没有aaa占位符也没关系
            HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("id: "+id+"<hr />");
        response.getWriter().write("cityName: "+cityName+"<hr />");
    }
​
}

@RequestParam

接收前端提交的参数(POST/GET提价都可以接收);当接收的参数的变量名与表单的name属性不一样时可以使用@RequestParam来绑定,同时可以封装其他类型的对象,如List、Set、Map

源码:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    @AliasFor("name")
    String value() default "";
​
    @AliasFor("value")
    String name() default "";
​
    boolean required() default true;
​
    String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}
  • name/value:当前端传递的参数名称和方法形参名称不一致时,可以使用name/value属性进行绑定;

  • required:是否必须提供传递此参数,默认为true;

  • defaultValue:如果前端没有传递此参数,默认为defaultValue的值;

测试:

@Controller
@RequestMapping("/demo02")
public class Demo02Controller_RequestParam {
​
    @GetMapping("/demo01")
    public void demo01(Integer id,
                       @RequestParam("name") String cityName,                  // 将前端传递的name参数与cityName进行绑定(name参数必须传递)
                       HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("id: " + id + "<hr />");
        response.getWriter().write("cityName: " + cityName + "<hr />");
    }
​
​
    @GetMapping("/demo02")
    public void demo02(@RequestParam(required = false) Integer id,                       // id可以不传递
                       @RequestParam(defaultValue = "guangzhou") String cityName,        // cityName参数如果没有传递默认为guangzhou
                       HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("id: " + id + "<hr />");
        response.getWriter().write("cityName: " + cityName + "<hr />");
    }
​
​
    /**
     * map类型
     * @param maps
     * @param response
     * @throws IOException
     */
    @GetMapping("/demo03")
    public void demo03(@RequestParam Map<String, Object> maps,
                       HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("id: " + maps.get("id") + "<hr />");
        response.getWriter().write("cityName: " + maps.get("cityName") + "<hr />");
    }
​
    /**
     * List类型
     * @param ids
     * @param response
     * @throws IOException
     */
    @PostMapping("/demo04")
    public void demo04(@RequestParam("ids") List<Integer> ids,
                       HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("id: " + ids.toString() + "<hr />");
    }
}

@CookieValue

获取Cookie中的数据

源码:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CookieValue {
    @AliasFor("name")
    String value() default "";
​
    @AliasFor("value")
    String name() default "";
​
    boolean required() default true;
​
    String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}
  • name/value:Cookie中的参数名称和方法形参名称不一致时,可以使用name/value属性进行绑定;

  • required:是否必须提供此参数Cookie参数,默认为true;

  • defaultValue:如果Cookie中没有此参数,默认为defaultValue的值;

测试:

@Controller
@RequestMapping("/demo03")
public class Demo03Controller_CookieValue {
​
    @GetMapping("/demo01")
    public void demo01(@CookieValue String JSESSIONID, HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("JSESSIONID: " + JSESSIONID + "<hr />");
    }
​
​
    @GetMapping("/demo02")
    public void demo02(HttpServletResponse response) throws IOException {
​
        Cookie cookie = new Cookie("cityName", "guangzhou");
​
        response.addCookie(cookie);
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("ok");
    }
​
    @GetMapping("/demo03")
    public void demo03(@CookieValue String cityName, HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("cityName: " + cityName + "<hr />");
        response.getWriter().write("ok");
    }
}

@RequestHeader

获取请求头中的数据

源码:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader {
    @AliasFor("name")
    String value() default "";
​
    @AliasFor("value")
    String name() default "";
​
    boolean required() default true;
​
    String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}

测试:

@Controller
@RequestMapping("/demo04")
public class Demo04Controller_RequestHeader {
​
    @GetMapping("/demo01")
    public void demo01(@RequestHeader String Host,
                       HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("Host: " + Host + "<hr />");
    }
​
​
    @GetMapping("/demo02")
    public void demo02(@RequestHeader String username,
                       HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("username: " + username + "<hr />");
    }
}

@SessionAttribute

从session中获取一个值封装到参数中

源码:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionAttribute {
    @AliasFor("name")
    String value() default "";
​
    @AliasFor("value")
    String name() default "";
​
    boolean required() default true;
}

测试:

@Controller
@RequestMapping("/demo05")
public class Demo05Controller_SessionAttribute {
​
    @GetMapping("/demo01")
    public String demo01(HttpSession session) throws IOException {
​
        session.setAttribute("loginUser", "zhangsan");
​
        return "redirect:/demo05/demo02";
    }
​
    /**
     * 从session中取出loginUser(必须要有这个属性)
     * @param loginUser
     * @param response
     * @throws IOException
     */
    @GetMapping("/demo02")
    public void demo02(@SessionAttribute String loginUser,
                       HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("loginUser: " + loginUser + "<hr />");
    }
}

@SessionAttributes

把BindingAwareModelMap中的指定的key或者指定的属性的值也存入一份进session域

tips:@SessionAttributes注解并不能把存入request域的值也存入session中

源码:

@Target({ElementType.TYPE})     // 标注在类上的
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {
    @AliasFor("names")
    String[] value() default {};
​
    @AliasFor("value")
    String[] names() default {};
​
    Class<?>[] types() default {};
}

测试:

@SessionAttributes(value = {"username"}, types = {Integer.class})
@Controller
@RequestMapping("/demo06")
public class Demo06Controller_SessionAttributes {
​
    @GetMapping("/demo01")
    public String demo01(Map<String, Object> map, HttpServletRequest request) throws IOException {
​
        map.put("username", "admin");
        map.put("password", "123");
        map.put("age", 23);
        map.put("tall", 1.78);
​
        // 存入request域的值不能被存入session
        request.setAttribute("test",111);
​
        return "redirect:/demo06/demo02";
    }
​
​
    @GetMapping("/demo02")
    public void demo02(HttpSession session, HttpServletResponse response) throws IOException {
​
        response.setContentType("text/html;charset=utf8");
​
        response.getWriter().write("username: " + session.getAttribute("username") + "<hr />");
        response.getWriter().write("password: " + session.getAttribute("password") + "<hr />");
        response.getWriter().write("age: " + session.getAttribute("age") + "<hr />");
        response.getWriter().write("tall: " + session.getAttribute("tall") + "<hr />");
        response.getWriter().write("test: " + session.getAttribute("test") + "<hr />");
    }
}

@RequestAttribute

获取request域中的数据;

测试:

@Controller
@RequestMapping("/demo07")
public class Demo07Controller_RequestAttribute {


    @GetMapping("/demo01")
    public String demo01(Model model) throws IOException {

        // 在请求域中添加几个属性
        model.addAttribute("username", "zhangsan");
        model.addAttribute("password", "123");

        return "/demo07/demo02";
    }

    @GetMapping("/demo02")
    public void demo02(
                        @RequestAttribute("username") String name,      // 获取请求域中的username属性值
                        @RequestAttribute String password,              // 获取请求域中password属性值
                        HttpServletResponse response
    ) throws IOException {

        response.getWriter().println("username: "+name);
        response.getWriter().println("password: "+password);
    }
}

@ModelAttribute

@ModelAttribute注解可以作用于方法和参数上

  • 作用于方法上:在执行Controller其他所有方法之前执行@ModelAttribute注解标注的方法

  • 作用于参数上:获取ModelAttribute标注的方法中BingMap中的某个值到某个属性到形参上,并且此次的BingMap会传递到下一个handler方法;BingMap中的值以前端传递的为准,如果前端传递了参数则以前端为准,如果没有传递参数则保持@ModelAttribute标注方法中的默认值;

源码:

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {
    @AliasFor("name")
    String value() default "";
​
    @AliasFor("value")
    String name() default "";
​
    boolean binding() default true;
}

测试:

@Controller
@RequestMapping("/demo08")
public class Demo08Controller_ModelAttribute {
    @ModelAttribute
    public void model(Model model, ModelMap modelMap, Map map) {
        model.addAttribute("username", "zhangsan");
        modelMap.addAttribute("password", "admin");
        map.put("email","zhangsan@qq.com");
    }
​
    @RequestMapping("/demo01")
    public void demo01(
            @ModelAttribute("username") String username,            // 注意: 形参名和BindingMap中的key一样也要写
            @ModelAttribute("password") String password,
            @ModelAttribute("email") String email,
            HttpServletResponse response
    ) throws Exception {
​
        response.getWriter().println("username: " + username);
        response.getWriter().println("password: " + password);
        response.getWriter().println("email: " + email);
    }
}

Tips:@ModelAttribute只能获取BingMap中的数据,不能获取request中的数据;

应用场景:现在需要修改一个City信息,有某些应用信息前端不提交(默认为null),

表单:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/demo08/demo01">
    城市ID:<input type="text" name="id">
    城市名称:<input type="text" name="cityName">
    GDP:<input type="text" name="GDP">
    是否省会:<input type="text" name="capital">
​
    <input type="submit">
</form>
</body>
</html>

Controller

@Controller
@RequestMapping("/demo09")
public class Demo09Controller_ModelAttribute02 {
    /**
     * ModelAttribute标注的方法在所有的handler方法之前执行
     *
     * @param maps
     * @throws IOException
     */
    @ModelAttribute
    public void model(Map<String, Object> maps) throws IOException {
​
        maps.put("city", new City(1, "南宁", 4700.00, true));
        System.out.println("model方法执行了...");
    }
​
    // 如果前端没有传递则以@ModelAttribute标注的为准,如果传递了则以前端参数为准
    @RequestMapping("/demo01")
    public void demo01(@ModelAttribute("city") City city, HttpServletResponse response) throws Exception{        
​
        response.setContentType("text/html;charset=utf8");
        response.getWriter().println(city);
    }
}

SpringMVC的放行

SpringMVC提供的拦截规则

  • .form:代表以.form结尾的后缀请求都会进入springmvc管

  • /:代表除了JSP以外拦截所有,html、css、js等静态web资源也会拦截

  • /*:拦截所有请求

缺省Servlet

依赖:

//Servlet 容器支持
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>8.5.41</version>
    <!--此jar包在tomcat中已经有了,但是如果不引入此依赖项目则会报错,因此依赖范围provided就行-->
    <scope>provided</scope>
</dependency>

//MVC架构支持
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.0.11</version>
</dependency>

Maven的依赖范围

  • compile:编译时存在,测试时存在,运行时存在,会打入war包;(普通jar包)

  • provided:编译时存在,测试时存在,运行时不存在,不会打入war包;(servlet-api)

  • runtime:编译时不存在,测试时存在,运行时存在,会打入war包(mysql驱动)

  • Test:编译时不存在,测试时存在,运行时不存在,不会打入war包(Junit)

  • system:编译时存在,测试时存在,运行时存在,不会打入war包(仅限于本地环境)

springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!-- 配置 SpringMVC 的前端控制器 -->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- 配置 SpringMVC 的前端控制器的映射 -->
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- 配置缺省 Servlet 的映射 -->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>/resources/*</url-pattern>
    </servlet-mapping>

</web-app>

springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">
​
    <!-- 开启 SpringMVC 的注解支持 -->
    <mvc:annotation-driven/>
​
    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
​
</beans>

Controller

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
​
@Controller
public class HelloController {
​
    @RequestMapping("/hello")
    public String hello() {
        return "hello";
    }
​
}

/WEB-IINF/views/hello.jsp

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello Page</title>
</head>
<body>
    <h1>Hello, SpringMVC!</h1>
</body>
</html>

启动服务器,访问http://localhost:8080/hello可以看到 SpringMVC 处理的页面

启动服务器,访问http://localhost:8080/hello可以看到 SpringMVC 处理的页面

通过以上步骤,你可以验证 SpringMVC 和缺省 Servlet 的不同映射和处理方式。

缺省servlet放行原理是:如果是配置的后缀请求直接进入缺省servlet,而不是进入SpringMVC进行处理(不会经过SpringVMC)

Tips:配置缺省servlet放行时,SpringMVC的拦截规则不能为/,因为缺省Servlet不能放行JSP请求,如果SpringMVC拦截规则为/,然后缺省Servlet又不能放行,那么访问JSP时,会出现404

resources放行

springmvc.xml

<!--
    resources:配置springmvc放行
        mapping:映射地址
        location:具体目录
        /js/*代表js下面的所有资源不拦截
        /js/** 代表js下所有的资源不拦截包括子孙包
-->
<mvc:resources mapping="/js/**" location="/js/" />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/imgs/**" location="/imgs/" />

<mvc:resources />由Spring MVC框架自己按配置的规则处理静态资源,并添加一些有用的附加值功能。

注意:JSP页面不属于静态资源!如果是常见的浏览器能解析的格式,直接按照协议返回,如果不是浏览器能直接解析的会返回下载头导致下载该jsp页面!

修改web.xml的前端处理器

<!--拦截所有请求(包括JSP)-->
<url-pattern>/*</url-pattern>
修改springmvc

<mvc:resources mapping="/jsp/**" location="/jsp/" />

访问:http://localhost:8080/jsp/demo01.jsp

浏览器默认以下载的方式处理本次请求的响应

Handler放行

springmvc.xml

<mvc:default-servlet-handler />

SpringMVC会在Spring MVC上下文中定义一个DefaultServletHttpRequestHandler;

对进入DispatcherServlet的URL进行筛查

如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理

如果不是静态资源的请求,才由DispatcherServlet继续处理。

这种方式同样只能放行静态资源,JSP不属于静态资源;

web.xml

<!--拦截所有请求(包括JSP)-->
<url-pattern>/*</url-pattern>

放行规则小结

  • 1)缺省Servlet:请求没有进入springmvc,而是直接进入缺省servlet进行处理放行

  • 2)resources:实质上请求是进入了springmvc然后由springmvc自己处理资源,达到放行效果

  • 3)handler:请求进入springmvc然后对其进行筛选,发现是个"静态"资源就交给缺省servlet去处理,但是缺省servlet只会处理静态资源,如果是jsp会特殊处理(下载、无法解析等)!

JSON支持

SpringMVC支持自动将JSON转换成Java对象,也支持将Java对象自动转成JSON,SpringMVC本身没有对JSON数据处理的类库,要支持JSON的自动转换必须导入JSON的支持包

依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.12.RELEASE</version>
    </dependency>

    <!--Jackson依赖-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.8</version>
    </dependency>

    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-api</artifactId>
        <version>8.5.41</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.18</version>
    </dependency>
</dependencies>

响应JSON

@ResponseBody

将响应的信息放入响应体中,如果响应的数据是Java对象,则会将本次的响应类型改为application/json

Controller

@Controller
@RequestMapping("/demo01")
public class Demo01Controller {
​
    @ResponseBody           // 将结果集以json形式响应
    @GetMapping("/demo01")
    public City demo01() throws Exception {
​
        City city = new City(1, "南昌", 5500.0, true);
        return city;
    }
​
    @ResponseBody           // 将结果集以json形式响应
    @GetMapping(value = "/demo02")
    public List<City> demo02() throws Exception {
​
        List<City> cityList = new ArrayList<>();
        cityList.add(new City(1, "南京", 14000.00, true));
        cityList.add(new City(2, "南通", 2400.00, false));
        cityList.add(new City(3, "南阳", 4000.00, false));
        return cityList;
    }
​
    @ResponseBody
    @GetMapping(value = "/demo03")
    public String demo03() throws Exception {
        return "<h1>hello</h1>";                 // 响应的信息被放在响应体中,而不是当做视图跳转
    }
}

@ResponseBody注解不仅可以标注在方法上,还可以标注在类上

当该注解标注在类上时,该类的所有方法的返回值都会当作Json响应到前端

相当于在该类的所有的方法上都标注了@ResponseBody注解

@RestController

@RestController是@Controller+@ResponseBody注解的整合版,拥有两个注解的功能

既能当前Bean注入到IOC容器,又能让当前Controller的所有方法的返回值以Json响应

Controller

@RestController                 // @Controller+/@ResponseBody
@RequestMapping("/test")
public class TestController {
​
    @GetMapping("/hello")
    public String str() {
        return "hello @ResponseBody...";
    }
}

@JsonIgnore

将实体转换为json时指定忽略的属性,使该属性不出现在JSON信息上

@Data
@AllArgsConstructor
@NoArgsConstructor
public class City {
​
    private Integer id;         // 城市id
    private String cityName;    // 城市名称
    private Double GDP;         // 城市GDP,单位亿元
    private Boolean capital;    // 是否省会城市
​
    @JsonIgnore
    private Province province;  // 所属省份
    
    public City(Integer id, String cityName, Double GDP, Boolean capital) {
        this.id = id;
        this.cityName = cityName;
        this.GDP = GDP;
        this.capital = capital;
    }
}

@JsonFormat

将需要转换成JSON的实体类中的Date按照指定的格式转换为JSON字符串

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private Integer age;
​
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    private Date birthday;
}

ResponseEntity

ResponseEntity对象的内容更加丰富,包括响应头、响应体、响应状态码等信息

@ResponseBody
@RequestMapping(value = "/demo06")
public ResponseEntity<City> demo06() {
    // 响应体
    City city = new City(1, "南宁", 5000.0D, true, new Province(1, "广西"));

    // 响应头
    HttpHeaders headers = new HttpHeaders();
    headers.put("test", Arrays.asList("123"));
​
    // 创建一个响应报文、响应状态码
    ResponseEntity<City> responseEntity = new ResponseEntity<>(city, headers, HttpStatus.OK);
​
    return responseEntity;
}

前端接收响应报文:

Status Code:200 OK

test:123

请求JSON

@RequestBody

获取请求体的内容

注意:GET请求方式是没有请求体的;

Controller

@Controller
@RequestMapping("/demo02")
public class Demo02Controller {
    @RequestMapping("/demo01")
    public void demo01(@RequestBody String str, HttpServletResponse response) throws Exception {          // 获取请求体的内容
​
        System.out.println(str);
        response.getWriter().write(str);
        System.out.println("--------------");
    }
}

demo01.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="/js/jquery-3.5.1.min.js"></script>
</head>
<body>
<form action="/demo02/demo01" method="post">
​
    <input type="text" name="id">
    <input type="text" name="name">
​
    <input type="submit">
</form>
​
<button id="btn">sendRequest</button>
​
<script>
​
    $(function () {
        $("#btn").click(function () {
​
            // Json对象
            var province = {id: 1, name: "吉林省"}
​
            $.post({
                url: "/demo02/demo01",
                // ajax默认的提交格式为: application/x-www-form-urlencoded
                contentType: "application/x-www-form-urlencoded",
                data: province,
                success: function (res) {
                    console.log(res);
                }
            })
        })
    })
</script>
</body>
</html>

Json的支持

@RequestBody不仅可以接收请求体中的参数

还可以将请求体数据为Json的转换为Java实体类型;但要求本次请求类型必须为application/json

Controller

@ResponseBody
@RequestMapping("/demo02")
public Province demo02(@RequestBody Province province) throws Exception {
    System.out.println(province);
    return province;
}

按钮

<button id="json">sendJson</button>

JavaScript

$("#json").click(function () {
    // Json对象
    var province = {id: 1, name: "辽宁省"}
    // 一定要转换为Json字符串
    var provinceJson = JSON.stringify(province)
    $.post({
        url: "/demo02/demo02",
        // ajax默认的提交格式为: application/x-www-form-urlencoded
        contentType: "application/json",
        data: provinceJson,
        success: function (res) {
            console.log(res);
        }
    })
})

Tips:

  • 1)请求类型(Content-Type)必须为application/json,否则出现415(请求类型不匹配)

  • 2)请求参数必须为Json字符串,否则出现400(请求参数错误)

注意:JQuery在传递Json对象(不是Json字符串)数据到后台时,在底层会将Json对象转换为form表单进行传递;

如果本次的提交类型(Content-Type)为application/x-www-form-urlencoded,那么在后端也是可以封装为Java对象的,前提是不能使用@RequestBody注解

Controller

@RequestMapping("/demo03")
@ResponseBody
public Province demo03(Province province) throws Exception {
    return province;
}

RequestEntity

RequestEntity对象的内容更加丰富,包含请求头、请求体等

Controller

@RequestMapping("/demo04")
@ResponseBody
public String demo04(HttpEntity<String> entity) throws Exception {
    // 获取请求头
    HttpHeaders headers = entity.getHeaders();
    System.out.println(headers);
    // 获取请求体
    String provinceJsonStr = entity.getBody();
    System.out.println(provinceJsonStr);
​
    return provinceJsonStr;
}

使用RequestEntity,将前端提交的Json字符串映射为Java对象(此时不能处理表单提交)

@RequestMapping("/demo05")
@ResponseBody
public Province demo05(RequestEntity<Province> entity) throws Exception {
​
    // 获取请求头
    HttpHeaders headers = entity.getHeaders();
    System.out.println(headers);
​
    // 获取请求体
    Province province = entity.getBody();
    System.out.println(province);
​
    return province;
}

文件上传

表单的entype类型必须改为multipart/form-data,提交方式必须为post提交(因为post提交才有请求体)

依赖

<dependencies>
    <!--SpringMVC依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.12.RELEASE</version>
    </dependency>
​
    <!--Json转换-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.8</version>
    </dependency>
​
    <!--Tomcat依赖-->
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-api</artifactId>
        <version>8.5.41</version>
    </dependency>
​
    <!--文件上传依赖-->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.4</version>
    </dependency>
</dependencies>

SpringMVC处理文件上传

配置文件上传解析器

<!--
    配置文件上传的解析器
    bean名称必须为: multipartResolver
 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--文件上传时文件的最大大小(单位:字节)-->
    <property name="maxUploadSize" value="102400000"></property>
    <!--文件上传时,文件名称的编码-->
    <property name="defaultEncoding" value="UTF-8"></property>
</bean>

demo01.jsp

<form action="/demo01/demo01" enctype="multipart/form-data" method="post">
    <input type="file" name="pic">
    <input type="submit">
</form>

Controller

@Controller
@RequestMapping("/demo01")
public class Demo01Controller {
​
    @RequestMapping("/demo01")
    public void demo01(
        // 当表单项名称和形参名不一样可以使用@RequestPart注解来指定名称
        @RequestPart("pic") MultipartFile pic,      
        HttpServletResponse response) throws Exception {
        // 表单项的名称
        String name = pic.getName();
        // 文件的名称
        String fileName = pic.getOriginalFilename();
        // 文件的大小
        long size = pic.getSize();
        // 文件的MIME类型
        String contentType = pic.getContentType();
        // 将前端传递的文件写入到磁盘
        pic.transferTo(new File("D:/000/" + fileName));
        
        response.setContentType("text/html;charset=utf8");
        response.getWriter().write("<hr/ >name: " + name);
        response.getWriter().write("<hr/ >fileName: " + fileName);
        response.getWriter().write("<hr/ >size: " + size);
        response.getWriter().write("<hr/ >contentType: " + contentType);
    }
}

多文件上传

form

<form action="/demo01/demo02" enctype="multipart/form-data" method="post">
    <input type="file" name="pics">
    <input type="file" name="pics">
    <input type="file" name="pics">
    <input type="file" name="pics">
    <input type="submit">
</form>

Controller

@RequestMapping("/demo02")
@ResponseBody
public String demo02(MultipartFile[] pics) throws Exception {
​
    for (MultipartFile file : pics) {
        file.transferTo(new File("D:/000/" + UUID.randomUUID() + "-" + file.getOriginalFilename()));
    }
    return "success";
}

SpringMVC的异常处理

我们在处理异常时,通常使用try...catch块来处理程序中发生的异常;出现异常时每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。

SpringMVC提供的异常处理机制可以把异常处理抽离成方法、类;让处理异常的代码与我们的业务代码完全分开,降低与业务核心代码的耦合度;

Controller抛出异常

@Controller
@RequestMapping("/demo01")
public class Demo01Controller {
​
    @RequestMapping("/demo01")
    @ResponseBody
    public String demo01(Integer flag) {
​
        if (flag == 0) {
            // 模拟异常
            int i = 1 / 0;
        }
​
        return "success";
    }
}

单异常处理

@ExceptionHandler

当控制器方法抛出特定类型的异常时,被@ExceptionHandler注解标注的方法会被自动调用,以处理这些异常。这样可以将异常处理逻辑集中在一个地方,提高代码的可读性和可维护性。

   @Controller
   public class GlobalExceptionHandler {
       @ExceptionHandler(YourException.class)
       public ResponseEntity<String> handleYourException(YourException ex) {
           return ResponseEntity.badRequest().body(ex.getMessage());
       }
   }

@ExceptionHandler只能处理该类或其内部抛出的异常

处理全局异常

编写一个类实现实现HandlerExceptionResolver接口来处理全局异常

@Component
public class MyExceptionResolver implements HandlerExceptionResolver {
    
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
​
        System.out.println(handler.getClass());
        System.out.println(ex.getClass());
​
        ModelAndView mv = new ModelAndView();
        mv.addObject("message", ex.getMessage());
        mv.setViewName("/error.jsp");
        return mv;
    }
}

Tips:如果异常被Controller中的某个ExceptionHandler方法匹配了,那么不会进入自定义的异常解析器;

注解

  • @ControllerAdvice:注解可以配置全局异常

  • @ExceptionHandler(Exception.class):设置什么异常才会进入方法

@ControllerAdvice           
public class AnnoMyExceptionHandler {
    @ExceptionHandler(value = ArithmeticException.class)     
    public String arithmeticExceptionHandler(Model model, Exception e) {
        model.addAttribute("message", e.getMessage());
        return "forward:/error.jsp";
    }
    @ExceptionHandler(Exception.class)
    public String exceptionHandler(Model model, Exception e) {
        model.addAttribute("message", e.getMessage());
        return "forward:/error.jsp";
    }
}

Tips:

  • 1)当同时定义了异常解析器(HandlerExceptionResolver)和@ControllerAdvice时,@ControllerAdvice优先级高;

  • 2)如果@ControllerAdvice标注的异常和Controller方法定义的异常冲突,则以Controller中标注有@ExceptionHandler的方法优先;

  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值