aoe1231

看得完吗

SpringMVC入门

1、SpringMVC简介

1.1、SpringMVC概述

SpringMVC与Servlet技术功能等同,是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于SpringFrameWork的后续产品,已经融合在Spring Web Flow中。

SpringMVC已经成为目前最主流的MVC框架之一,并且随着Spring 3.0 的发布,全面超越Struts2,成为最优秀的MVC框架。它通过一套注解,让一个简单的Java类称为处理请求的控制器,而无需实现任何接口。同时,他还支持RESTful编程风格的请求。

SpringMVC的优点:相比Servlet使用更简单,开发更便捷,灵活性强。

1.2、SpringMVC快速入门

需求:客户端发起请求,服务器端接收请求,执行逻辑并进行视图跳转。

1.2.1、开发步骤

  1. 使用SpringMVC技术需要先导入SpringMVC坐标与Servlet坐标;
    pom.xml:
            ...
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
            ...
    
        <!-- 添加Tomcat插件 -->
        <build>
            <finalName>SSM</finalName>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.1</version>
                    <configuration>
                        <port>80</port>
                        <path>/</path>
                    </configuration>
                </plugin>
            </plugins>
        </build>
  2.  创建控制器Controller类和视图页面,使用注解配置Controller类中业务方法的映射地址。
    @Controller //将控制器加入到容器中
    @RequestMapping("/user")
    public class UserController {
    
        // 请求地址:  http://localhost:80/user/login ,访问时会执行该方法
        @RequestMapping(value="/login",method = RequestMethod.GET,params = {"username"})
        public String save(){
            System.out.println("Controller save running....");
            return "success.jsp"; //跳转到success.jsp
        }
    
        // 设置当前操作的访问路径
        @RequestMapping("/save")
        // 设置当前操作的返回值类型
        @ResponseBody
        public String save() {
            System.out.println("user save ...");
            return "{'module':'springmvc'}";
        }
    }
  3. 初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应的bean。
    /**
     * 创建SpringMVC的配置文件,加载controller对应的bean
     */
    @Configuration
    @ComponentScan("com.clp.controller")
    public class SpringMvcConfig {
    }
  4. 初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求。
    AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类。
    AbstractDispatcherServletInitializer提供3个接口方法供用户实现:
        createRootApplicationContext()方法:
            如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行。使用方式同createServletApplicationContext()
    
    /**
     * 定义一个servlet容器启动的配置类,在里面加载spring的配置
     */
    public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
        // 加载SpringMVC容器配置
        @Override
        protected WebApplicationContext createServletApplicationContext() {
            AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
            ctx.register(SpringMvcConfig.class);
            return ctx;
        }
    
        // 设置哪些请求归属SpringMVC处理
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"}; // 所有请求归SpringMVC进行处理
        }
    
        // 加载Spring容器的配置
        @Override
        protected WebApplicationContext createRootApplicationContext() {
            AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
            ctx.register(SpringConfig.class);
            return ctx;
        }
    }
    
    上述的简化配置:
    class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[]{SpringConfig.class};
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[]{SpringMvcConfig.class};
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[]{"/"};
        }
    }
    
    /*
    注:在web.xml中配置的方式:
        <!-- 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>classpath:spring-mvc.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>
    */

1.2.2、流程分析

启动服务器初始化过程:

  1. 服务器启动,执行ServletContainerInitConfig类,初始化web容器;
  2. 执行createServletApplicationContext()方法,创建了WebApplicationContext对象;
  3. 加载SpringMvcConfig;
  4. 执行@ComponentScan加载对应的bean;
  5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法;
  6. 执行getServletMappings方法,定义所有的请求都通过SpringMvc。

单次请求过程:

  1. 发送请求localhost/save;
  2. web容器发现所有请求都通过SpringMVC,将请求交给SpringMVC处理;
  3. 解析请求路径/save;
  4. 由/save匹配执行对应的方法save();
  5. 执行save();
  6. 检测到有@ResponseBody直接将save()方法的返回值作为响应体返回给请求方。

1.3、SpringMVC的组件解析

  1. 用户发送请求至前端控制器DispatcherServlet;
  2. DispatcherServlet搜到请求调用HandlerMapping处理器映射器;
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DIspatcherServlet;
  4. DispatcherServlet调用HandlerAdapter处理器适配器;
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器);
  6. Controller执行完成返回ModelAndView;
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet;
  8. DispatcherServlet将ModelAndView传给ViewResolver视图解析器;
  9. ViewResolver解析后返回具体View;
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。

1.4、Controller加载控制与业务bean加载控制

  • SpringMVC相关bean(表现层bean);
  • Spring控制的bean:① 业务bean(Service);② 功能bean(DataSource等)。

因为功能不同,如何避免Spring错误地加载到SpringMVC的bean?——加载Spring控制的bean的时候排除掉SpringMVC控制的bean。

  • SpringMVC相关bean加载控制:SpringMVC加载的bean对应的包均在com.itheima.controller包内;
  • Spring相关bean加载控制:① 方式1:Spring加载的bean设定扫描范围为com.itheima,排除掉controller包内的bean;② 方式2:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等;③ 方式3:不区分Spring与SpringMVC的环境,加载到同一个环境中。
    - @ComponentScan注解属性:
        excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)与具体项(classes);
        includeFilters:加载指定的bean,需要指定类别(type)与具体项(classes)。
    
    @Configuration
    @ComponentScan(value="com.clp",
            excludeFilters = @ComponentScan.Filter(
                    type = FilterType.ANNOTATION, // 按注解过滤
                    classes = Controller.class // 排除使用@Controller注解的类
            )
    )
    @EnableTransactionManagement
    public class SpringConfig {
    }

2、请求

2.1、请求映射路径

@RequestMapping
    作用:用于建立请求URL和处理请求方法之间的对应关系;
    作用位置:
        类上:请求URL的第一级访问目录,此处不写的话,就相当于应用的根目录。
        方法上:请求URL的第二级访问目录,与类上的使用@RequestMapping标注的一极目录一起组成访问虚拟路径。
    属性:
        value:用于指定请求的URL,它和path属性的作用是一样的。
        method:用于指定请求的方式。
        params:用于指定限制请求参数的条件,它支持简单的表达式。要求请求参数的key和value必须和配置一模一样。

/**
 * 定义Controller
 */
