Java教程

SpringMVC&Maven进阶

本文主要是介绍SpringMVC&Maven进阶,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

3. SpringMVC

3.1 了解SpringMVC

  • 概述
    • SpringMVC技术与Servlet技术功能等同,均属于web层开发技术
  • 学习路线
    • 请求与响应
    • REST分割
    • SSM整合
    • 拦截器
  • 目标:
    • 掌握基于SpringMVC获取请求参数与响应json数据操作
    • 熟练应用基于REST风格的请求路径设置与参数传递
    • 能够根据实际业务建立前后端开发通信协议并进行实现
    • 基于SSM整合技术开发任意业务模块功能

3.2 SpringMVC简介

3.2.1 SpringMVC概述

image

  • SpringMVC是一种基于Java实现MVC模型轻量级Web框架
  • 优点
    • 使用简单,开发便捷(相比于Servlet)
    • 灵活性强

3.2.2 SpringMVC入门案例

  • 使用SpringMVC需要先导入SpringMVC坐标与Servlet坐标

    <dependencies>
      <!--1.导入坐标-->
      <!--Servlet-->
      <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <!--由于可能会和tomcat插件冲突,添加范围provided-->
        <scope>provided</scope>
      </dependency>
    
      <!--SpringMVC-->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.10.RELEASE</version>
      </dependency>
    </dependencies>
    
  • 创建SpringMVC控制器类(等同于Servlet功能)

    //2.定义controller
    //2.1 使用@Controller定义bean
    @Controller
    public class UserController {
        //2.2 设置当前操作的访问路径
        @RequestMapping("/save")
        //2.3 设置当前操作的返回值类型
        @ResponseBody
        public String save(){
            System.out.println("user save..");
            return "{'module':'Spring MVC'}";
        }
    }
    
  • 初始化SpringMVC环境(同Spring环境),设定SpringMVC加载对应Bean

    //3.创建SpringMVC的配置文件,加载Controller对应的bean
    @Configuration
    @ComponentScan("com.mark.controller")
    public class SpringMVC {
    
    }
    
  • 初始化Servlet容器,加载SpringMVC环境,并设置SpringMVC技术处理的请求

    //4.定义一个Servlet容器启动的配置类,在里面加载Spring的配置,告知服务器使用SpringMVC
    public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {
        //加载SpringMVC容器配置
        @Override
        protected WebApplicationContext createServletApplicationContext() {
            //原来Spring获取容器:ApplicationContext ctx= new AnnotationConfigApplicationContext()
            //获取容器
            AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
            //注册配置
            ctx.register(SpringMVCConfig.class);
            return ctx;
        }
    
        //设置哪些请求归属SpringMVC处理
        @Override
        protected String[] getServletMappings() {
            //所有请求都归SpringMVC处理
            return new String[]{"/"};
        }
        //加载Spring容器配置
        @Override
        protected WebApplicationContext createRootApplicationContext() {
            return null;
        }
    }
    

    注意:此时web.xml可以删除,之后配置tomcat服务器

    <build>
      <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>
    

    这时浏览器访问http://localhost/save 即可看到结果 {'module':'Spring MVC'}

3.2.3 注解介绍

  • @Controller

    • 类型:类注解

    • 位置:SpringMVC控制器类定义上方

    • 作用:设定SpringMVC的核心控制器bean

    • 范例:

      @Controller
      public class UserController {
        
      }
      
  • @RequestMapping

    • 类型:方法注解

    • 位置:SpringMVC控制器方法定义上方

    • 作用:设置当前控制器方法请求访问路径

    • 范例:

      @RequestMapping("/save")
      public void save(){
        	System.out.println("user save ...");
      }
      
    • 相关属性

      • value(默认):请求访问路径
  • @ResponseBody

    • 类型:方法注解

    • 位置:SpringMVC控制器方法定义上方

    • 作用:设置当前控制器方法响应内容为当前返回值,无需解析

    • 范例

      @RequestMapping("/save")
      @ResponseBody
      public String save(){
        	System.out.println("user save ...");
        	return "{'info':'springmvc'}";
      }
      

3.2.4 SpringMVC入门程序开发总结(1+N)

  • 一次性工作
    • 创建工程,设置服务器,加载工程
    • 导入坐标
    • 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
    • SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
  • 多次工作
    • 定义处理请求的控制器类(@Controller)
    • 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)

3.2.5 Servlet配置类详解

  • AbstractDispatcherServletInitializer类是SpringMVC提供的快速初始化Web3.0容器的抽象类

  • AbstractDispatcherServletInitializer提供三个接口方法供用户实现

    • createServletApplicationContext()方法:
      创建Servlet容器时,加载SpringMVC对应的bean并放入WebApplicationContext对象范围中,而WebApplicationContext的作用范围为ServletContext范围,即整个web容器范围

      @Override
      protected WebApplicationContext createServletApplicationContext() {
          //原来Spring获取容器:ApplicationContext ctx= new AnnotationConfigApplicationContext()
          //获取容器
          AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
          //注册配置
          ctx.register(SpringMVCConfig.class);
          return ctx;
      }
      
    • getServletMappings()方法:
      设定SpringMVC对应的请求映射路径,设置为/表示拦截所有请求,任意请求都将转入到SpringMVC进行处理

      @Override
      protected String[] getServletMappings() {
          //所有请求都归SpringMVC处理
          return new String[]{"/"};
      }
      
    • createRootApplicationContext()方法:
      如果创建Servlet容器时需要加载非SpringMVC对应的bean,使用当前方法进行,使用方式同createServletApplicationContext()。如果没有返回null即可

      @Override
      protected WebApplicationContext createRootApplicationContext() {
          AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
          ctx.register(SpringConfig.class);
          return ctx;
      }
      
      

3.2.6 入门案例工作流程分析

  • 启动服务器初始化过程

    • 服务器启动,执行ServletContainersInitConfig类,初始化web容器
    • 执行createServletApplicationContext方法,创建了WebApplicationContext对象
    • 加载SpringMvcConfig
    • 执行@ComponentScan加载对应的bean
    • 加载UserController,每个@RequestMapping的名称对应一个具体的方法
    • 执行getServletMappings方法,定义所有的请求都通过SpringMVC

    image

  • 单次请求过程

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

3.2.7 Controller加载控制与业务bena加载控制

