基础

介绍

Spring家族有一套完全,之前学习的Spring5(Spring Framework),SpringMVC,都是Spring家族的组件,用于解决某一问题的,可以这么说

Spring家族还有很多的组件,在以前,整合起来会非常的麻烦,要写很多的配置文件,有了SpringBoot就会方便去整合,有了它,可以快速去构建出生产级别的Spring应用

背景

微服务

  • 微服务是一种架构风格
  • 一个应用拆分成一组小型服务
  • 每个服务运行在自己的进程里,可独立部署和升级
  • 服务之间使用轻量级HTTP进行交互
  • 服务围绕业务功能进行拆分
  • 可以由全自动部署机制独立部署
  • 去中心化,服务自治。服务可以使用不同的语言,不同的存储技术

分布式

分布式-有了微服务的架构,就必然会有分布式的概念

分布式的难点:

  • 远程调用
  • 服务发现
  • 负载均衡
  • 服务容错
  • 配置管理
  • 服务监控
  • 链路追踪
  • 日志管理
  • 任务调度

分布式难点的解决:

  • SpringBoot+SpringCloud

云原生

应用写出来就会有上云的问题,上云的过程中又会出现很多问题

难点:

  • 服务自愈
  • 弹性伸缩
  • 服务隔离
  • 自动化部署
  • 灰度发布
  • 流量自理

解决:

  • 云原生

优点

  • 创建独立的Spring应用
  • 内嵌web服务器
  • 自动starter依赖,简化构建配置
  • 自动配置Spring以及第三方功能
  • 提供生产级别的监控,健康检查以及外部化配置
  • 无代码生成,无需编写XML
  • 对于很多的东西(拿不到源码的Java文件),不需要再去写配置Bean,直接在配置文件就可以解决

缺点

迭代快,需要时刻关注变化

封装太深,内部原理复杂,难以精通

使用

多看文档。

https://docs.spring.io/spring-boot/docs/current/reference/html/

初使用

Maven版

父级依赖

  • 这个项目是父项目,用于依赖管理
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.3</version>
</parent>

引入场景依赖

  • 这个引入的是SpringBoot的依赖
  • 这个依赖会把与这个场景相关的依赖全部引入进来,不需要我们自己手动去找依赖!
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

编写启动程序

  • SpringBoot内置了有web服务器,所以说我们不用手动配置Tomcat服务器,直接写个程序启动就行了如下操作
  • @SpringBootApplication标识这个类是主程序类,用它去启动SpringBoot程序
  • 这个类里main方法就是用来启动的,写法如下
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}

编写配置文件

  • 在resource目录里(资源目录)新建一个application.properties,这一个配置文件可以将web服务器等配置进行修改,具体看官方文档
  • 如果没需求,这个文件可以不写
  • 下面demo就是修改服务访问端口的配置
server.port=8888

编写服务

  • 服务就是控制层
  • @RestController@Controller@ResponseBody的复合注解,标识这是控制层,同时标识这个类下的方法都会给页面一个响应
  • @ResponseBody这个注解,方法返回的字符串就会直接响应在浏览器内容中
@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String handle01() {
        return "Hello,SpringBoot2";
    }
}

简化部署

  • 何为简化部署?

    • 就是我们不用手动去打jar包(不加配置打出来的jar包是不能够直接运行的)
    • SpringBoot为我们提供了maven插件,可以让我们直接打成jar包,然后在终端中直接运行
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

启动服务

  • 启动服务就是将启动程序启动,启动后在浏览器里输入localhost:8888/hello,就会出现Hello,SpringBoot2的页面。
  • 启动服务可以在IDEA里面启动,也可以在终端中运行jar包

特点

依赖管理

修改依赖

  • 初使用里面的pom文件里面有个parent标签,那是SpringBoot帮我们在做依赖管理,我们可以依次往上翻,找到真正的父级项目依赖管理

  • spring-boot-dependencies.pom文件里可以看到SpringBoot帮我们把会用到的依赖都引入了,还有默认版本号

  • 如果我们需要自定义依赖版本号,那么就在自己项目的父类项目里按一下方式写

    • 具体细节翻看Maven
<properties>
		<mysql.verson>8.0.23</mysql.verson>
<properties>