@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/save")
    @ResponseBody
    public String save() {
        System.out.println("user save ...");
        return "{'module':'user save'}";
    }

    @RequestMapping("/delete")
    @ResponseBody
    public String dalete() {
        System.out.println("user delete ...");
        return "{'module':'user delete'}";
    }
}

浏览器访问:http://127.0.0.1/user/save,后端会执行save()方法

2.2、获得请求头

2.2.1、@RequestHeader

使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)。@RequestHeader注解的属性如下:

  • value:请求头的名称。
  • required:是否必须携带此请求头。

    @RequestMapping(value="/quick20")
    @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
    public void save20(@RequestHeader(value="User-Agent",required = false) String user_agent) throws IOException {
        System.out.println(user_agent);
    }

2.2.2、@CookieValue

使用@CookieValue可以获得指定Cookie的值。@CookieValue注解的属性如下:

  • value:指定cookie的名称。
  • required:是否必须携带此cookie。
    @RequestMapping(value="/quick21")
    @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
    public void save21(@CookieValue(value="JSESSIONID") String jsessionid) throws IOException {
        System.out.println(jsessionid);
    }

2.3、请求方式

2.3.1、Get / Post 请求传参 & Post请求中文乱码处理

@Configuration
@ComponentScan("com.clp.controller")
public class SpringMvcConfig {
}

/*********************************************************************************/
/**
 * 定义一个servlet容器启动的配置类,在里面加载spring的配置
 */
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
    // 加载SpringMVC容器配置
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }

    // 设置哪些请求归属SpringMVC处理
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"}; // 所有请求归SpringMVC进行处理
    }

    // 加载Spring容器的配置
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }

    @Override
    protected Filter[] getServletFilters() {
        // Post请求中文乱码处理:配置字符过滤器,为web容器添加过滤器并指定字符集,spring-web包中提供了专用的字符过滤器
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}

/*********************************************************************************/
/**
 * 定义Controller
 */
@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/save")
    @ResponseBody
    public String save() {
        System.out.println("user save ...");
        return "{'module':'user save'}";
    }

    @RequestMapping("/delete")
    @ResponseBody
    public String dalete() {
        System.out.println("user delete ...");
        return "{'module':'user delete'}";
    }

    /**
     * Get请求:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。
     *  例如:http://127.0.0.1/user/commonParam?name=zhangsan&age=18
     * @param name
     * @param age
     * @return
     */
    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name, int age) {
        System.out.println("普通参数传递 name:" + name + ", age: " + age);
        return "{'module':'common param'}";
    }
}

注:通过配置方式获得过滤器:

​web.xml:
    ...
    <!--配置全局过滤的filter-->
    <filter>
        <filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...

2.4、请求参数

服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数:

参数种类:

  • 普通参数;
  • POJO类型参数;
  • 嵌套POJO类型参数;
  • 数组类型参数;
  • 集合类型参数。

2.4.1、获得普通类型参数

普通参数:url地址传参,地址参数名与形参变量名相同,定义形参即可接收参数。

Controller的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配。当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显式地绑定。注解@RequestParam有如下参数可以使用:

  • value:请求参数名称。
  • required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错。
  • dafaultValue:当没有指定请求参数时,则使用指定的默认值赋值。
    /*
     * 普通参数:请求参数名与形参名不同
     *  例如:http://127.0.0.1/user/commonParamDifferentName?name=zhangsan&age=18
     */
    @RequestMapping("/commonParamDifferentName")
    @ResponseBody
    // @RequestParam的作用是绑定Get请求参数与形参的对应关系
    public String commonParamDifferentName(@RequestParam("name") String userName, int age) {
        System.out.println("【普通参数传递】 userName:" + userName + ", age:" + age);
        return "'module':'common param different name'";
    }

2.4.2、获得POJO类型参数(简单JavaBean)

Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配。

public class User {
    private String name;
    private int age;
    // getter()/setter()...
}

    /*
    * POJO参数
    *   例如:http://127.0.0.1/user/commonParamDifferentName?name=zhangsan&age=18
    * */
    @RequestMapping("/pojoParam")
    @ResponseBody
    public String pojoParam(User user) {
        System.out.println("【pojo参数传递】 user:" + user);
        return "{'module':'pojo param'}";
    }

2.4.3、获得嵌套POJO类型参数

public class Address {
    private String city;
    private String province;
    // getter & setter
}

public class User {
    private String name;
    private int age;
    private Address address;
    // getter & setter
}

    /*
    * 嵌套POJO参数
    *   例如:http://127.0.0.1/user/commonParamDifferentName?name=zhangsan&age=18&address.city=beijing&address.province=beijing
    * */
    @RequestMapping("/pojoParam")
    @ResponseBody
    public String pojoParam(User user) {
        System.out.println("【pojo参数传递】 user:" + user);
        return "{'module':'pojo param'}";
    }

2.4.4、获得数组类型参数

Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。

    /**
     * 数组参数
     *  例如:http://127.0.0.1/user/arrayParam?likes=game&likes=music&likes=travel
     */
    @RequestMapping("/arrayParam")
    @ResponseBody
    public String arrayParam(String[] likes) {
        System.out.println("【数组参数传递】 likes:" + Arrays.toString(likes));
        return "{'module':'array param'}";
    }

2.4.5、获得集合类型参数

获得集合参数时,一定要在参数前添加@RequestParam注解。

    /**
     * 集合参数
     *  例如:http://127.0.0.1/user/listParam?likes=game&likes=music&likes=travel
     */
    @RequestMapping("listParam")
    @ResponseBody
    public String listParam(@RequestParam List<String> likes) {
        System.out.println("【集合参数传递】 likes:" + likes);
        return "{'module':'list param'}";
    }

2.5、请求参数——传递json数据

解析json数据需要先导入相关坐标:

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

再在SpringMVC核心配置类中进行如下配置,开启自动转换json数据的支持(@EnableWebMVC注解功能强大,该注解整合了多个功能,此处仅使用其中一部分功能,即json数据进行自动类型转换):

@Configuration
@ComponentScan("com.clp.controller")
@EnableWebMvc // 开启由json数据转换成对象的功能
public class SpringMvcConfig {
}