image

  • 不同的bean由不同的容器管理

    • SpringMVC相关的bean:表现层bean
    • Spring控制的bean:
      • 业务bean(Service)
      • 功能bean(DataSource等)
  • 因为功能不同,如何避免Spring错误的加载到SpringMVC的bean?

    • 加载Spring控制的bean的时候排除掉SpringMVC控制的bean
  • 不同bean的加载控制:

    • SpringMVC相关bean加载控制

      • SpringMVC加载的bean对应均在com.mark.controller包内
    • Spring相关bean加载控制

      • 方式一:Spring加载的bean设定扫描范围为com.mark,排除掉controller包内的bean

        @Configuration
        //@ComponentScan({"com.mark.service","com.mark.dao"})
        //扫描com.mark下的所有,但是按照注解排除掉使用了Controller注解的bean
        @ComponentScan(value = "com.mark",
                excludeFilters = @ComponentScan.Filter(
                    type = FilterType.ANNOTATION,
                    classes = Controller.class
                )
        )
        public class SpringConfig {
        
        }
        
      • 方式二:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包

        @Configuration
        @ComponentScan({"com.mark.service","com.mark.dao"})
        public class SpringConfig {
        
        }
        
      • 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境

  • 相关注解介绍

    • @ComponentScan

      • 类型:类注解

      • 范例:

        @Configuration
        @ComponentScan(value = "com.mark",
              excludeFilters = @ComponentScan.Filter(        
                	type = FilterType.ANNOTATION,
                	classes = Controller.class
              )
        )
        public class SpringConfig {
          
        }
        
      • 属性

        • excludeFilters:排除扫描路径中加载的bean,需要指定类别(type)与具体项(classes)
        • includeFilters:加载指定的bean,需要指定类别(type)与具体项(classes)
  • 配置Web容器启动类改进、简化:继承AbstractAnnotationConfigDispatcherServletInitializer

    public class ServletContainerInitConfig 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[]{"/"};
        }
    }
    

3.2.8 PostMan

  • PostMan简介
    • PostMan是一款功能强大的网页调试发送网页HTTP请求的Chrome插件
    • 作用:常用于进行接口测试
    • 特征:
      • 简单
      • 实用
      • 美观
      • 大方
  • PostMan基础操作
    • 注册登录
    • 创建/进入工作空间
    • 发起请求测试结果

3.3 请求与响应

3.3.1 请求映射路径

  • 思考:

    • 团队多人开发,每个人设置不同的请求路径,冲突如何解决?

      • 设置模块名作为请求路径前缀

        @Controller
        public class BookController {
        
            @RequestMapping("/book/save")
            @ResponseBody
            public String save(){
                System.out.println("book save ...");
                return "{'module':'book save'}";
            }
        }
        
        @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 delete() {
                System.out.println("user delete ...");
                return "{'module':'user delete'}";
            }
        
        }
        
  • 注解介绍

    • 名称:@RequestMapping

    • 类型:方法注解 类注解

    • 位置:SpringMVC控制器方法定义上方

    • 作用:设置当前控制器方法请求访问路径,如果设置在类上统一设置当前控制器方法请求访问路径前缀

    • 范例:

      @Controller
      @RequestMapping("/book")
      public class BookController {
      
          @RequestMapping("/save")
          @ResponseBody
          public String save() {
              System.out.println("book save ...");
              return "{'module':'book save'}";
          }
      }
      
    • 属性

      • value(默认):请求访问路径,或访问路径前缀

3.3.2 各种请求参数传递

  • 请求方式

    • Get请求
    • Post请求
  • Get请求参数

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

      image

      @Controller
      @RequestMapping("/user")
      public class UserController {
          //普通参数
          @RequestMapping("/commonParam")
          @ResponseBody
          public String commonParam(String name,int age){
              System.out.println("普通参数传递 name ==>"+name);
              System.out.println("普通参数传递 age ==>"+age);
              return "{'module':'common param'}";
          }
      }
      

      请求参数名与形参变量名不同,使用@RequestParam绑定参数关系

      @Controller
      @RequestMapping("/user")
      public class UserController {
          //普通参数
          @RequestMapping("/commonParam")
          @ResponseBody
          public String commonParam(@RequestParam("name") String username,int age){
              System.out.println("普通参数传递 name ==>"+username);
              System.out.println("普通参数传递 age ==>"+age);
              return "{'module':'common param'}";
          }
      }
      

      这时发送请求的名字为name,username可以接收到name的值

    • POJO参数请求参数名与形参对象属性名相同定义POJO类型形参即可接收参数

      //POJO参数
      @RequestMapping("/pojoParam")
      @ResponseBody
      public String pojoParam(User user){
          System.out.println("POJO参数传递 user ==>"+user);
          return "{'module':'pojo param'}";
      }
      

      嵌套POJO参数:POJO对象中包含POJO对象
      请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数

      public class User {
          private String name;
          private int age;
      
          private Address address;
      }
      
      public class Address {
          private String province;
          private String city;
      }
      

      image

    • 数组参数:请求参数名与形参数组名相同且请求参数为多个,定义数组类型形参即可接收参数

      @RequestMapping("arrayParam")
      @ResponseBody
      public String arrayParam(String[] hobby){
          System.out.println(("数组参数传递 hobby ==> "+ Arrays.toString(hobby)));
          return "{'module':'array param'}";
      }
      

      image

    • 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系

      @RequestMapping("listParam")
      @ResponseBody
      public String listParam(@RequestParam List<String> hobby){
          System.out.println("集合参数传递 hobby ==> "+ hobby);
          return "{'module':'list param'}";
      }
      
  • Post请求参数

    • 普通参数:form表单post请求传参,表单参数名与形参变量名相同,定义形参即可接收参数,代码与Get相同。

      image

    • Post请求中文乱码处理

      在Servlet启动类配置中添加过滤器

      //乱码处理
      @Override
      protected Filter[] getServletFilters() {
          CharacterEncodingFilter filter = new CharacterEncodingFilter();
          filter.setEncoding("UTF-8");
          return new Filter[]{filter};
      } 
      
      
    • 其他类型与Get方式规则一样

3.3.3 响应json数据

  • 添加json数据转换相关坐标

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.0</version>
    </dependency>
    
  • 设置发送json数据(请求body中添加json数据)

    image

  • 开启json数据格式的自动转换,在配置类中开启@EnableWebMvc

    @Configuration
    @ComponentScan("com.mark.controller")
    @EnableWebMvc
    public class SpringMvcConfig {
    }
    
  • 使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据

    集合类数据:

    @RequestMapping("/listParamForJson")
    @ResponseBody
    public String listParamForJson(@RequestBody List<String> hobby){
        System.out.println("list common(json)参数传递 list ==> "+hobby);
        return "{'module':'list common for json param'}";
    }
    

    POJO类数据:json数据与形参对象属性名相同,定义POJO类型形参即可接收参数

    @RequestMapping("/pojoParamForJson")
    @ResponseBody
    public String pojoParamForJson(@RequestBody User user){
        System.out.println("pojo(json)参数传递 user ==> "+user);
        return "{'module':'pojo for json param'}";
    }
    

    image

    POJO集合参数

    @RequestMapping("/listPojoParamForJson")
    @ResponseBody
    public String listPojoParamForJson(@RequestBody List<User> list){
        System.out.println("list pojo(json)参数传递 list ==> "+list);
        return "{'module':'list pojo for json param'}";
    }
    

    image

  • image