修改场景启动器

  • 初使用项目中,我们看到只引入了spring-boot-starter-web依赖,这个就叫做场景启动器,就是web的依赖,会将web需要的依赖全部导入。
  • 具体翻源码spring-boot-starter-*
  • *-spring-boot-starter类似这种的就是第三方的场景启动器

自动配置

  • 自动配好Tomcat服务器

  • 自动配好SpringMVC常用组件

  • 自动配好Web常用功能

    • 字符编码
    • 文件上传
  • 默认包结构

    • 主程序所在的包以及其下面所有子包里的所有被默认扫描
  • 各个配置文件拥有默认值

    • application.properties文件中的默认配置都会绑定一个类,这个类会在容器中创建对象
  • 按需加载所有自动配置项

    • 自动配置的依赖在场景启动器的依赖中,名为spring-boot-autoconfigur.jar
    • 这个jar包里有很多的自动配置项,有缓存,aop等等
    • 但是SpringBoot会根据你引入的 场景启动器去开启需要的,不需要的则会爆红(具体如何实现的后续会讲)

容器功能

组件添加

@Configuration

  • 在SpringBoot中,我们要为容器添加bean组件,可以使用纯XML的方式,也可以写配置类,在配置类里引入bean
  • @Configuration标识在类上,告诉SpringBoot这是一个配置类
  • 在配置类里对方法使用@Bean注解,这样就可以为容器注册bean组件
  • 这个方法也是有讲究的,看下面的注释
  • 详情翻阅Spring笔记
@Configuration
public class MyConfig {
    
    /**
     *给容器注册组件
     *方法名作为组件id
     *返回类型就是组件类型
     */
    @Bean
    public User user01(){
        return new User("zhangsan", 18);
    }
}

full-lite

  • 上面的操作注册的组件(bean)是单例模式的,就是单实例对象。
  • 但我们有时候不需要单实例模式,这样就只需要下面的操作
  • 默认是true,会创建动态代理
@Configuration(proxyBeanMethods = false)
public class MyConfig {
    @Bean
    public User user01(){
        return new User("zhangsan", 18);
    }
}

@ComponentScan @Import

  • 在容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
  • 这种使用方式下就不用
  • 用在@Configuration注解下面
@Import({User.class, DBHelper.class})

@Conditional

  • @Conditional及其子注解是用于条件注册组件的

XML配置文件引入

  • 我们有时候会遇到写了很多的配置文件,想迁移到配置类里里面,但是太繁琐,这个时候我们就在配置类哪里使用注解将beans.xml引入
  • 使用下面这个注解,就会把XML文件中配置的bean组件注册到容器中
@ImportResource("classpath:beans.xml")

配置绑定(Properties)

  • 配置绑定就是将配置文件里面的信息注入到某个类里
  • 以下两种实现方式
/**
 *方案一:
 *perfix就相当于是这个类的别名
 *然后就可以在SpringBoot配置文件里写了
 */
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
/**
 *方案二:
 *在配置类上面使用注解@EnableConfigurationProperties(类名.class)
 *只在Car类上使用@ConfigurationProperties(prefix = "mycar")
 */

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(Car.class)
public class MyConfig {

}
mycar.brand=BYD
mycar.price=100000

开发小技巧

Lombok

  • Lombok好用,帮我们简化开发,我们的pojo类,可以只写字段,而使用Lombok提供的注解简化
  • 使用注解后就可以不写构造器,set/get方法,toString方法等等,Lombok会在程序编译的时候为我们补上
  • 记得导包和安装IDEA的lombok插件
  • 注解不止下面几种,还有别的,比如@Slf4j,用了这个我们就不用在主程序去打印了,直接在类哪里使用log.info()
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
    private String brand;
    private int price;

}

Spring Initailizer

前面哪个项目是使用maven手动去设置的SpringBoot项目,我们可以继续简化开发,使用Spring的初始化程序去创建项目

dev-tools

相当于就是热部署,想用速度快些,添加依赖,Command+F9

配置文件

SpringBoot开发的时候,配置文件有两种

一种是:properties

二种是:yaml

yaml

yaml是一种标记语言,类似markdown,html等,非常适合用来做以数据为中心的配置文件

基本语法

  • key:value,kv之间有空格
  • 大小写敏感
  • 使用缩进表示层级关系,缩进不允许使用tab,只允许空格
  • 缩进的空格数不做要求,对齐即可
  • #表示注释
  • ‘’和”” 表示的字符串内容 会被 转义/不转义

数据类型

字面量

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v