2.5.1、集合参数

    /**
     * 集合参数:json格式
     *  例如:http://127.0.0.1/user/listParamForJson,请求体:["game", "music", "travel"]
     */
    @RequestMapping("/listParamForJson")
    @ResponseBody
    // @RequestBody的作用是将请求体的内容映射为对应的对象
    public String listParamForJson(@RequestBody List<String> likes) {
        System.out.println("【list param - json 参数传递】 likes:" + likes);
        return "{'module':'list param - json'}";
    }

2.5.2、POJO参数

    /**
     * 集合参数:json格式
     *  例如:http://127.0.0.1/user/pojoParamForJson,请求体:{"name":"zhangsan","age":"18","address":{"province":"beijing","city":"beijing"}}
     */
    @RequestMapping("/pojoParamForJson")
    @ResponseBody
    // @RequestBody的作用是将请求体的内容映射为对应的对象
    public String pojoParamForJson(@RequestBody User user) {
        System.out.println("【pojo param - json 参数传递】 likes:" + user);
        return "{'module':'pojo param - json'}";
    }

2.5.3、集合+POJO参数

    /**
     * 集合参数:json格式
     *  例如:http://127.0.0.1/user/listPojoParamForJson,请求体:[{"name":"zhangsan","age":"18"},{"name":"lisi","age":"20"}]
     */
    @RequestMapping("listPojoParamForJson")
    @ResponseBody
    // @RequestBody的作用是将请求体的内容映射为对应的对象
    public String listPojoParamForJson(@RequestBody List<User> userList) {
        System.out.println("【list pojo param - json 参数传递】 likes:" + userList);
        return "{'module':'list pojo param - json'}";
    }

2.6、请求参数——日期类型参数传递

日期类型数据基于系统不同格式也不尽相同。

    /**
     * 日期参数
     *  例如:http://127.0.0.1/user/dateParam?date1=2088/08/08&date2=2088-08-18&date3=2088/08/28 8:08:08
     */
    @RequestMapping("/dateParam")
    @ResponseBody
    // 接收形参时,根据不同的日期格式设置不同的接收方式
    // @DateTimeFormat用于指定日期时间型数据格式。属性pattern:日期时间格式字符串
    public String dateParam(Date date1,
                            @DateTimeFormat(pattern = "yyyy-MM-dd") Date date2,
                            @DateTimeFormat(pattern = "yyyy/MM/dd HH:mm:ss") Date date3) {
        System.out.println("【参数传递】 date1:" + date1);
        System.out.println("【参数传递】 date2:" + date2);
        System.out.println("【参数传递】 date3:" + date3);
        return "{'module':'date param'}";
    }

2.6.1、类型转换器

Converter接口:

public interface Converter<S, T> {
    @Nullable
    T convert(S var1);
}

- 请求参数年龄数据(String -> Integer);
- 日期格式转换(String -> Date)。

2.7、@RequestBody、@RequestParam、@PathVariable的区别

区别:

  • @RequestParam用于接收url地址传参、表单传参【application/x-www-form-urlencoded】;
  • @RequestBody用于接收json数据【application/json】;
  • @PathVariable用于接收路径参数,使用{参数名称}描述路径参数。

应用:

  • 后期开发中,发送请求参数超过1个时,以发送json格式数据为主,@RequestBody应用较广;
  • 如果发送非json格式数据,选用@RequestParam接收请求参数;
  • 采用RESTful进行开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于传递id值。

3、响应

3.1、SpringMVC的数据响应方式

1、页面跳转
    直接返回字符串
    通过ModelAndView对象返回

2、回写数据
    直接返回字符串
    返回对象或集合

3.1.1、响应页面

1、返回字符串形式

直接返回字符串“此种方式会将返回的字符串与视图解析器的前后缀拼接后跳转。

    /**
     * 响应页面 / 跳转页面
     */
    @RequestMapping("/toJumpPage")
    public String toJumpPage() {
        System.out.println("跳转页面");
        return "page.jsp";
    }

2、返回ModelAndView对象

package com.itheima.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

@Controller //将控制器加入到容器中
@RequestMapping("/user")
public class UserController {
    // 请求地址:  http://localhost:80/user/login ,访问时会执行该方法,要求使用GET方式请求
    @RequestMapping(value="/login",method = RequestMethod.GET,params = {"username"})
    public String save(){
        System.out.println("Controller save running....");
        return "/success"; //跳转到success.jsp( / 代表在当前web应用下寻找资源)
    }

    @RequestMapping(value="/quick2")
    public ModelAndView save2() {
        /**
         * Model:模型,作用封装数据
         * View:视图,作用展示数据
         */
        ModelAndView modelAndView = new ModelAndView();
        //设置模型数据(放入request域)
        modelAndView.addObject("username","zhangsan");
        //设置视图名称
        modelAndView.setViewName("success");
        return modelAndView; //请求转发
    }

    @RequestMapping(value="/quick3")
    public ModelAndView save3(ModelAndView modelAndView) {
        //设置模型数据(放入request域)
        modelAndView.addObject("username","zhangsan");
        //设置视图名称
        modelAndView.setViewName("success");
        return modelAndView; //请求转发
    }

    @RequestMapping(value="/quick4")
    public String save4(Model model) {
        model.addAttribute("username","lisi");
        return "success"; //请求转发
    }

    @RequestMapping(value="/quick5")
    public String save5(HttpServletRequest request) {
        request.setAttribute("username","wangwu");
        return "success"; //请求转发
    }
}

3.1.2、响应文本数据(字符串)

Web基础阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用response.getWriter().print("hello world")即可,那么在Controller中想直接回写字符串该怎样呢?

  •  方法1:通过SpringMVC框架注入的response对象,使用response.getWriter().print("hello world")回写数据,此时不需要视图跳转,业务方法返回值为void。
        @RequestMapping(value="/quick6")
        public void save6(HttpServletResponse response) throws IOException {
            response.getWriter().print("hello world");
        }
  • 方法2:将需要回写的字符串直接返回,但此时需要通过@ResponseBody注解告知SpringMVC框架,方法返回的字符串不是跳转是直接在http响应体中返回。
        @RequestMapping(value="/quick7")
        @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
        public String save7() throws IOException {
            return "hello world";
        }
    
        @RequestMapping(value="/quick8")
        @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
        public String save8() throws IOException {
            return "{'username':'zhangsan','age':'18'}";
        }
    
        @RequestMapping(value="/quick9")
        @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
        public String save9() throws IOException {
            User user = new User();
            user.setUsername("zhangsan");
            user.setAge(20);
            //使用json的转换工具,将对象转换成json格式字符串,再返回
            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(user);
            return json;
        }
    /**
     * 响应文本数据
     */
    @RequestMapping("/toText")
    @ResponseBody
    public String toText() {
        System.out.println("返回纯文本数据");
        return "response text";
    }