3.3.4 日期类型参数传递

  • 日期类型数据基于不同系统 格式也不尽相同
    • 2000-04-18
    • 2000/04/18
    • 04/18/2000
  • 接收形参时,根据不同的日期格式设置不同的接收方式,默认格式:yyyy/MM/dd。实用@DateTimeFormat设定日期时间型数据格式,属性:pattern = 日期时间格式字符串
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
                        @DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
                        @DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss") Date date2){
    System.out.println("参数传递 date ==> "+date);
    System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
    System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
    return "{'module':'data param'}";
}

/*
参数传递 date ==> Tue Apr 18 00:00:00 CST 2000
参数传递 date1(yyyy-MM-dd) ==> Tue Apr 18 00:00:00 CST 2000
参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> Tue Apr 18 00:05:20 CST 2000
*/

image

3.3.5 响应

  • 响应页面(了解)

    返回值为String类型,设置返回值为页面名称,即可实现页面跳转

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

    • 文本数据(了解)

      返回值为String类型,设置返回值为任意字符串信息,即可实现返回指定字符串信息,需要依赖@ResponseBody注解

      @RequestMapping("/toText")
      @ResponseBody
      public String toText(){
          System.out.println("返回纯文本数据");
          return "response text";
      }
      
    • json数据(重点)

      • 响应POJO对象

        返回值为实体类对象,设置返回值为实体类类型,即可实现返回对应对象的json数据,需要依赖@ResponseBody注解和@EnableWebMvc注解

        @RequestMapping("/toJsonPOJO")
        @ResponseBody
        public User toJsonPOJO(){
            System.out.println("返回json对象数据");
            User user = new User();
            user.setName("itcast");
            user.setAge(15);
            return user;
        }
        
      • 响应POJO集合对象

        返回值为集合对象,设置返回值为集合类型,即可实现返回对应集合的json数组数据,需要依赖@ResponseBody注解和@EnableWebMvc注解

        @RequestMapping("/toJsonList")
        @ResponseBody
        public List<User> toJsonList(){
            System.out.println("返回json集合数据");
            User user1 = new User();
            user1.setName("传智播客");
            user1.setAge(15);
        
            User user2 = new User();
            user2.setName("黑马程序员");
            user2.setAge(12);
        
            List<User> userList = new ArrayList<User>();
            userList.add(user1);
            userList.add(user2);
        
            return userList;
        }
        

3.4 REST风格

3.4.1 REST风格简介

  • REST(Representational State Transfer),表现形式状态转换
    • 传统风格资源描述形式
      • http://localhost/user/getById?id=1
      • http://localhost/user/saveUser
    • REST风格描述形式
      • http://localhost/user/1
      • http://localhost/user
  • 优点:
    • 书写简化
    • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
  • 按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
    • http://localhost/users:查询全部用户信息 GET(查询)
    • http://localhost/users/1:查询指定用户信息 GET(查询)
    • http://localhost/users:添加用户信息 POST(新增/保存)
    • http://localhost/users:修改用户信息 PUT(修改/更新)
    • http://localhost/users/1:删除用户信息 DELETE(删除)
  • 上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范。
    描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、books、accounts……
  • 根据REST风格对资源进行访问称为RESTful

3.4.2 RESTful入门案例

  • 步骤:

    • 修改@RequestMapping路径为模块名称复数

      @RequestMapping("/users")

    • 设置请求行为(http请求动作)

      @RequestMapping(value = "/users",method = RequestMethod.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'}";
      }
      
  • 实现:

    @Controller
    public class UserController {
    
        //设置当前请求方法为POST,表示REST风格中的添加操作
        @RequestMapping(value = "/users",method = RequestMethod.POST)
        @ResponseBody
        public String save(@RequestBody User user) {
            System.out.println("user save..."+user);
            return "{'module':'user save'}";
        }
    
        //设置当前请求方法为DELETE,表示REST风格中的删除操作
        //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
        @RequestMapping(value = "/users/{id}",method = RequestMethod.DELETE)
        @ResponseBody
        public String delete(@PathVariable Integer id) {
            System.out.println("user delete..." + id);
            return "{'module':'user delete'}";
        }
    
        //设置当前请求方法为PUT,表示REST风格中的修改操作
        @RequestMapping(value = "/users",method = RequestMethod.PUT)
        @ResponseBody
        public String update(@RequestBody User user) {
            System.out.println("user update..." + user);
            return "{'module':'user update'}";
        }
    
        //设置当前请求方法为GET,表示REST风格中的查询操作
        //@PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同
        @RequestMapping(value = "/users/{id}",method = RequestMethod.GET)
        @ResponseBody
        public String getById(@PathVariable Integer id) {
            System.out.println("user getById..." + id);
            return "{'module':'user getById'}";
        }
    
        //设置当前请求方法为GET,表示REST风格中的查询操作
        @RequestMapping( value = "/users",method = RequestMethod.GET)
        @ResponseBody
        public String getAll() {
            System.out.println("user getAll...");
            return "{'module':'user getAll'}";
        }
    
    }
    
  • POST、DELETE、PUT、GET分别对应增删改查

  • @PathVariable注解用于设置路径变量(路径参数),要求路径上设置对应的占位符,并且占位符名称与方法形参名称相同

  • 截至目前,见到过的接收参数注解有三种

    • @RequestParam:用于接收url地址传参或表单传参 绑定参数
    • @RequestBody:用于接收json数据映射到形参中作为数据
    • @PathVariable:用于接收路径参数,@RequestMapping中使用{参数名称}描述路径参数
  • 应用

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

3.4.3 REST快速开发

在上面的案例中,有很多重复编写的代码

image

将每个方法的value中的模块名提取到类前

将每个方法的@ResponseBody提取到类前

类的@Controller和@ResponseBody可以合并成@RestController

每个方法@PostMapping中的请求行为设置可以用对应的@xxxMapping注解替换

//@Controller
//@ResponseBody配置在类上可以简化配置,表示设置当前每个方法的返回值都作为响应体
//@ResponseBody
@RestController//使用@RestController注解替换@Controller与@ResponseBody注解,简化书写
@RequestMapping("/books")
public class BookController {

    //@RequestMapping( method = RequestMethod.POST)
    @PostMapping//使用@PostMapping简化Post请求方法对应的映射配置
    public String save(@RequestBody Book book) {
        System.out.println("book save..." + book);
        return "{'module':'book save'}";
    }