对象

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null
行内写法:  k: {k1:v1,k2:v2,k3:v3}
#或
k: 
	k1: v1
  k2: v2
  k3: v3

数组

  • 数组:一组按次序排列的值。array、list、queue
行内写法:  k: [v1,v2,v3]
#或者
k:
 - v1
 - v2
 - v3

示例

需要注意的是List就是数组

@ConfigurationProperties(prefix = "person")
@Component
@ToString
@Data
public class Person {

    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private Pet pet;
    private String[] interests;
    private List<String> animal;
    private Map<String, Object> score;
    private Set<Double> salarys;
    private Map<String, List<Pet>> allPets;
}
@Component
@ToString
@Data
public class Pet {
    private String name;
    private Double weight;
}
person:
  user-name: "Jsckot"
  boss: true
  birth: 2001/11/28 20:12:33
  age: 20
  pet:
    name: "tomcat"
    weight: 23.4

  interests: ["篮球","游泳"]

  animal:
    - Jerry
    - Mario
  
  score:
    english:
      first: 20
      second: 40
      third: 50
    math: {23,45,65}
    chinese: {first: 34,second: 43,third: 65}

  salarys: [1234,6789,9877,7777]

  all-pets:
    sick:
      - {name: tom}
      - {name: jerry,weight: 47}
    health: [{name: Mario,weight: 47.9}]

yml简化开发

我们在使用yml以及properties两种文件类型做配置绑定的时候,IDEA不会提醒我们去写类的字段,这时候我们可以使用SpringBoot提供的依赖去辅助我们开发

tip:这个依赖只是用于方便我们的开发,项目运行并不需要这个,所以我们可以将这个依赖在编译的时候移除是,以免JVM需要加载过多无用的类

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </exclude>
                    <exclude>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-configuration-processor</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

WEB开发

WEB开发中,SpringBoot为我们做了很多的自动配置,具体翻看文档

简单功能

静态资源访问

  • 类路径下的/staticor/publucor/resourcesor/META-INF/resources
  • 只要文件在类的这些目录下,项目启动后就可以通过域名:端口/静态资源名进行访问
  • 原理:静态映射/**
  • 请求进来先看Controller能否处理,不能处理的则又交给静态资源处理器,就会去指定目录寻找,找不到就404

img

静态资源访问前缀

  • 这个功能挺有用的,上面的操作是默认的,没有前缀,直接使用域名:端口/静态资源名
  • 我们可以进行操作,使其可以按这种路径访问域名:端口/访问前缀/静态资源名
spring:
  mvc:
    static-path-pattern: /res/**

自定义资源路径

  • 自定义静态资源路径可以使用自定义的,默认是关闭的,开启后上面的默认静态资源就没用了,就完全按照自定义的路径来

  • 开启后就是按照以下的操作来

    • 将静态资源文件放到/haha目录下,然后按照自己设定的访问前缀进行访问
    • 这个路径是一个数组,可以自定义多个资源路径
spring:
  web:
    resources:
      static-locations: [classpath:/haha/]

webjars

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>

欢迎页

  • index.html文件放到静态资源目录下,启动程序的时候就会首先访问这个

    • 可以配置自定义静态资源路径
    • 但是不可以配置静态资源的访问前缀
  • controller也能够处理 /index,都会被当成静态页

自定义Favicon

  • Faviocn就是网站在浏览器中访问的时候,浏览器上面的小图
  • 直接将文件改为favicon.ico,然后放在静态资源目录下即可

请求处理

请求映射

  • 以前:

    • /getUser 获取用户
    • /deleteUser 删除用户
    • /editUser 修改用户
    • /saveUser 保存用户
  • 现在:

    • /user GET-获取用户
    • /user DELETE-删除用户
    • /user PUT-修改用户
    • /user POST-保存用户
  • 使用:

    • 先在SpringBoot配置文件中开启隐藏method
    • 表单中使用POST请求
    • 前端使用隐藏域,name属性为_method
    • 请求映射哪里使用 method = RequestMethod.GET指定
  • 原理:

    • 表单提交会带上**_method=PUT**
    • 请求过来被HiddenHttpMethodFilter拦截
      • 请求是否正常,并且是POST
      • 获取到**_method**的值。
      • 兼容以下请求;PUT.DELETE.PATCH
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
  • 注意:这个主要是用于页面开发,所以在表单请求的时候才会有效,因为表单只能够有post和get

    • 如果直接http请求,就会直接变成对应的请求方式
  • 使用PostMan等测试工具直接发送DELETE,就会和上一行说得一样,直接会变成DELETE,而不会经过filter

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true
@RestController
public class HelloController {
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
        return "POST-张三";
    }


    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
        return "PUT-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-张三";
    }
}
/**上面的操作**/
@RestController
public class HelloController {
    @GetMapping("/user")
    public String getUser(){
        return "GET-张三";
    }