3.1.3、响应json数据(对象集合转json数组)

通过SpringMVC帮助我们对对象或集合进行json字符串的转换并回写,为处理器适配器配置消息转换参数,指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中进行如下配置:

spring-mvc.xml
    
    ...
    <!--配置处理器映射器-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean>
            </list>
        </property>
    </bean>
    ...
    /**
     * 响应POJO对象
     */
    @RequestMapping("/toJsonPojo")
    @ResponseBody // 加上该注解后返回的对象自动转为json格式字符串
    public User toJsonPojo() {
        System.out.println("返回json对象数据");
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

    /**
     * 响应POJO集合对象
     */
    @RequestMapping("/toJsonList")
    @ResponseBody // 加上该注解后返回的对象自动转为json格式字符串
    public List<User> toJsonList() {
        System.out.println("返回json集合数据");
        User user1 = new User();
        user1.setName("张三");
        user1.setAge(18);;
        User user2 = new User();
        user2.setName("李四");
        user2.setAge(20);;
        List<User> userList = Arrays.asList(user1, user2);
        return userList;
    }

在方法上添加@ResponseBody就可以返回json格式的字符串,但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动代替上述配置。

spring-mvc.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--Controller的组件扫描-->
    <context:component-scan base-package="com.itheima">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--配置内部资源视图解析器-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--  /jsp/success.jsp  -->
        <property name="prefix" value="/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

    <!--配置处理器映射器-->
<!--    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">-->
<!--        <property name="messageConverters">-->
<!--            <list>-->
<!--                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></bean>-->
<!--            </list>-->
<!--        </property>-->
<!--    </bean>-->
    <!--mvc的注解驱动-->
    <mvc:annotation-driven />
</beans>

在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。使用<mvc:annotation-driven>自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),可用在spring-mvc.xml配置文件中使用<mvc:annotation-driven>替代注解处理器和适配器的配置。同时使用<mvc:annotation-driven>默认底层就会集成jackson进行对象或集合的json格式字符串的转换。

3.2、类型转换器HttpMessageConverter

- HttpMessageConverter接口
public interface HttpMessageConverter<T> {
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    List<MediaType> getSupportedMediaTypes();
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage) 
            throws IOException, HttpMessageNotReadableException;
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) 
            throws IOException, HttpMessageNotReadableException;
}

4、配置对静态资源的访问放行

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    /**
     * 放行 非SpringMvc的请求
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 当访问/pages/????的时候不要走mvc,走/pages目录下的内容
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        // 其他静态资源
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}

@Configuration
@ComponentScan({"com.clp.controller", "com.clp.config"}) // 添加扫描
@EnableWebMvc // 开启由json数据转换成对象的功能
public class SpringMvcConfig {
}

5、自定义类型转换器

SpringMVC默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。

自定义类型转换器的开发步骤:

  1. 定义转换器实现Converter接口;
  2. 在配置文件中声明转换器;
  3. 在<annotation-driver>中引用转换器。
import org.springframework.core.convert.converter.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateConverter implements Converter<String, Date> {
    public Date convert(String dateStr) {
        //将日期字符串转换成日期对象,然后返回
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        Date date = null;
        try {
            date = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
spring-mvc.xml:

    ...
    <!--mvc的注解驱动-->
    <mvc:annotation-driven conversion-service="conversionService"/>
    <!--声明转换器-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.converter.DateConverter"></bean>
            </list>
        </property>
    </bean>
    ...
    //localhost:80/user/quick18?date=2020-12-12
    @RequestMapping(value="/quick18")
    @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
    public void save17(Date date) throws IOException {
        System.out.println(date);
    }

6、获得Servlet相关API

SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的参数如下:

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession

    @RequestMapping(value="/quick19")
    @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
    public void save19(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
        System.out.println(request);
        System.out.println(response);
        System.out.println(session);
    }

7、REST风格

7.1、简介

REST(Representational State Transfer,表现形式状态转换)。根据REST风格对资源进行访问称为RESTful。Restful是一种软件架构风格、设计风格,而不是标准,知识提供了一组设计原则和约束条件。主要用于客户端和服务器交互的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

Restful风格的请求是使用"url+请求方式"表示一次请求的目的,HTTP协议里面四个表示操作方式的动词如下:

  • GET:用于获取资源。
  • POST:用于新建资源。
  • PUT:用于更新资源。
  • DELETE:用于删除资源。

上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范。描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts...

REST风格的优点:

  • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作;
  • 书写简化。

7.2、入门案例

@Controller
public class UserController {
    /*
    * @RequestMapping:设置当前控制器方法请求访问路径。
    *   属性:
    *       1、value(默认):请求访问路径;
    *       2、method:http请求动作,标准动作(GET、POST、PUT、DELETE)
    *
    * @PathVariable:绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应。
    * */

    @RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
    @ResponseBody
    public String get(@PathVariable Integer id) {
        System.out.println("user get ..." + id);
        return "{'module':'user get'}";
    }

    @RequestMapping(value = "/users", method = RequestMethod.GET)
    @ResponseBody
    public String getAll() {
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }

    @RequestMapping(value = "/users", method = RequestMethod.POST)
    @ResponseBody
    public String post(@RequestBody User user) {
        System.out.println("user post ..." + user);
        return "{'module':'user post'}";
    }

    @RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id) {
        System.out.println("user delete ..." + id);
        return "{'module':'user delete'}";
    }

    @RequestMapping(value = "/users", method = RequestMethod.PUT)
    @ResponseBody
    public String put(@RequestBody User user) {
        System.out.println("user put ..." + user);
        return "{'module':'user put'}";
    }
}

7.3、RESTful快速开发

/*
* @RestController:设置当前控制器类为RESTful风格,等同于@Controller与@ResponseBody两个注解组合功能。
* @GetMapping & @PostMapping & @PutMapping & @DeleteMapping:
*   设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,例如@GetMapping对应GET请求。
* */
//@Controller
//@ResponseBody
@RestController // @RestController = @Controller + @ResponseBody
@RequestMapping("/books")
public class BookController {
//    @RequestMapping(method = RequestMethod.POST)
    @PostMapping // @PostMapping = @RequestMapping(method = RequestMethod.POST)
    public String post(@RequestBody Book book) {
        System.out.println("book post ..." + book);
        return "{'module':'book post'}";
    }

//    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @DeleteMapping("/{id}") // @DeleteMapping("/{id}") = @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable Integer id) {
        System.out.println("book delete ..." + id);
        return "{'module':'book delete'}";
    }