    //@RequestMapping(value = "/{id}" ,method = RequestMethod.DELETE)
    @DeleteMapping("/{id}")//使用@DeleteMapping简化DELETE请求方法对应的映射配置
    public String delete(@PathVariable Integer id) {
        System.out.println("book delete..." + id);
        return "{'module':'book delete'}";
    }

    //@RequestMapping(method = RequestMethod.PUT)
    @PutMapping//使用@PutMapping简化Put请求方法对应的映射配置
    public String update(@RequestBody Book book) {
        System.out.println("book update..." + book);
        return "{'module':'book update'}";
    }

    //@RequestMapping(value = "/{id}" ,method = RequestMethod.GET)
    @GetMapping("/{id}")//使用@GetMapping简化GET请求方法对应的映射配置
    public String getById(@PathVariable Integer id) {
        System.out.println("book getById..." + id);
        return "{'module':'book getById'}";
    }

    //@RequestMapping(method = RequestMethod.GET)
    @GetMapping//使用@GetMapping简化GET请求方法对应的映射配置
    public String getAll() {
        System.out.println("book getAll...");
        return "{'module':'book getAll'}";
    }
}

3.4.4 案例:基于RESTful页面数据交互

POSTMan实现后台接口开发

@RestController
@RequestMapping("/books")
public class BookController {
    /**
     * 保存新增数据
     * @param book
     * @return
     */
    @PostMapping
    public String save(@RequestBody Book book) {
        System.out.println("book save" + book);
        return "{'module':'book save success'}";
    }

    /**
     * 获取全部
     * @return
     */
    @GetMapping
    public List<Book> getAll() {
        Book book1 = new Book();
        book1.setType("计算机");
        book1.setName("SpringMVC入门");
        book1.setDescription("我是小白");

        Book book2 = new Book();
        book2.setType("计算机");
        book2.setName("SpringMVC实战");
        book2.setDescription("我是大佬");

        List<Book> list = new ArrayList<Book>();
        list.add(book1);
        list.add(book2);
        return list;
    }
}

实现页面数据交互:

由于在访问静态资源页面时,SpringMVC的Sevlet容器配置中设置了protected String[] getServletMappings() { return new String[]{"/"};}会拦截所有请求,导致无法打开页面

因此创建SpringMvcSupport配置类,设置对静态资源的访问放行

@Configuration
public class SpringMVCSupport extends WebMvcConfigurationSupport {
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pages/???的时候,不要走MVC,走/pages目录下的内容
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");

    }
}

前端页面通过异步提交访问后台控制器

//添加
saveBook() {
    axios.post("/books", this.formData).then((res) => {

    });
},

//主页列表查询
getAll() {
    axios.get("/books").then((res) => {
        this.dataList = res.data;
    });
},

3.5 ⭐SSM整合

3.5.1 整合配置

  • 创建工程

    image

  • SSM整合

    • Spring

      • SpringConfig

        @Configuration
        @ComponentScan({"com.mark.service"})
        @PropertySource("classpath:jdbc.properties")
        @Import({JdbcConfig.class,MybatisConfig.class})
        public class SpringConfig {}
        
    • MyBatis

      • JdbcConfig

        public class JdbcConfig {
            @Value("${jdbc.driver}")
            private String driver;
            @Value("${jdbc.url}")
            private String url;
            @Value("${jdbc.username}")
            private String username;
            @Value("${jdbc.password}")
            private String password;
        
            @Bean
            public DataSource dataSource() {
                DruidDataSource dataSource = new DruidDataSource();
                dataSource.setDriverClassName(driver);
                dataSource.setUrl(url);
                dataSource.setUsername(username);
                dataSource.setPassword(password);
                return dataSource;
            }
        }
        
      • MyBatisConfig

        public class MybatisConfig {
            @Bean
            public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
                SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
                factoryBean.setDataSource(dataSource);
                factoryBean.setTypeAliasesPackage("com.mark.domain");
                return factoryBean;
            }
        
            @Bean
            public MapperScannerConfigurer mapperScannerConfigurer() {
                MapperScannerConfigurer msc = new MapperScannerConfigurer();
                msc.setBasePackage("com.mark.dao");
                return msc;
            }
        }
        
      • jdbc.properties

        jdbc.driver=com.mysql.cj.jdbc.Driver
        jdbc.url=jdbc:mysql://localhost:3306/ssm_db
        jdbc.username=root
        jdbc.password=123
        
    • SpringMVC

      • SpringMVCConfig

        @Configuration
        @ComponentScan({"com.mark.controller","com.mark.config"})
        @EnableWebMvc
        public class SpringMvcConfig {}
        
      • ServletConfig

        public class ServletConfig 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[]{"/"};
            }
        
            @Override
            protected Filter[] getServletFilters() {
                CharacterEncodingFilter characterEncodingFilter =new CharacterEncodingFilter();
                characterEncodingFilter.setEncoding("UTF-8");
                return new Filter[]{characterEncodingFilter};
            }
        }
        
      • SpringMVCSupport

        @Configuration
        public class SpringMvcSupport extends WebMvcConfigurationSupport {
            @Override
            protected void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
                registry.addResourceHandler("/js/**").addResourceLocations("/js/");
                registry.addResourceHandler("/css/**").addResourceLocations("/css/");
             registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
            }
        }
        