    @PostMapping("/user")
    public String saveUser(){
        return "POST-张三";
    }


    @PutMapping("/user")
    public String putUser(){
        return "PUT-张三";
    }

    @DeleteMapping("/user")
    public String deleteUser(){
        return "DELETE-张三";
    }
}

改变默认_method

  • 修改后记得去前端进行连调修改
package com.Jsckot.thirdweb.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;

@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
}

普通参数与基本注解

  • SpringBoot的web层是基于SpringMVC实现的,在Controller的请求方法的参数中,可以传很多的参数来获取信息
  • 下面介绍的就是一些常用的信息

基本注解

  • @PathVariable–路径变量–就是请求路径里的变量 - user/{id}

  • @RequestHeader–获取请求头信息

  • @RequestParam–获取请求参数–就是?携带的信息

  • @CookieValue–获取cookies

  • @RequestBody–获取请求体信息[post请求的数据]

  • @RequestAttribute–获取request域信息

  • @MatrixVariable–矩阵变量

  • 获取的信息都是健值对形式的,可以使用上面的注解将全部的信息或者某一个key的信息获取

  • 获取全部就是将获取的信息放在Map中,健值对嘛

  • 获取特定的信息就是 将这个key放注解的参数里传给具体的变量

@RestController
public class ParameterTestController {

    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,Object> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> headers) {
        Map<String,Object> map = new HashMap<>();

        map.put("id",id);
        map.put("name",name);
        map.put("pv",pv);
        map.put("User-Agent",userAgent);
        map.put("header",headers);

        return map;
    }
}

ServletAPI

  • 参数也可以传ServletAPI参数,比如HttpServlet等等

复杂参数

  • 就是传存map,也都是request域
  • 键值对形

自定义对象参数-POJO

  • SpringMVC支持将前端的数据模型封装在Java的类里面,然后前端的数据发起请求后可以通过请求方法直接封装在POJO类对象供后端进行操作

    • 支持级联属性,比如下面5,6行的代码
<form action="/saveuser" method="post">
        姓名: <input name="userName" value="zhangsan"/> <br/>
        年龄: <input name="age" value="18"/> <br/>
        生日: <input name="birth" value="2019/12/10"/> <br/>
<!--    宠物姓名:<input name="pet.name" value="阿猫"/><br/>-->
<!--    宠物年龄:<input name="pet.age" value="5"/>-->
        宠物: <input name="pet" value="啊猫,3"/>
        <input type="submit" value="保存"/>
</form>
// 下面的代码可以不写,如果需要自定义handle再来写
@Bean
public WebMvcConfigurer webMvcConfigurer() {
    // 这是Lamada表达式的写法
    return new WebMvcConfigurer() {
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverter(new Converter<String, Pet>() {
                @Override
                public Pet convert(String source) {
                    if (!StringUtils.isEmpty(source)) {
                        Pet pet = new Pet();
                        String[] split = source.split(","); 
                        pet.setName(split[0]);
                        pet.setAge(split[1 ]);
                    }
                    return null;
                }
            });
        }
    };
}

响应处理

响应JSON

  • 准备工作

    • web场景依赖+jackson.jar
    • 映射方法上标注@ResponseBody注解
    • 返回的就是JSON类型
  • 上面的测试中,返回的都是XML

响应XML

  • 将jackson-dataformat-xml.jar的依赖导入进来
  • 映射方法上标注@ResponseBody注解

内容协商

  • 内容协商就是使用会根据请求头信息获取返回的类型,是json还是xml

  • 只不过需要把xml和json对应的依赖导入

  • 如果要返回xml,就把请求头的accept改为application/xml

  • 这样的话就会返回xml格式的数据

    • 也可以使用携带参数来实现,开启这个参数就行了
    • 就像这样http://localhost:8080/saveuser?format=xml
// 开启内容协商
spring:
    contentnegotiation:
      favor-parameter: true
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