//    @RequestMapping(method = RequestMethod.PUT)
    @PutMapping // @PutMapping = @RequestMapping(method = RequestMethod.PUT)
    public String put(@RequestBody Book book) {
        System.out.println("book put ..." + book);
        return "{'module':'book put'}";
    }

//    @RequestMapping(value = "/{id}}", method = RequestMethod.GET)
    @GetMapping("/{id}") // @GetMapping("/{id}") = @RequestMapping(value = "/{id}}", method = RequestMethod.GET)
    public String get(@PathVariable Integer id) {
        System.out.println("book get ..." + id);
        return "{'module':'book get'}";
    }

//    @RequestMapping(method = RequestMethod.GET)
    @GetMapping // @GetMapping = @RequestMapping(method = RequestMethod.GET)
    public String getAll() {
        System.out.println("book getAll ...");
        return "{'module':'book getAll'}";
    }
}

8、SSM整合

8.1、SSM整合流程

1、创建工程
2、SSM整合
    - Spring
        SpringConfig
    - MyBatis
        MyBatisConfig
        JdbcConfig
        jdbc.properties
    - SpringMVC
        ServletConfig
        SpringMvcConfig
3、功能模块
    - 表与实体类
    - dao(接口+自动代理)
    - service(接口+实现类)
        业务层接口测试(整合JUnit)
    - controller
        表现层接口测试(PostMan)

8.1.1、导入相关坐标

pom.xml:
    ...
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>
    ...
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <port>80</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    ...

8.1.2、SSM整合

jdbc.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssmdb
jdbc.username=root
jdbc.password=123456
public class JdbcConfig {
    /*
    * 简单类型的依赖注入使用@Value
    * */
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    // 1、定义一个方法获得要管理的对象
    // 2、添加@Bean表示当前方法的返回值是一个bean
    /*
     * 引用类型的依赖注入:主需要为bean定义方法设置形参即可,容器会根据 类型 自动装配对象。
     * */
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 创建平台事务管理器
     * @param dataSource
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager dtm = new DataSourceTransactionManager();
        dtm.setDataSource(dataSource);

        return dtm;
    }
}
public class MyBatisConfig {
    /**
     * SqlSessionFactoryBean专门产生SqlSessionFactory
     * @return
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setDataSource(dataSource); // 设置数据源
        ssfb.setTypeAliasesPackage("com.clp.domain"); // 设置实体类别名扫描包
        return ssfb;
    }

    /**
     * 扫描Dao层的映射信息
     * @return
     */
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.clp.dao");
        return msc;
    }
}
@Configuration
@ComponentScan({"com.clp.service"})
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class, MyBatisConfig.class})
@EnableTransactionManagement // 开启事务管理器
public class SpringConfig {
}
@Configuration
@ComponentScan({"com.clp.controller"})
@EnableWebMvc // 开启由json数据转换成对象的功能
public class SpringMvcConfig {
}
/**
 * 定义一个servlet容器启动的配置类,在里面加载spring的配置(需继承AbstractDispatcherServletInitializer,这样tomcat可以探测到这个类)
 */
public class ServletConfig extends AbstractDispatcherServletInitializer {
    /*
    * 注:Web容器(Tomcat)启动时,会加载SpringMvcConfig和SpringConfig配置类。
    *   这两个配置类产生的容器对象不是同一个对象。
    *   SpringMVC容器能访问Spring容器,而Spring容器不能访问SpringMVC容器。
    * */

    // 加载SpringMVC容器配置
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }

    // 拦截请求:设置哪些请求归属SpringMVC处理
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"}; // 所有请求归SpringMVC进行处理
    }

    // 加载Spring容器的配置
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }

    // 设置过滤器,处理中文乱码问题
    @Override
    protected Filter[] getServletFilters() {
        // Post请求中文乱码处理:配置字符过滤器,为web容器添加过滤器并指定字符集,spring-web包中提供了专用的字符过滤器
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
    }
}

8.1.3、功能模块

public interface BookDao {
    @Insert("insert into book values(null, #{type}, #{name}, #{description})")
    void save(Book book);

    @Update("update book set type=#{type}, name=#{name}, description=#{description} where id = #{id}")
    void update(Book book);

    @Delete("delete from book where id = #{id}")
    void delete(Integer id);

    @Select("select * from book where id = #{id}")
    Book getById(Integer id);

    @Select("select * from book")
    List<Book> getAll();
}
@Transactional
public interface BookService {
    boolean save(Book book);
    boolean update(Book book);
    boolean delete(Integer id);
    Book getById(Integer id);
    List<Book> getAll();
}

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    @Override
    public boolean save(Book book) {
        bookDao.save(book);
        return true;
    }

    @Override
    public boolean update(Book book) {
        bookDao.update(book);
        return true;
    }

    @Override
    public boolean delete(Integer id) {
        bookDao.delete(id);
        return true;
    }

    @Override
    public Book getById(Integer id) {
        return bookDao.getById(id);
    }

    @Override
    public List<Book> getAll() {
        return bookDao.getAll();
    }
}
@RestController // @RestController = @Controller + @ResponseBody
@RequestMapping("/books")
public class BookController {
    @Autowired
    private BookService bookService;

//    @RequestMapping(method = RequestMethod.POST)
    @PostMapping // @PostMapping = @RequestMapping(method = RequestMethod.POST)
    public boolean post(@RequestBody Book book) {
        return bookService.save(book);
    }

//    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @DeleteMapping("/{id}") // @DeleteMapping("/{id}") = @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public boolean delete(@PathVariable Integer id) {
        return bookService.delete(id);
    }

//    @RequestMapping(method = RequestMethod.PUT)
    @PutMapping // @PutMapping = @RequestMapping(method = RequestMethod.PUT)
    public boolean put(@RequestBody Book book) {
        return bookService.update(book);
    }