3.5.2 功能模块开发

  • 表与实体类

    创建表:

    -- ----------------------------
    -- Table structure for tbl_book
    -- ----------------------------
    DROP TABLE IF EXISTS `tbl_book`;
    CREATE TABLE `tbl_book`  (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of tbl_book
    -- ----------------------------
    INSERT INTO `tbl_book` VALUES (1, '计算机理论', 'Spring实战 第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
    INSERT INTO `tbl_book` VALUES (2, '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,手写Spring精华思想');
    INSERT INTO `tbl_book` VALUES (3, '计算机理论', 'Spring 5 设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式');
    INSERT INTO `tbl_book` VALUES (4, '计算机理论', 'Spring MVC+MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
    INSERT INTO `tbl_book` VALUES (5, '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
    INSERT INTO `tbl_book` VALUES (6, '计算机理论', 'Java核心技术 卷I 基础知识(原书第11版)', 'Core Java 第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新');
    INSERT INTO `tbl_book` VALUES (7, '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,大厂面试知识点全覆盖');
    INSERT INTO `tbl_book` VALUES (8, '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉');
    INSERT INTO `tbl_book` VALUES (9, '计算机理论', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术');
    INSERT INTO `tbl_book` VALUES (10, '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
    INSERT INTO `tbl_book` VALUES (11, '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
    INSERT INTO `tbl_book` VALUES (12, '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
    
    

    实体类:

    @Getter
    @Setter
    @ToString
    public class Book {
        private Integer id;
        private String type;
        private String name;
        private String description;
    }
    
  • 创建好所有的接口和实现类

    image

  • dao(接口+自动代理实现)

    public interface BookDao {
    
        //@Insert("insert into tbl_book values (null,#{type},#{name},#{description})")
        //上面语句中type指的是Book类的属性名,id需要为null
        //下面语句中第一个type指的是表里的属性名 第二个type指的是Book类的属性名,不需要为id赋值
        /**
         * 保存
         * @param book
         */
        @Insert("insert into tbl_book (type, name, description) values (#{type}, #{name}, #{description})")
        public void save(Book book);
    
        /**
         * 修改/更新
         * @param book
         */
        @Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
        public void update(Book book);
    
        /**
         * 删除
         * @param id
         */
        @Delete("delete from tbl_book where id = #{id}")
        public void delete(Integer id);
    
        /**
         * 根据id获取book
         * @param id
         * @return
         */
        @Select("select id, type, name, description from tbl_book where id = #{id}")
        public Book getById(Integer id);
    
        /**
         * 获取所有book
         * @return
         */
        @Select("select id, type, name, description from tbl_book")
        public List<Book> getAll();
    }
    
  • service(接口+实体类)

    public interface BookService {
        /**
         * 保存
         * @param book
         */
        public boolean save(Book book);
    
        /**
         * 修改/更新
         * @param book
         */
        public boolean update(Book book);
    
        /**
         * 根据id删除
         * @param id
         */
        public boolean delete(Integer id);
    
        /**
         * 根据id查询
         * @param id
         * @return
         */
        public Book getById(Integer id);
    
        /**
         * 获取所有
         * @return
         */
        public 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) {
            //Book book = bookDao.getById(id);
            //return book;
            return bookDao.getById(id);
        }
    
        @Override
        public List<Book> getAll() {
            return bookDao.getAll();
        }
    }
    
    
  • controller

    @RestController
    @RequestMapping("/books")
    public class BookController {
        @Autowired
        private BookService bookService;
    
        /**
         * 保存
         * @param book
         */
        @PostMapping
        public boolean save(@RequestBody Book book) {
            return bookService.save(book);
        }
    
        /**
         * 修改/更新
         * @param book
         */
        @PutMapping
        public boolean update(@RequestBody Book book) {
            return bookService.update(book);
        }
    
        /**
         * 根据id删除
         * @param id
         */
        @DeleteMapping("/{id}")
        public boolean delete(@PathVariable Integer id) {
            return bookService.delete(id);
        }
    
        /**
         * 根据id查询
         * @param id
         * @return
         */
        @GetMapping("/{id}")
        public Book getById(@PathVariable Integer id) {
            return bookService.getById(id);
        }
    
        /**
         * 获取所有
         * @return
         */
        @GetMapping
        public List<Book> getAll() {
            return bookService.getAll();
        }
    }
    
    

3.5.3 接口测试

  • 业务层接口测试(整合Junit)

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class BookServiceTest {
    
        @Autowired
        private BookService bookService;
    
        @Test
        public void testGetById() {
            Book book = bookService.getById(2);
            System.out.println(book);
        }
    
        @Test
        public void testGetAll() {
            List<Book> list = bookService.getAll();
            System.out.println(list);
        }
    }
    
  • 表现层接口测试(PostMan)

3.5.4 添加事务

  • 在Spring配置类中打开使用注解式事务驱动@EnableTransactionManagement

  • 在JDBC配置类中添加事务管理器配置PlatformTransactionManager

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager ptm = new DataSourceTransactionManager();
        ptm.setDataSource(dataSource);
        return ptm;
    }
    
  • 给要添加事务的实现类的接口添加注解@Transactional

3.5.5 表现层数据封装协议

  • 前端接收数据格式

    • 增删改:true

    • 查单条:

      {
          "id": 1,
          "type": "计算机理论",
          "name": "Spring实战 第5版",
          "description": "Spring入门经典教程,深入理解Spring原理技术内幕"
      }
      
    • 查全部:

      [
          {
              "id": 1,
              "type": "计算机理论",
              "name": "Spring实战 第5版",
              "description": "Spring入门经典教程,深入理解Spring原理技术内幕"
          },
          {
              "id": 2,
              "type": "计算机理论",
              "name": "Spring 5核心原理与30个类手写实战",
              "description": "十年沉淀之作,手写Spring精华思想"
          }
      ]
      
  • 统一格式,前端接收数据格式:封装数据到data属性中,封装操作到code属性中,封装特殊消息到message(msg)属性中

    • 增删改:

      {
        	"code":20031
        	"data":true
      }
      
    • 查单条:

      {
        	"code":20041
        	"data":{
          "id": 1,
          "type": "计算机理论",
          "name": "Spring实战 第5版",
          "description": "Spring入门经典教程,深入理解Spring原理技术内幕"
      	}
      }
      
    • 查单条数据为空

      {
        	"code":20040
        	"data":null
        	"msg":"数据查询失败,请重试!"
      }
      
    • 查全部:

      {
        	"code":20041
        	"data":[
          	{
              	"id": 1,
              	"type": "计算机理论",
              	"name": "Spring实战 第5版",
              	"description": "Spring入门经典教程,深入理解Spring原理技术内幕"
          	},
          	{
              	"id": 2,
              	"type": "计算机理论",
              	"name": "Spring 5核心原理与30个类手写实战",
              	"description": "十年沉淀之作,手写Spring精华思想"
          	}
      	]
      }
      
  • 设置统一数据返回结果类

    public class Result {
        private Object data;
        private Integer code;
        private String msg;
    }
    
    • Result类中的字段并不是固定的,可以根据需要自行删减
    • 提供若干个构造方法,方便操作

3.5.5 表现层与前端数据传输数据协议实现

  • 添加Result结果数据

    @Getter
    @Setter
    public class Result {
        private Object data;
        private Integer code;
        private String msg;
    
        public Result(Integer code, Object data, String msg) {
            this.data = data;
            this.code = code;
            this.msg = msg;
        }
    
        public Result(Integer code, Object data) {
            this.data = data;
            this.code = code;
        }
    
        public Result() {
        }
    }
    
  • 修改Controller的return数据

    @RestController
    @RequestMapping("/books")
    public class BookController {
        @Autowired
        private BookService bookService;
    
        /**
         * 保存
         * @param book
         */
        @PostMapping
        public Result save(@RequestBody Book book) {
            boolean flag = bookService.save(book);
            return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
        }
    
        /**
         * 修改/更新
         * @param book
         */
        @PutMapping
        public Result update(@RequestBody Book book) {
            boolean flag = bookService.update(book);
            return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
        }
    
        /**
         * 根据id删除
         * @param id
         */
        @DeleteMapping("/{id}")
        public Result delete(@PathVariable Integer id) {
            boolean flag = bookService.delete(id);
            return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
        }
    
        /**
         * 根据id查询
         * @param id
         * @return
         */
        @GetMapping("/{id}")
        public Result getById(@PathVariable Integer id) {
            Book book = bookService.getById(id);
            Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
            String msg = book != null ? "" : "数据查询失败,请重试";
            return new Result(code, book, msg);
        }
    
        /**
         * 获取所有
         * @return
         */
        @GetMapping
        public Result getAll() {
            List<Book> books = bookService.getAll();
            Integer code = books.isEmpty() ? Code.GET_OK : Code.GET_ERR;
            String msg = books.isEmpty() ? "" : "数据查询失败,请重试";
            return new Result(code, books, msg);
        }
    }
    

3.5.6 异常处理器

程序开发过程中不可避免的会遇到异常现象

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

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

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

    • AOP思想
  • 异常处理器

    • 集中的、统一的处理项目中出现的异常
    @RestControllerAdvice
    public class ProjectExceptionAdvice {
        @ExceptionHandler(Exception.class)
        public Result doException(Exception ex){
            System.out.println("异常别跑");
            return new Result(666,null,"异常别跑");
        }
    }
    

    @RestControllerAdvice:声明一个类作为异常处理器

    @ExceptionHandler:定义当前方法处理哪一种异常

  • 项目异常分类

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

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

    • 新建exception包

    • 创建Exception类,自定义系统、业务级异常

      @Getter
      @Setter
      public class SystemException extends RuntimeException {
          private Integer 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;
          }
      }
      
      @Getter
      @Setter
      public class BusinessException extends RuntimeException{
          private Integer 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;
          }
      }
      
    • 自定义异常编码

      public class Code {
          public static final Integer SYS_ERR = 50001;
          public static final Integer SYS_TIMEOUT_ERR = 50002;
          public static final Integer SYS_UNKNOW_ERR = 59999;
      
          public static final Integer Business_ERR = 60002;
      }
      
    • 将可能出现的异常进行包装,转换成自定义异常,触发自定义异常

      @Override
      public Book getById(Integer id) {
      
          if (id == 1){
              throw new BusinessException(Code.Business_ERR,"请不要乱来!");
          }
      
          //将可能出现的异常进行包装,转换成自定义异常
          try {
              int i = 1 / 0;
          }catch (Exception e){
              throw new SystemException(Code.SYS_TIMEOUT_ERR,"服务器访问超时,请重试",e);
          }
          return bookDao.getById(id);
      }
      
    • 在异常处理器中分别进行处理

      @RestControllerAdvice
      public class ProjectExceptionAdvice {
          @ExceptionHandler(SystemException.class)
          public Result doSystemException(SystemException ex){
              //记录日志
              //发送消息给运维
              //发送邮件给开发文件,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){
              //记录日志
              //发送消息给运维
              //发送邮件给开发文件,ex对象发送给开发人员
              return new Result(Code.SYS_UNKNOW_ERR,null,"系统繁忙,请稍后再试");
          }
      }
      

3.5.7 前后台协议联调

  • 列表功能

    getAll() {
        //发送ajax请求
        axios.get("/books").then((res)=>{
            this.dataList = res.data.data;
        })
    },
    
  • 添加功能

    //弹出添加窗口
    handleCreate() {
        this.dialogFormVisible = true;
    },
    
    //添加
    handleAdd() {
        //发送ajax请求
        axios.post("/books", this.formData).then((res) => {
            //console.log(res.data)
            if (res.data.code == 20011){
                //如果操作成功,关闭弹窗,显示数据
                this.dialogFormVisible = false;
                this.$message.success("添加成功")
            }else if (res.data.code ==20010){
                this.$message.error("添加失败")
            }else {
                this.$message.error(res.data.msg)
            }
        }).finally(()=>{
            this.getAll();
        })
    },
    
    //重置表单
    resetForm() {
        this.formData = {}
    },
    
    //弹出添加窗口
    handleCreate() {
        this.dialogFormVisible = true;
        this.resetForm();
    },
    
  • 修改功能

    //弹出编辑窗口
    handleUpdate(row) {
        //查询数据,根据id查询
        axios.get("/books/"+row.id).then((res)=>{
            if (res.data.code== 20041){
                //展示弹层,加载数据
                this.formData = res.data.data;
                this.dialogFormVisible4Edit = true;
            }else{
                this.$message.error(res.data.msg)
            }
        })
    },
    
    //编辑
    handleEdit() {
        //发送ajax请求
        axios.put("/books", this.formData).then((res) => {
            //如果操作成功,关闭弹层,显示数据
            if (res.data.code == 20031){
                this.dialogFormVisible4Edit = false;
                this.$message.success("修改成功")
            }else if (res.data.code ==20030){
                this.$message.error("修改失败")
            }else {
                this.$message.error(res.data.msg)
            }
        }).finally(()=>{
            this.getAll();
        })
    },
    
  • 删除功能

    // 删除
    handleDelete(row) {
        //弹出提示框
        this.$confirm("此操作不可逆,永久删除数据,是否继续", "提示", {
            type: 'info'
        }).then(() => {
            //删除业务
            //查询数据,根据id查询
            axios.delete("/books/" + row.id).then((res) => {
                if (res.data.code == 20021) {
                    this.$message.success("删除成功")
                } else {
                    this.$message.error("删除失败")
                }
            }).finally(() => {
                this.getAll();
            });
        }).catch(() => {
            //取消删除
            this.$message.info("取消删除操作")
        });
    }
    

3.6 拦截器

3.6.1 拦截器概念

image

  • 拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
  • 作用
    • 在指定的方法调用前后执行预先设定的代码
    • 组织原始方法的执行
  • 拦截器和过滤器的区别
    • 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
    • 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强

3.6.2 入门案例

  • 制作拦截器功能类:声明拦截器的bean,并实现HanderInterceptor接口(注意:扫描加载bean)

    @Component
    public class ProjectInterceptor implements HandlerInterceptor {
      	//在原始被拦截之前运行的代码
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle");
          	//false时,只执行preHandle,终止原始操作的运行
            return true;
        }
    	//在原始被拦截之后运行的代码
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle");
        }
    	//在原始被拦截之后运行的代码并且在post之后
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("afterCompletion");
        }
    }
    
  • 配置拦截器的执行位置:定义配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法设定拦截的访问路径(注意:扫描加载配置)

    @Configuration
    public class SpringMVCSupport extends WebMvcConfigurationSupport {
        @Autowired
        private ProjectInterceptor projectInterceptor;
    
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/pages/**").addResourceLocations("/pages");
        }
    
      	//可以配置多个拦截路径
        @Override
        protected void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
        }
    }
    
    
  • 在访问books时输出:

    preHandle
    book save...Book{书名='haha', 价格=200.0}
    postHandle
    afterCompletion
    
  • 简化开发:

    • 不需要SpringMVCSupport类,直接在SpringMvcConfig中实现(侵入式较强)

      @Configuration
      @ComponentScan({"com.mark.controller"})
      @EnableWebMvc
      public class SpringMvcConfig implements WebMvcConfigurer {
          @Autowired
          private ProjectInterceptor projectInterceptor;
      
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              registry.addInterceptor(projectInterceptor).addPathPatterns("/books","/books/*");
          }
      }
      

image

3.6.3 拦截器参数

  • 前置处理

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String contentType = request.getHeader("Content-Type");
        HandlerMethod hm=(HandlerMethod) handler;
        hm.getMethod();
        System.out.println("preHandle..."+contentType);
        return true;
    }
    
    • 参数
      • request:请求对象
      • response:响应对象
      • handler:被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装
    • 返回值
      • 返回值为false,被拦截的处理器将不再执行
  • 后置处理

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }
    
    • 参数
      • modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整
  • 完成后处理

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
    
    • 参数
      • ex:如果处理器执行过程中出现异常对象,可以针对异常情况精选单独处理

3.6.4 拦截器链配置

当配置多个拦截器时,形成拦截器链

  • 配置两个拦截器后运行顺序:

    preHandle...
    preHandle...222
    book getById...1
    postHandle...222
    postHandle...
    afterCompletion...222
    afterCompletion
    

    先进后出

  • 拦截器链的运行顺序参照拦截器添加顺序为准

    image

拦截器运行中断,post都不会执行

4. Maven进阶

4.1 分模块开发与设计

  • 分模块开发意义

    • 将原始模块按照功能拆分成若干个子模块,方便模块间的相互调用,接口共享
  • 分模块开发步骤

    • 创建Maven模块

    image

    • 书写模块代码

      image

      分模块开发需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分

    • 通过maven指令安装模块到本地仓库(install命令)

      image

      团队内部开发需要发布模块到团队内部可共享的仓库中(私服)

    • 在主项目pom中引入各个模块坐标

4.2 依赖管理

依赖指当前项目运行所需的jar,一个项目可以设置多个依赖

格式:

<!--设置当前项目所依赖的所有jar-->
<dependencies>
	<!--设置具体的依赖-->
    <dependency>
      	<!--依赖所属群组的id-->
        <groupId>com.mark</groupId>
      	<!--依赖所属项目的id-->
        <artifactId>Maven_02_Pojo</artifactId>
      	<!--依赖版本号-->
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>


</dependencies>
  • 依赖传递

    • 依赖具有传递性
      • 直接依赖:在当前项目中通过依赖配置建立的依赖关系
      • 间接依赖:被建立依赖关系的资源如果依赖其他资源,当前项目简介依赖其他资源
  • 依赖传递冲突问题

    • 路径优先:当依赖中出现相同的资源时,层级越深,优先级越低,层级越浅,优先级越高
    • 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序靠后的
    • 特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的

    image

image

  • 可选依赖optional标签。对外隐藏当前资源。——不透明

    可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依赖传递性

    <dependency>
        <groupId>com.mark</groupId>
        <artifactId>Maven_02_Pojo</artifactId>
        <version>1.0-SNAPSHOT</version>
        <!--可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源将不具有依赖传递性-->
        <optional>true</optional>
    </dependency>
    
  • 排除依赖:主动端来依赖的资源。——不需要

    在引入其他模块坐标时,该有不需要的依赖可使用排除依赖标签exclusions,被排除的资源无需指定版本

    <dependency>
        <groupId>com.mark</groupId>
        <artifactId>Maven_03_Dao</artifactId>
        <version>1.0-SNAPSHOT</version>
        <!--隐藏当前资源对应的依赖关系-->
        <exclusions>
            <exclusion>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
  • 可选依赖和排除依赖区别:

    • 可选依赖:控制当前模块资源能不能被别人发现
    • 排除依赖:用别人的资源发现不好的资源可以去掉

4.3 聚合与继承

  • 聚合

    • 聚合就是将多个模块组织成一个整体同时进行项目构建的过程称为聚合

    • 聚合工程:通常是一个不具有业务功能的“空”工程(有且仅有一个pom文件)

    • 作用:使用聚合工程可以将多个工程编组,通过对聚合工程进行构建,实现对所包含的模块进行同步构建

      • 当工程中某个模块发生更新(变更)时,必须保障工程中与已更新模块关联的模块同步更新,此时可以使用聚合工程来解决批量模块同步构建的问题
    • 步骤:

      • 创建新的模块

      • 修改打包方式为pom

        <groupId>com.mark</groupId>
        <artifactId>Maven_00_parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>pom</packaging>
        
      • 设置管理的模块名称

        <!--设置管理的模块名称-->
        <modules>
            <module>../Maven_01_SSM</module>
            <module>../Maven_02_Pojo</module>
            <module>../Maven_03_Dao</module>
        </modules>
        
  • 继承

    • 概念:继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承。简单来说,父工程的依赖子工程可以使用

    • 作用:

      • 简化配置
      • 减少版本冲突
    • 实现:

      • 在父工程的pom中设置打包类型为pom

      • 在父工程的pom文件中配置依赖关系(子工程将沿用父工程的依赖关系)

        <dependencies>
        
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
        
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.2.10.RELEASE</version>
            </dependency>
        	......
        </dependencies>
        
      • 在父工程中配置子工程可以选择的依赖

        <!--定义依赖管理-->
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                    <version>4.12</version>
                    <scope>test</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        
      • 在子工程中配置当前工程所继承的父工程

        <parent>
            <groupId>com.mark</groupId>
            <artifactId>Maven_00_parent</artifactId>
            <version>1.0-SNAPSHOT</version>
            <!--相对路径,可以快速地找到继承的工程。可以不写-->
            <relativePath>../Maven_00_parent/pom.xml</relativePath>
        </parent>
        
      • 子工程这时就可以使用父工程的依赖,同时还可以配置父工程中可选的依赖坐标

        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
        

        子工程中使用父工程中的可选依赖时,仅需要提供群组id和项目id,无需提供版本,版本由父工程统一提供,避免版本冲突
        子工程中还可以定义父工程中没有定义的依赖关系

  • 聚合与继承的区别

    • 作用
      • 聚合用于快速构建项目
      • 继承用于快速配置
    • 相同点:
      • 聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
      • 聚合与继承均属于设计型模块,并无实际的模块内容
    • 不同点:
      • 聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
      • 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

4.4 属性管理

  • 属性的配置与使用

    • 定义属性
    <properties>
        <spring.version>5.2.10.RELEASE</spring.version>
        <junit.version>4.12</junit.version>
    </properties>
    
    • 引用属性
    <dependencies>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
    
    <!--定义依赖管理-->
    <dependencyManagement>
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>${junit.version}</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </dependencyManagement>
    
  • 配置文件加载属性

    • 定义属性

      <properties>
          <spring.version>5.2.10.RELEASE</spring.version>
          <junit.version>4.12</junit.version>
          
          <jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db</jdbc.url>
      </properties>
      
    • 配置文件中引用属性

      jdbc.driver=com.mysql.cj.jdbc.Driver
      jdbc.url=${jdbc.url}
      jdbc.username=root
      jdbc.password=123
      
    • 开启资源文件目录加载属性的过滤器

      <build>
          <!--扩大maven构建范围-->
          <resources>
              <resource>
                  <!--使指定的目录里的文件可以解析${}的格式-->
                  <directory>${project.basedir}/src/main/resources</directory>
                  <filtering>true</filtering>
              </resource>
          </resources>
      </build>
      
    • 配置maven打war包时,可以忽略web.xml的检查

      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.3</version>
          <configuration>
            <failOnMissingWebXml>false</failOnMissingWebXml>
          </configuration>
        </plugin>
      </plugins>
      
      
  • 其他属性(了解)

    • 属性列表
      • 自定义属性(常用)
      • 内置属性
      • Setting属性
      • Java系统属性
      • 环境变量属性

    image

  • 版本管理

    • 工程版本:
      • SNAPSHOT(快照版本)
        • 项目开发过程中临时输出的版本,称为快照版本
        • 快照版本会随着开发的进展不断更新
      • RELEASE(发布版本)
        • 项目开发到进入阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构件文件是稳定的,即便进行功能的后续开发,也不会改变当前发布版本内容,这种版本称为发布版本
    • 发布版本
      • alpha版
      • beta版
      • 纯数字版

4.5 多环境配置与应用

  • 多环境开发

    • maven提供配置多种环境的设定,帮助开发者使用过程中快速切换环境
    <!--配置多环境开发-->
    <profiles>
        <!--定义开发环境:生产环境-->
        <profile>
          	<!--定义环境对应的唯一名称-->
            <id>env_dep</id>
          	<!--定义环境中专属的属性值-->
            <properties>
                <jdbc.url>jdbc:mysql://127.1.1.1:3306/ssm_db</jdbc.url>
            </properties>
            <!--设定是否为默认启动环境-->
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <!--定义开发环境:开发环境-->
        <profile>
            <id>env_pro</id>
            <properties>
                <jdbc.url>jdbc:mysql://127.2.2.2:3306/ssm_db</jdbc.url>
            </properties>
        </profile>
        <!--定义开发环境:测试环境-->
        <profile>
            <id>env_test</id>
            <properties>
                <jdbc.url>jdbc:mysql://127.3.3.3:3306/ssm_db</jdbc.url>
            </properties>
        </profile>
    </profiles>
    
    • 使用多环境(构建过程)

      mvn 指令 -P 环境定义id

      范例:

      mvn install -P env_pro

  • 跳过测试

    • 应用场景

      • 功能更新中并且没有开发完毕
      • 快速打包
      • ......
    • 方式一:快速跳过

      image

    • 方式二:配置跳过

      <plugins>
          <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <version>2.12.4</version>
              <configuration>
                	<!--false:不跳过所有test-->
                  <skipTests>false</skipTests>
                  <!--排除掉不参与测试的内容-->
                  <excludes>**/BookServiceTest.java</excludes>
              </configuration>
          </plugin>
      </plugins>
      
    • 方式三:命令跳过

      mvn package -D skipTests

4.6 私服

  • 私服简介

    • 私服就是一台独立的服务器,用于解决团队内部的资源共享与资源同步问题

    image

    • Nexus

      • Sonatype公司的一款maven私服产品
      • 下载地址:https://help.sonatype.com/repomanager3/product-information/download
    • 使用:

      • 启动服务器(命令行启动)

        nexus.exe /run nexus

      • 访问服务器(默认端口8081)

        http://localhost:8081

      • 修改基础配置信息

        • 安装路径下etc目录中nexus-default.properties文件保存有nexus基础配置信息,例如默认访问端口
      • 修改服务器运行配置信息

        • 安装路径下bin目录中nexus.vmoptions文件保存有nexus服务器启动的配置信息,例如默认占用内存空间
  • 私服仓库分类

    image

    仓库组是小组内共享资源用的,宿主仓库是小组内自己用的,代理仓库是所有项目组公用的

  • 资源上传与下载

    image

    • 本地仓库访问私服配置

      • 创建自己的两个仓库

        image

      • 配置本地仓库对私服的访问权限:打开maven的settings.xml文件,找到servers

        <!-- 配置访问私服的权限 -->
        <server>
          <!-- 私服中的服务器id名称 -->
          <id>mark-snapshot</id>
          <!-- admin -->
          <username>admin</username>
          <!-- 123 -->
          <password>123</password>
        </server>
        
        <server>
          <!-- 私服中的服务器id名称 -->
          <id>mark-release</id>
          <!-- admin -->
          <username>admin</username>
          <!-- 123 -->
          <password>123</password>
        </server>
        
      • 设置仓库组管理范围

        image

      • 配置映射关系

        <!-- 私服的访问路径 -->
        <mirror>
          <!-- 仓库组的id -->
          <id>maven-public</id>
          <mirrorOf>*</mirrorOf>
          <url>http://localhost:8081/repository/maven-public/h</url>
        </mirror>
        
    • 私服资源上传与下载

      • 配置工程保存在私服中的位置

        <!--配置当前工程保存在私服中的具体位置-->
        <distributionManagement>
            <repository>
                <id>mark-release</id>
                <url>http://localhost:8081/repository/mark-release/</url>
            </repository>
        
            <snapshotRepository>
                <id>mark-snapshot</id>
                <url>http://localhost:8081/repository/mark-snapshot/</url>
            </snapshotRepository>
        </distributionManagement>
        
      • 上传指令:deploy(在上传前要为所有的模块配置继承关系)

      • 然后接可以在仓库中看到上传的资源了

        image

      • 因为pom.xml中设置了版本为SNAPSHOT,因此只上传到了snapshot仓库,当修改为RELEASE时便可上传到mark-release仓库中

        <groupId>com.mark</groupId>
        <artifactId>Maven_00_parent</artifactId>
        <version>1.0-RELEASE</version>
        <packaging>pom</packaging>
        
        
    • 可以更换中央仓库

      image

这篇关于SpringMVC&Maven进阶的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!