自定义内容协商处理

  • 可以使用WebMvcConfig去定制化SpringMVC功能,需要的时候自己去看文档

视图解析与模板引擎

Thymeleaf使用

  • 导入Thymeleaf的依赖-SpringBoot会为我们配置好Thymeleaf以及模板引擎,视图解析器
  • 在html页面中引入Thymeleaf的名称空间-可以直接修改IDEA的配置信息
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

FreeMaker

也是一种模板引擎,可以了解一下

解决表单重复提交

  • 在使用请求映射的情况下会出现表单重复提交样子,比如这个demo
  • 因为它是映射的main.html这个文件,如果已经映射了再刷新就会再次发起请求
@PostMapping("/login")
public String main(String username,String passward) {
    return "main";
}
  • 解决方案如下:
  • 访问/login这个请求映射的时候,满足情况的话会带着请求参数直接重定向到具体的页面,而不是请求本身。
  • 这个点去了解一下请求与重定向等等
@PostMapping("/login")
public String main(String username,String passward) {
    //登录成功重定向到main.html
    return "redirect:/main.html";
}


@GetMapping("/main.html")
public String mainPage() {
    return "main";
}

抽取公共部分

  • 将公共部分抽取后放到一个html页面中,然后使用th:formtarget命名,再在原页面中引入即可

拦截器

  • 拦截器和原生Servlet里面的 Filter功能类似,都是用于拦截的

HandlerInterceptor接口

  • HandlerInterceptor接口是拦截器接口,有三个默认方法,用于实现拦截。

操作

  • 新建一个类实现拦截器接口,实现拦截的方法
  • 将拦截器类注入到容器中,在容器中配置拦截以及放行的路径
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/","/login","/css/**","fonts/**","/images/**","/js/**","/aa/**");
    }
}
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) throws Exception {
        //登录检查逻辑
        HttpSession session = request.getSession();

        Object loginUser = session.getAttribute("loginUser");

        if(loginUser != null){
            //放行
            return true;
        }

        //拦截住。未登录。跳转到登录页
        request.setAttribute("msg","请先登录");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

文件上传

  • SpringBoot将文件上传封装到了Multipart里面了,原理后续专门看,很方便

操作

  • 前端写好文件上传的表单

    • 请求方法使用post
    • action使用thymeleaf语法,提交给请求映射
    • enctype设置为 multipart/form-data
  • 后端将请求映射的方法写好,将文件写入目标目录,具体看demo

  • 修改SpringBoot配置文件,因为默认上传大小只有1MB

<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
/**
 *使用注解将前端上传的文件标识
 */
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
                     @RequestParam("username") String username,
                     @RequestPart("headerImg") MultipartFile headerImg,
                     @RequestPart("photos") MultipartFile[] photos) throws Exception{

    log.info("{}---{}---{}---{}---{}",email,username,headerImg.getSize(),photos.length);
    if (!headerImg.isEmpty()) {
        //获取原文件名
        String originalFilename = headerImg.getOriginalFilename();
        
        //使用Multipart的transferTo方法将文件写入目标目录
        headerImg.transferTo(new File("/Users/Jsckot/Desktop/upload/"+originalFilename));
    }

    if (photos.length > 0) {
        for (MultipartFile photo : photos) {
            if (!photo.isEmpty()) {
                String originalFilename = photo.getOriginalFilename();
                photo.transferTo(new File("/Users/Jsckot/Desktop/upload//"+originalFilename));
            }
        }
    }
    
    return "redirect:/main.html";
}

异常处理(TODO)

默认情况下SpringBoot提供/error处理所有请求的映射

  • 对于机器客户端,它将响应json
  • 对于浏览器,会响应一个页面
{
    "timestamp": "2022-02-18T08:35:42.807+00:00",
    "status": 999,
    "error": "None",
    "message": "No message available"
}

自定义页面处理

  • 自定义处理就是可以在resource/templates目录下创建一个error目录,里面放对应的页面,SpringBoot会帮我们自动解析,当遇到访问为5xx或者404的时候就会自动访问页面
  • 我们也可以通过json将报错栈放到页面上
  • 5xx表示响应状态码为5开头就会自定响应这个页面

img