//    @RequestMapping(value = "/{id}}", method = RequestMethod.GET)
    @GetMapping("/{id}") // @GetMapping("/{id}") = @RequestMapping(value = "/{id}}", method = RequestMethod.GET)
    public Book get(@PathVariable Integer id) {
        return bookService.getById(id);
    }

//    @RequestMapping(method = RequestMethod.GET)
    @GetMapping // @GetMapping = @RequestMapping(method = RequestMethod.GET)
    public List<Book> getAll() {
        return bookService.getAll();
    }
}

8.1.4、测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {
    @Autowired
    private BookService bookService;
    
    @Test
    public void testGetById() {
        Book book = bookService.getById(1);
        System.out.println(book);
    }
    
    @Test
    public void testGetAll() {
        List<Book> bookList = bookService.getAll();
        System.out.println(bookList);
    }
}

8.2、表现层数据封装

前端接收数据格式——封装特殊消息到message(msg)属性中。

- 设置统一数据返回结果类:
    public class Result {
        private Object data; // 数据
        private Integer code; // 编码
        private String msg; // 消息
    }
  注意:Result类中的字段并不是固定的,可以根据需要自行增减。
  提供若干个构造方法,方便操作。

- 设置统一数据返回结果编码
    public class Code {
        public static final Integer SAVE_OK = 20011;
        public static final Integer DELETE_OK = 20021;
        public static final Integer UPDATE_OK = 20031;
        public static final Integer GET_OK = 20041;

        public static final Integer SAVE_ERR = 20010;
        public static final Integer DELETE_ERR = 20020;
        public static final Integer UPDATE_ERR = 20030;
        public static final Integer GET_ERR = 20040;
    }
  注意:Code类的常量设计也不是固定的,可以根据需要自行增减,例如将查询再进行细分为GET_OK,GET_ALL_OK,GET_PAGE_OK。

- 根据情况设定合理的Result
    @RestController // @RestController = @Controller + @ResponseBody
    @RequestMapping("/books")
    public class BookController {
        @Autowired
        private BookService bookService;

        //    @RequestMapping(method = RequestMethod.POST)
        @PostMapping // @PostMapping = @RequestMapping(method = RequestMethod.POST)
        public Result post(@RequestBody Book book) {
            boolean flag = bookService.save(book);
            return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
        }

        //    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
        @DeleteMapping("/{id}") // @DeleteMapping("/{id}") = @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
        public Result delete(@PathVariable Integer id) {
            boolean flag = bookService.delete(id);
            return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
        }

        //    @RequestMapping(method = RequestMethod.PUT)
        @PutMapping // @PutMapping = @RequestMapping(method = RequestMethod.PUT)
        public Result put(@RequestBody Book book) {
            boolean flag = bookService.update(book);
            return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
        }

        //    @RequestMapping(value = "/{id}}", method = RequestMethod.GET)
        @GetMapping("/{id}") // @GetMapping("/{id}") = @RequestMapping(value = "/{id}}", method = RequestMethod.GET)
        public Result get(@PathVariable Integer id) {
            Book book = bookService.getById(id);
            return new Result(
                    book != null ? Code.GET_OK : Code.GET_ERR,
                    book,
                    book != null ? "查询成功" : "查询失败"
            );
        }

        //    @RequestMapping(method = RequestMethod.GET)
        @GetMapping // @GetMapping = @RequestMapping(method = RequestMethod.GET)
        public Result getAll() {
            List<Book> bookList = bookService.getAll();
            return new Result(
                    bookList != null ? Code.GET_OK : Code.GET_ERR,
                    bookList,
                    bookList != null ? "查询成功" : "查询失败"
            );
        }
    }

9、异常处理

9.1、异常处理的思路

系统中异常包括两类:预期异常和运行时异常RintimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。

系统的Dao、Service、Controller出现都通过throws Exception向上抛出,最后由SpringMVC前端控制器交由异常处理器进行异常处理。如下图:

9.2、异常处理的两种方式

9.2.1、简单异常处理器SimpleMappingExceptionResolver

使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver。

SpringMVC已经定义好了该类型转换器,在使用时可以根据项目情况进行相应异常与视图的映射配置。

spring-mvc.xml:

    ...
    <!--配置简单映射异常处理器-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="defaultErrorView" value="error.jsp" />
        <property name="exceptionMappings">
            <map>
                <entry key="java.lang.ClassCastException" value="error1.jsp"/>
                <entry key="com.exception.MyException" value="error2.jsp"/>
            </map>
        </property>
    </bean>
    ...

9.2.2、自定义异常处理器

实现Spring的异常处理接口HandleExceptionResolver自定义自己的异常处理器。

自定义异常处理步骤:

  1. 创建异常处理器实现HandlerExceptionResolver;
  2. 配置异常处理器;
  3. 编写异常页面;
  4. 测试异常跳转。
package com.exception;

public class MyException extends Exception{
}
/************************************************************************************/

package com.exception;

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyExceptionResolver implements HandlerExceptionResolver {
    @Override
    /**
     * 参数Exception:异常对象。
     * 返回值ModelAndView:跳转的错误视图信息。
     */
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView modelAndView = new ModelAndView();

        if(ex instanceof MyException) {
            modelAndView.addObject("info","自定义异常");
        }else if(ex instanceof ClassCastException) {
            modelAndView.addObject("info","类型转换异常");
        }
        modelAndView.setViewName("error.jsp");

        return null;
    }
}
spring-mvc.xml:

    ...
    <!--自定义异常处理器-->
    <bean class="com.exception.MyExceptionResolver" />
    ...

9.3、Spring中的异常处理器

出现异常现象的常见位置与常见诱因如下:

  • 框架内部抛出的异常:因使用不合规导致;
  • 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时);
  • 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等);
  • 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致的异常);
  • 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)。

思考:

  • 各个层级均出现异常,异常代码书写在哪一层?——所有的异常均抛出到表现层进行处理。
  • 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?——AOP思想。

Spring中的异常处理器可以集中、统一地处理项目中出现的异常。

// 声明这个类是异常处理器,用于AOP处理异常,需要SpringMvcConfig配置扫描该类所在的包(@ComponentScan)
@RestControllerAdvice
public class ProjectExceptionAdvice {
    @ExceptionHandler(Exception.class) // 拦截异常
    // 方法的返回值将会作为响应结果
    public Result doException(Exception ex) {
        System.out.println("获取异常");
        return new Result(666, null);
    }
}

BookController:
    @PostMapping // @PostMapping = @RequestMapping(method = RequestMethod.POST)
    public Result post(@RequestBody Book book) {
        boolean flag = bookService.save(book);
        int i = 1 / 0; // 手动产生异常
        return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
    }

9.4、项目异常处理方案

- 项目异常分类
    - 业务异常(BusinessException)
        规范的用户行为产生的异常
        不规范的用户行为操作产生的异常
    - 系统异常(SystenException)
        项目运行过程中可预计且无法避免的异常
    - 其他异常(Exception)
        编程人员未预期到的异常

- 项目异常处理方案:
    - 业务异常(BusinessException)
        发送对应消息传递给用户,提醒规范操作
    - 系统异常(SystemException)
        发送固定消息传递给用户,安抚用户
        发送特定消息给运维人员,提醒用户
        记录日志
    - 其他异常(Exception)
        发送固定消息传递给用户,安抚用户
        发送特定消息给编程人员,提醒维护(纳入预期范围内)
        记录日志

步骤:

  1. 自定义项目系统级异常、业务级异常:
    public class SystemException extends RuntimeException{
        private Integer code; // 异常编号
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public SystemException(Integer code, String message) {
            super(message);
            this.code = code;
        }
    
        public SystemException(Integer code, String message, Throwable cause) {
            super(message, cause);
            this.code = code;
        }
    }
    
    public class BusinessException extends RuntimeException{
        private Integer code; // 异常编号
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public BusinessException(Integer code, String message) {
            super(message);
            this.code = code;
        }
    
        public BusinessException(Integer code, String message, Throwable cause) {
            super(message, cause);
            this.code = code;
        }
    }
  2. 自定义异常编码(持续补充):
    public class Code {
        public static final Integer SYSTEM_ERR = 50001;
        public static final Integer BUSINESS_ERR = 60001;
        public static final Integer SYSTEM_UNKNOWN_ERR = 59999;
    }
  3. 触发(模拟)自定义异常:
    @Service
    public class BookServiceImpl implements BookService {
        @Autowired
        private BookDao bookDao;
    
        @Override
        public boolean save(Book book) {
            if (true) {
                throw new BusinessException(Code.BUSINESS_ERR, "请不要使用你的技术挑战我的耐心!");
            }
            // 将可能出现的异常进行包装,转换成自定义异常
            try {
                int i = 1 / 0;
            } catch (Exception e) {
                throw new SystemException(Code.SYSTEM_ERR, "服务器访问超时,请重试!", e);
            }
            bookDao.save(book);
            return true;
        }
    }
  4. 拦截并处理异常;
    // 声明这个类是异常处理器,用于AOP处理异常,需要SpringMvcConfig配置扫描该类所在的包(@ComponentScan)
    @RestControllerAdvice
    public class ProjectExceptionAdvice {
        // 处理系统异常
        @ExceptionHandler(SystemException.class)
        public Result doSystemException(SystemException ex) {
            // 记录日志
            // 发送消息给运维
            // 发送邮件给开发人员
            return new Result(ex.getCode(), null, ex.getMessage());
        }
    
        // 处理业务异常
        @ExceptionHandler(BusinessException.class)
        public Result doBusinessException(BusinessException ex) {
            return new Result(ex.getCode(), null, ex.getMessage());
        }
    
        // 处理其他异常
        @ExceptionHandler(Exception.class) // 拦截异常
        // 方法的返回值作为出现异常的方法的返回值
        public Result doException(Exception ex) {
            return new Result(Code.SYSTEM_UNKNOWN_ERR, null, "系统繁忙,请稍后再试!");
        }
    }

10、拦截器

10.1、拦截器的概念

拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行。SpringMVC的拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。

拦截器的作用:

  • 在指定的方法调用前后执行预先设定的代码;
  • 阻止原始方法的执行。

将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体体现。

10.2、拦截器和过滤器的区别