<section class="error-wrapper text-center">
    <h1><img alt="" src="images/500-error.png"></h1>
    <h2>OOOPS!!!</h2>
    <h3 th:text="${message}">Something went wrong.</h3>
    <p class="nrml-txt" th:text="${trace}">Why not try refreshing you page? Or you can <a href="#">contact our support</a> if the problem persists.</p>
    <a class="back-btn" th:href="@{/main.html}"> Back To Home</a>
</section>

定制异常处理

处理全局异常

  • @ControllerAdvice@ExceptionHandler注解配合使用进行全局异常捕获
  • @ControllerAdvice继承了@Component
  • @ExceptionHandler注解用于自定全局异常类型
  • 返回的是视图地址
  • 这个操作是全局的,全局内遇到后会自动进行响应
/**
 * 处理整个web controller异常
 */
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler  {
    /**
     * 处理异常
     * @return
     */
    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})
    public String handlerArithException(Exception e) {
        return "login"; //返回一个视图地址
    }
}

@ResponseStatus+自定义异常

  • 自定义异常,然后再在程序中抛出这个异常
  • reason属性会被封装在message里面
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException{

    public UserTooManyException() {
    }

    public UserTooManyException(String message) {
        super(message);
    }
}

自定义异常解析器

  • 这是自定义异常解析器,用的不多,配合Order注解可以实现优先级的分配
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception ex) {

        try {
            response.sendError(511,"我喜欢的错误");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

原生组件

  • 原生组件比如Servlet,Filter,Listener等等,注入容器有两种方式

原生注解➕包扫描

  • 原生注解就是@WebServlet,再在启动器上面加一个Servlet的包扫描注解
  • 这样的话就可以通过/myservlet路径进行访问,这个访问是不受Spring拦截器的拦截的,直接访问
@Slf4j
@WebServlet(urlPatterns = "/myservlet")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        log.info("11111");
        resp.getWriter().write("666666");
    }
}
@ServletComponentScan(basePackages = "com.Jsckot.webtest.servlet")
@SpringBootApplication
public class ForthTestApplication {

RegistrationBean注解

  • 使用RegistBean的话,就必须先写好上面的MyServlet的自定义类
@Configuration
public class MyRegistConfig {
    @Bean
    public ServletRegistrationBean myServlet() {
        MyServlet myServlet = new MyServlet();
        return new ServletRegistrationBean(myServlet);
    }
}

定制Servlet容器

img

数据访问

  • 数据访问就和数据库挂钩了。

数据源的自动配置

JDBC场景

  • 导入JDBC依赖
  • 导入数据库驱动-因为SpringBoot也不知道我们需要使用哪个-而且也有版本仲裁
  • 配置文件写好数据源信息
  • 再使用JdbcTemplate进行单元测试
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssmbuild?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    username: root
    password: xxxxxxxxx
    driver-class-name: com.mysql.cj.jdbc.Driver

使用Druid数据库连接池

  • JDBC依赖是是使用的Hikari的数据库连接,现在整合Druid

  • 引入三方的技术,有两种方式

    • 自定义-就是将依赖引入,一项项的去写配置
    • 找对应的starter

自定义方式

  • 导入Druid的依赖

  • 新建一个配置类

  • 配置连接信息

    • 连接信息的话和上面一样,写在SpringBoot的配置文件中
    • 将配置信息导入到数据库连接池的配置信息中
  • 配置Druid监控页面等-根据官方文档

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.29</version>
</dependency>
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssmbuild?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    username: root
    password: xxxxxxx
    driver-class-name: com.mysql.cj.jdbc.Driver
    filters: stat,wall
@Configuration
public class MyDataSourceConfig {
    //配置连接池信息
    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource ;
    }

    /**
     * 配置监控页
     * @return
     */
    @Bean
    public ServletRegistrationBean statViewServlet() {
        //配置监控页路径
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet,"/druid/*");

        //配置监控页访问账号密码
        registrationBean.addInitParameter("loginUsername","admin");
        registrationBean.addInitParameter("loginPassword","123456");

        return registrationBean;
    }

    /**
     * WebStatFilter -用于采集Web-Jdbc关联监控的属性
     * 拦截排除的连接,将拦截到的展示在监控平台
     * @return
     */
    @Bean
    public FilterRegistrationBean WebStatFilter() {
        WebStatFilter webStatFilter = new WebStatFilter();
        FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(webStatFilter);
        registrationBean.setUrlPatterns(Arrays.asList("/*"));
        registrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return registrationBean;
    }
}