区别 过滤器(Filter) 拦截器(Interceptor)
使用范围 是servlet规范中的一部分,任何Java Web工程都可以使用。 是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能用。
拦截范围 在url-pattern中配置了/*之后,可以对所有要访问的资源拦截。 仅针对SpringMVC的访问进行增强。在<mvc:mapping path="" />中配置了/**之后,也可以对所有资源进行拦截,但是可以通过<mvc:exclude-mapping path="" />标签排除不需要拦截的资源。

10.3、拦截器入门案例

自定义拦截器很简单,只有如下三步:

  1. 创建拦截器类实现HandlerInterceptor接口;
    /**
     * 拦截器(interceptor)包在controller包下,因为已经配置了扫描controller包,所以不用再配置interceptor包了
     */
    @Component
    public class ProjectInterceptor implements HandlerInterceptor{
        // 被拦截的原始操作之前运行的方法
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle");
            return true; // return false 会终止原始操作的执行。因此下面两个方法不会再执行
        }
    
        // 被拦截的原始操作之后运行的方法
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle");
            return;
        }
    
        // 被拦截的原始操作之后并且在postHandle()方法后执行的方法
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("afterCompletion");
            return;
        }
    }
  2. 配置拦截器;
    @Configuration
    public class SpringMvcSupport extends WebMvcConfigurationSupport {
        @Autowired
        private ProjectInterceptor projectInterceptor;
    
        /**
         * 配置拦截器,并设定拦截的访问路径,路径可以通过可变参数设置多个
         */
        @Override
        protected void addInterceptors(InterceptorRegistry registry) {
            // addPathPatterns()方法的参数表示拦截的url字符串匹配则进行拦截
            registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
        }
    }
    
    @Configuration
    @ComponentScan({"com.clp.controller", "com.clp.config"}) // 添加扫描
    @EnableWebMvc // 开启由json数据转换成对象的功能
    public class SpringMvcConfig {
    }
    
    /*
    - xml配置方式:
    spring-mvc.xml:
        ...
        <!--配置拦截器-->
        <mvc:interceptors>
            <mvc:interceptor>
                <!--对哪些资源执行拦截操作(此处代表对所有)-->
                <mvc:mapping path="/**" />
                <!--配置哪些资源排除拦截操作-->
                <mvc:exclude-mapping path="/user/login"/>
                
                <bean class="com.interceptor.MyInterceptor1" />
            </mvc:interceptor>
        </mvc:interceptors>
        ...
    */
    
    - 上述配置的简化版(注意:侵入式较强):
    @Configuration
    @ComponentScan({"com.clp.controller"})
    @EnableWebMvc // 开启由json数据转换成对象的功能
    public class SpringMvcConfig implements WebMvcConfigurer {
        @Autowired
        private ProjectInterceptor projectInterceptor;
    
        /**
         * 配置拦截器
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // addPathPatterns()方法的参数表示拦截的url字符串匹配则进行拦截
            registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
        }
    }
  3. 测试拦截器的拦截效果。

10.4、拦截器HandlerInterceptor接口方法说明

方法名 说明
preHandle() 方法将在请求处理之前进行调用,该方法的返回值是布尔值boolean类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为true时就会继续调用下一个Interceptor的preHandle方法。
postHandle() 该方法是在当前请求进行处理之后被调用,前提是preHandle方法的返回值为true时才能被调用,且它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作。
afterCompletion() 该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行,前提是preHandle方法的返回值为true时才能被调用。

10.5、拦截器执行流程

10.6、拦截器参数

- 前置处理
@Component
public class ProjectInterceptor implements HandlerInterceptor{
    // 被拦截的原始操作之前运行的方法
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod hm = (HandlerMethod) handler;

        String contentType = request.getHeader("Content-Type");
        System.out.println("preHandle" + contentType);
        return true; // return false 会终止原始操作的执行。因此下面两个方法不会再执行
    }
}
- 参数
    request:请求对象
    response:响应对象
    handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装
- 返回值
    若返回值为false:被拦截的处理器将不执行
- 后置处理
    // 被拦截的原始操作之后运行的方法
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
        return;
    }
- 参数:
    modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整。
- 完成后处理
    // 被拦截的原始操作之后并且在postHandle()方法后执行的方法
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
        return;
    }
- 参数:
    ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理。

10.7、多拦截器执行顺序

10.7.1、拦截器链配置方式

@Configuration
@ComponentScan({"com.clp.controller"})
@EnableWebMvc // 开启由json数据转换成对象的功能
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;
    @Autowired
    private ProjectInterceptor2 projectInterceptor2;

    /**
     * 配置拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns()方法的参数表示拦截的url字符串匹配则进行拦截
        // 拦截器链的执行顺序与添加顺序有关
        registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
        registry.addInterceptor(projectInterceptor2).addPathPatterns("/books", "/books/*");
    }
}

10.7.2、拦截器链的运行顺序

  • 当配置多个拦截器时,形成拦截器链。
  • 拦截器链的运行顺序参照拦截器添加顺序为准。
  • 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行。
  • 当拦截器运行中断,仅运行配置在前面的拦截器的afterCompletion操作。

11、文件上传

11.1、文件上传三要素

  • 表单项type="file"
  • 表单的提交方式是post
  • 表单的enctype属性是多部份表单形式,及enctype="multipart/form-data"
form.jsp:

    ...
    <form action="${pageContext.request.contextPath}/quick22" method="post"
          enctype="multipart/form-data">
        名称:<input type="text" name="username"><br />
        文件:<input type="file" name="uploadFile"><br />
        <input type="submit" value="提交"><br />
    </form>
    ...

11.2、文件上传原理

当form表单修改为多部分表单时,request.getParameter()将失效;

enctype="application/x-www-form-urlencoded"时,form表单的正文内容格式是:key=value&key=value...;

当form表单的enctype取值为multipart/form-data时,请求正文内容就变成多部分形式:

11.3、单文件上传步骤

  1. 导入fileupload和io坐标;
  2. 配置文件上传解析器;
  3. 编写文件上传代码。
pom.xml:

        <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.3</version>
        </dependency>
spring-mvc.xml:

    ...
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--上传文件总大小-->
        <property name="maxUploadSize" value="5242800" />
        <!--上传单个文件的大小-->
        <property name="maxUploadSizePerFile" value="5242800" />
        <!--上传文件的编码类型-->
        <property name="defaultEncoding" value="UTF-8" />
    </bean>
    ...
    @RequestMapping(value="/quick22")
    @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
    public void save22(String username, MultipartFile uploadFile) throws IOException {
        System.out.println(username);
        //获得文件名称
        String originalFilename = uploadFile.getOriginalFilename();
        //保存文件
        uploadFile.transferTo(new File("D:\\upload\\"+originalFilename));
    }

11.4、多文件上传实现

多文件上传,只需要将页面修改为多个文件上传项,将方法参数MultipartFile类型修改为MultipartFile[]即可。

    ...
    <form action="${pageContext.request.contextPath}/quick22" method="post"
          enctype="multipart/form-data">
        名称:<input type="text" name="username1"><br />
        文件:<input type="file" name="uploadFile1"><br />

        名称:<input type="text" name="username2"><br />
        文件:<input type="file" name="uploadFile2"><br />
        <input type="submit" value="提交"><br />
    </form>
    ...
    @RequestMapping(value="/quick22")
    @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
    public void save22(String username1,String username2, MultipartFile uploadFile1, MultipartFile uploadFile2) throws IOException {
        System.out.println(username);
        //获得文件名称
        String originalFilename1 = uploadFile1.getOriginalFilename();
        //保存文件
        uploadFile1.transferTo(new File("D:\\upload\\"+originalFilename1));
        //获得文件名称
        String originalFilename2 = uploadFile1.getOriginalFilename();
        //保存文件
        uploadFile2.transferTo(new File("D:\\upload\\"+originalFilename2));
    }
    <form action="${pageContext.request.contextPath}/quick23" method="post"
          enctype="multipart/form-data">
        名称:<input type="text" name="username"><br />
        文件1:<input type="file" name="uploadFiles"><br />
        文件2:<input type="file" name="uploadFiles"><br />
        文件3:<input type="file" name="uploadFiles"><br />
        <input type="submit" value="提交"><br />
    </form>
    @RequestMapping(value="/quick23")
    @ResponseBody //告诉SpringMVC框架,不是页面跳转,而是回写
    public void save23(String username, MultipartFile[] uploadFiles) throws IOException {
        System.out.println(username);
        
        for(MultipartFile multipartFile : uploadFiles) {
            //获得文件名称
            String originalFilename = multipartFile.getOriginalFilename();
            //保存文件
            multipartFile.transferTo(new File("D:\\upload\\"+originalFilename));
        }
    }

posted on 2022-09-18 15:48  啊噢1231  阅读(142)  评论(0编辑  收藏  举报

导航

回到顶部