starter方式

  • 引入starter-spring-boot-druid
  • SpringBoot配置文件中写配置
  • SpringBoot会为我们自动配置,不用手写AutoConfig
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.17</version>
</dependency>
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ssmbuild?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    username: root
    password: xxxxxxs
    driver-class-name: com.mysql.cj.jdbc.Driver

    druid:
      aop-patterns: com.Jsckot.webtest.*  #监控SpringBean
      filters: stat,wall  #sql监控和防火墙

      stat-view-servlet:
        enabled: true  #开启监控页
        login-username: admin  #监控账号
        login-password: 123456 #监控密码
        reset-enable: false  #不允许重置

      web-stat-filter:
        enabled: true  #开启url拦截
        url-pattern: /*  #拦截的请求
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'  #不拦截的请求

      filter:
        stat:  对上面filters里面的进行详细配置
          slow-sql-millis: 1000
          log-slow-sql: true
          enabled: true
        wall:
          enabled: true
          config:
            delete-allow: false #不允许删表

整合MyBatis

  • 引入mybatis-spring-boot-starter
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot</artifactId>
    <version>2.2.3-SNAPSHOT</version>
</dependency>

配置方式

  • 全局配置文件-自己写好,放在/resource/mybatis

  • SqlSessionFactory:【SpringBoot已自动配好,不用再手动进行配置bean操作】

  • SqlSession:【自动配置了SqlSessionTemplate,里面自动组合了SqlSession】

  • Mapper:只要我们写的Mybatis的接口标注了@Mapper注解就会被自动扫描进来

  • 超级方便,再自己写好接口和映射文件即可,可以搭配IDEA插件MyBatisX进行使用

  • img

  • 操作

    • 导入mybatis的starter
    • 编写mapper接口–(一定要标注@Mapper注解)
    • 编写sql映射文件并绑定mapper接口
    • application.yml中指xml全局配置文件的位置和mapper映射文件
      • mybatis-config.xml全局配置文件可以不写,直接使用 mybatis-configuration(application.yml)进行配置
#配置MyBatis的规则
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml   #全局配置文件,在配置文件里写全局配置
  mapper-locations: classpath:mybatis/mapper/*.xml        #mapper映射文件

注解方式

  • 注解模式就是使用注解,不写mapper映射文件,不过只适合简单的sql语句

混合模式

  • 混合模式就是将配置方式和注解方式 混合使用,简单的sql语句就使用注解,复杂的sql语句就使用配置方式

整合MyBatis-Plus

  • 引入MyBatis-Plus的starter-【可以不用引入MyBatis的】

    • MyBatis-Plus的mapper映射文件目录,可以不写,因为有默认值 classpath:/mapper/**/*.xml,以后我们就把mapper映射文件放到这里
  • 写Mapper接口-将接口继承BaseMapper类,这样的话就能满足基本的CRUD操作

  • MyBatis-Plus的注解也可以完成很多的步骤!!比如TableField

  • …学习MyBatisPlus后再来复习

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>

单元测试

  • SpringBoot2.2.0版本开始引入JUnit5作为单元测试默认库
  • 和以前的有很大不同
  • SpringBoot2.4及以上移除了JUnit4

img

准备工作

  • 引入JUnit的starter依赖
  • 使用注解@Test,jupiter包下的
  • 编写方法
  • JUnit具有Spring的功能
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

常见注解

img

@DisplayName注解

img

@DisplayName("JUnit5功能测试1")
public class Junit5Test {

    @DisplayName("测试displayname注解")
    @Test
    void testDisplayName() {
        System.out.println(1);
    }
}

@BeforeEach注解

这个注解是用于提示的,每个测试开始前都会提示这句话

@BeforeEach
void testBeforeEach() {
    System.out.println("测试即将开始...");
}

@AfterEach注解

和上面的一致

@AfterEach
void testAfterEach() {
    System.out.println("测试已经结束...");
}

···

断言机制

断言是测试方法中的核心部分,用来对测试需要满足的条件进行验证

分为以下几个品类

  • 检查业务逻辑返回的数据是否合理
  • 所有测试运行结束以后会有一个详细的测试报告

img

前置条件

和断言类似,可以说是假设。

它不会停止测试,而是抛出这里的异常进行下面的

具体翻看文档

嵌套测试

翻文档

参数化测试

翻文档

指标监控