SpringBoot知识梳理

SpringBoot 2 快速梳理

入门文档
Spring Boot Reference Documentation🎉
Spring Boot 中文文档🎉

SpringBoot 是为了简化开发, 推出的封神框架(约定优于配置[COC],简化了 Spring 项目 的配置流程)

0、快速入门

0.1 pom.xml

简化了 Maven 项目的 pom.xml 依赖导入, 可以说是一键导入Spring,SpringMVC,Tomcat等web开发所需jar包

<!-- 导入 Spring Boot 父工程,规定的写法 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.3</version>
</parent>

<dependencies>
    <!-- 导入 web 项目场景启动器,会自动导入和 web 开发相关依赖,非常方便 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
	
    <!--引入Spring Boot配置处理器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
  </dependencies>

0.2 开启

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
        System.out.println("启动了");
    }
}

0.2.1 测试

建立一个Contller,映射一个接口用于测试,返回一个字符串即可,用于展示返回成功。

@Controller
public class HelloController {
    @RequestMapping("/hello")
    @ResponseBody
    public String hello(){
        return "Hello, Spring Boot";
    }
}

访问:http://127.0.0.1:8080/hello

显示如下效果表示成功

在这里插入图片描述

0.3 梳理

  • Spring Boot > Spring > Spring MVC
  • Spring MVC 只是 Spring 处理 WEB 层请求的一个模块/组件, Spring MVC 的基石是 Servlet
  • Spring 的核心是 IOC 和 AOP, IOC 提供了依赖注入的容器 , AOP 解决了面向切面编程

0.4 约定优于配置

约定优于配置(Convention over Configuration/COC),又称按约定编程,是一种软件设计规范, 本质上是对系统、类库或框架中一些东西假定一个大众化合理的默认值(缺省值)
简单来说就是假如你所期待的配置约定的配置一致,那么就可以不做任何配置,约定不符合期待时, 才需要对约定进行替换配置

Spring Boot的每个版本都提供了一个它所支持的依赖的列表。 在实践中,你不需要在构建配置中为这些依赖声明版本,因为Spring Boot会帮你管理这些。 当你升级Spring Boot本身时,这些依赖也会一同升级。你仍然可以指定某个依赖的版本,来覆盖spring boot默认的版本。

想要做到这一功能,首先你只需要将项目配置为继承自spring-boot-starter-parent,类似这样,就可以只需要在此依赖项上指定 Spring Boot 版本号,在导入其他启动器时,则可以安全地省略版本号。

<!-- Inherit defaults from Spring Boot -->
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>3.2.0-SNAPSHOT</version>
</parent>

常见两种情况

  1. 因为在spring-boot-starter-parent 的父项目中,声明了开发中常用的依赖的版本号。即如果程序员没有指定某个依赖 jar 的版本,则以父项目指定的版本为准
    • 双击pom.xmlspring-boot-starter-parent,再进入它的父项目spring-boot-dependencies即可查询父项目中默认的版本

在这里插入图片描述
2. (不推荐)当你引入某个依赖而不想使用父项目默认的版本时,你只需在引入该依赖时赋予其明确的<version>标签即可,例如:

<!-- 引入 mybatis-plus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>
  1. (推荐)你也可以通过重写自己项目中的属性来重写单个依赖项。例如,要使用不同版本的 SLF4J 库和 Spring Data 发布系列,您需要将以下内容添加到您的 pom.xml
<properties>
    <slf4j.version>1.7.30</slf4j.version>
    <spring-data-releasetrain.version>Moore-SR6</spring-data-releasetrain.version>
</properties>

0.5 Starter

Starter是一系列开箱即用的依赖,你可以在你的应用程序中导入它们。 通过你Starter,可以获得所有你需要的Spring和相关技术的一站式服务,免去了需要到处大量复制粘贴依赖的烦恼。 例如,如果你想开始使用Spring和JPA进行数据库访问,那么可以直接在你的项目中导入 spring-boot-starter-data-jpa 依赖。

Starter含了很多你需要的依赖,以使项目快速启动和运行,并拥有一套一致的、受支持的可管理的过渡性依赖。

关于Starter的命名

所有官方的Starter都遵循一个类似的命名模式;spring-boot-starter-*,其中 * 是一个特定类型的应用程序。 这种命名结构是为了在你需要寻找Starter时提供帮助。 许多IDE中集成的Maven可以让你按名称搜索依赖。 例如,如果安装了相应的Eclipse或Spring Tools插件,你可以在POM编辑器中按下 ctrl-space ,然后输入 “spring-boot-starter” 来获取完整的列表。

正如 “创建你自己的 Starter” 一节所解释的,第三方启动器不应该以 spring-boot 开头,因为它是留给Spring Boot官方组件的。 相反,第三方启动器通常以项目的名称开始。 例如,一个名为 thirdpartyproject 的第三方启动器项目通常被命名为 thirdpartyproject-spring-boot-starter

0.6 修改配置

在resources\application.properties 文件中 修改配置

application.properties

0.6.0 在配置文件中自定义属性值

@Value(“${pre.pr0perty}”)

在properties 文件中可以自定义配置属性值,通过@Value(“${}”)获取对应属性值。例如:

配置文件:

my:
  test: yu

类:

@Controller
public class HelloController {
    @Value("${my.test}")
    private String s;

    @RequestMapping("/hello")
    @ResponseBody
    public String hello(){
        System.out.println(s);
        return "Hello, Spring Boot";
    }
}

另外,我们还可以使用 @Value("${property.name:defaultValue}") 指定默认值,以注入配置值。

使用类型安全的配置属性绑定@ConfigurationProperties

与其使用 @Value 分别绑定每个属性值,我们可以创建一个包含属性的类,然后让 Spring 框架通过使用,类中属性必须有set方法@ConfigurationProperties 将配置值绑定到该配置属性类的实例中,如下所示:

配置:

jyu:
  username: yu
  eat: cake
  age: 18
  sprot: football

类:(使用lombok的@Setter省略了set方法)

@ConfigurationProperties(prefix = "jyu")
@Setter
public class Jyu {
    private String username;
    private Integer age;
    private String sport;
    private String eat;
}

定义配置属性类后,我们需要使用 @EnableConfigurationProperties(jyu.class) 启用绑定。这通常在应用程序 main 类中完成。

或者在启动类上添加@ConfigurationPropertiesScan 来扫描所有注解了 @ConfigurationProperties 的类, 并自动注册它们,而不是显式地指定每个配置属性类。如下所示:

@SpringBootApplication
@ConfigurationPropertiesScan
public class FlashSaleApplication {
    public static void main(String[] args) {
        SpringApplication.run(FlashSaleApplication.class,args);
    }
}

想要了解更多操作,推荐阅读这篇文章:@ConfigurationProperties 注解使用姿势,这一篇就够了

0.6.1 常见配置

application.properties:

#tomcat监听端口
server.port=9090
#应用的上下文路径(项目路径)
server.servlet.context-path=/jyu

#开启页面表单的Rest功能
spring.mvc.hiddenmethod.filter.enabled=true
#开启基于请求参数的内容协商功能
spring.mvc.contentnegotiation.favor-parameter=true
#指定一个内容协商的参数名
spring.mvc.contentnegotiation.parameter-name=format

#静态资源路径
spring.mvc.static-path-pattern=/static/**
#配置视图解析器
#这里是需要注意 prefix需要和当前的static-path-pattern一致
spring.mvc.view.suffix=.html
spring.mvc.view.prefix=/static/

application.yml:

server:
  port: 10090 #tomcat 监听端口
  servlet:
    context-path: /jyu #应用的上下文路径
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/furn_boot?useSSL=true&useUnicode=true&characterEncoding=utf-8
    username: root
    password: abcdef
    driver-class-name: com.mysql.cj.jdbc.Driver
  mvc:
    static-path-pattern: /** #静态资源访问路径
    hiddenmethod:
      filter:
        enabled: true # 开启页面表单的Rest功能
    view:   #配置视图解析器
      prefix: /     #这里是需要注意 prefix需要和当前的static-path-pattern一致
      suffix: .html
    contentnegotiation: # 内容协商
      favor-parameter: true
      parameter-name: format
            
mybatis-plus:
  mapper-locations: classpath:mapper/*.mapper
  type-aliases-package: com.jyu.furn.bean
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

1、容器

@Component、``@Controller@Service@Repository` 依然可以通过注解注入组件,与在Spring中使用方法无异。

1.1 @Configuration

通过配置类注入组件,功能类似于Spring用于注入组件的xml文件,并且配置类兼容xml注入组件的形式。

@Bean:给容器中添加组件,方法名就是Bean在容器中的默认ID。也可以通过@Bean("xxx")的方式指定BeanID。

@Scope(value = "prototype"):指定单例模式还普通模式。

@Import :方式注入的组件, 默认组件的名字就是全类名

1.1.1 proxyBeanMethods

proxyBeanMethods:代理 bean 的方法

  • Full:@Configuration(proxyBeanMethods = true):保证每个@Bean 方法被调用多少次返回的组件都是单实例的, 是代理方式。

  • Lite:@Configuration(proxyBeanMethods = false):每个@Bean 方法被调用多少次返回的组件都是新创建的, 是非代理方式。

proxyBeanMethods 是在 调用被@Bean注解的方法才生效,因此,需要先获取配置类,通过配置类对象再调用方法获取 Bean。

如何选择:

  • 组件依赖必须使用 Full 模式默认。

  • 如果不需要组件依赖使用 Lite 模 ,Lite 模 也称为轻量级模式,因为不检测依赖关系,运行速度快。

1.2 @Condtional

条件装配:满足 Conditional 指定的条件,则进行组件注入

@Conditional 是一个根注解,下面有很多扩展注

1.2.1 @Conditional扩展注解

@Conditional扩展注解作用
ConditionalOnJava系统的java版本是否符合要求
@ConditionalOnBean容器中存在指定Bean
@ConditionalOnMissingBean容器中不存在指定Bean
@ConditionalOnExpression满足SpEL表达式指定
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定资源文件
@ConditionalOnWebApplication当前是web环境
@ConditionalOnNotWebApplication当前不是web环境
@ConditionalOnJndiJNDI存在指定项

1.3 @ImportResource

原生配置文件引入, 也就是可以直接导入 Spring 传统的 beans.xml ,可以认 为是 SpringBoot 对 Spring 容器文件的兼容.

@ImportResource("classpath:beans.xml")
public class BeanConfig {}

1.4 属性数据绑定

使用 Java 读取到 SpringBoot 核心配置文件 application.properties 的内容, 并且把它封装到 JavaBean 中

0.6.0在配置文件中自定义属性值 已经详细展示

  1. xxx.属性名=abc
  2. 在bean类上注解 @ConfigurationProperties(prefix = “xxx”)
  3. 在配置类上注解 @EnableConfigurationProperties(Furn.class)

如果application.properties 有中文, 需要转成 unicode 编码写入, 否则出现乱码

配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

1.5 lombok

  • 简化JavaBean开发, 可以使用Lombok的注解让代码更加简洁。

  • Java 项目中,很多没有技术含量又必须存在的代码:POJO的getter/setter/toString;异常 处理;I/O流的关闭操作等等,这些代码既没有技术含量,又影响着代码的美观,Lombok 应运而生

  • 推荐阅读文章:

配置:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

1.6 yaml

YAML 是"YAML Ain’t a Markup Language"(YAML 不是一种标记语言) 的递归缩写。在开发 的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言), 是为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重命名 【百度百科】

推荐阅读:

1.6.0 修改配置

使用 yaml 配置文件 和 JavaBean进行数据绑定,如 果 application.properties 和 application.yml 有 相 同 的 前 缀 值 绑 定 , 则 application.properties 优先级高, 开发时,应当避免。

server:
  port: 10090 #tomcat 监听端口
  servlet:
    context-path: /jyu #应用的上下文路径

spring:
  mvc:
#    static-path-pattern: /hspres/** #修改静态资源访问的路径/前缀
	#启用了HiddenHttpMethodFilter,开启页面表单的Rest功能
    hiddenmethod:
      filter:
        enabled: true 
    #配置视图解析器
    view:       
      suffix: .html
      prefix: /        #这里是需要注意 prefix需要和当前的static-path-pattern一致
    contentnegotiation:
      favor-parameter: true #开启基于请求参数的内容协商功能
      parameter-name: hspformat #指定一个内容协商的参数名
  web:
    resources:
      #修改/指定 静态资源的访问路径/位置
      static-locations: ["classpath:/hspimg/","classpath:/META-INF/resources/",
                         "classpath:/resources/", "classpath:/static/", "classpath:/public/"]      #String[] staticLocations

1.6.1 yaml 基本语法

序号语法
1形式为 key: value;注意: 后面有空格
2区分大小写
3使用缩进表示层级关系,缩进不允许使用 tab,只允许空格
[有些地方也识别 tab , 推荐使用空格] ,缩进的空格数不重要,
只要相同层级的元素左对齐即可
4字符串无需加引号
5yml 中, 注释使用 #

1.6.2 数据类型

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

对象:键值对的集合, 比如 map、hash、set、objec

1.6.3 案例练习

利用application.yml文件自动向ioc容器中注入bean对象并进行数据绑定。

java

@Data
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "monster")
public class Monster {
    private Integer id;
    private String name;
    private Boolean isMarried;
    private Date birth;
    private Car car;
    private String[] skills;
    private List<String> hobby;
    private Map<String,String> wife;
}

application.yml

monster:
  id: 1
  name: 牛魔王
  is-married: true
  birth: 2013/11/01
  car:
    name: 避水晶晶兽
    price: 1000
  skills:
    - 牛魔拳
    - 芭蕉扇
  hobby: [唱歌,洗澡]
  wife:
    first: 铁扇公主
    second: 玉面狐狸

结果显示

Monster(id=1, name=牛魔王, isMarried=true, birth=Fri Nov 01 00:00:00 CST 2013, car=Car(name=避水晶晶兽, price=1000.0), skills=[牛魔拳, 芭蕉扇], hobby=[唱歌, 洗澡], wife={first=铁扇公主, second=玉面狐狸})

2、静态资源访问

  • 静态资源放在以下路径下/static/public /resources /META-INF/resources 可以被直接访问
    • 常见静态资源JSCSS 、图片(.jpg .png .gif .bmp .svg)、字体文件(Fonts)。
    • 访问方式 :(默认)项目根路径/ + 静态资源名。
  • 静态资源访问原理:静态映射是 /** , 也就是对所有请求拦截,请求进来,先看 Controller 能不能处理,不能处理的请求交给静态资源处理器,如果静态资源找不到则响应 404 页。

以下配置可以修改静态资源的默认存放路径默认访问路径

#修改静态资源访问的路径/前缀
spring:
 mvc:
  static-path-pattern: /jyu/**
web:
 resources:
  static-locations: [classpath:/hspimg/, classpath:/META-INF/resources/, classpath:/static/,classpath:/public/,classpath:/resources/]

视图解析配置

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true # 启用了 HiddenHttpMethodFilter,开启页面表单的 Rest 功能
    view:
      prefix: /  #这里是需要注意 prefix需要和当前的static-path-pattern一致
      suffix: .html

3、接收参数

3.1 Rest风格处理请求

Rest风格
GET获取操作
DELETE删除操作
PUT保存操作
POST修改操作

启用Rest风格配置:application.yml

spring:
  mvc:
    #启用了HiddenHttpMethodFilter,开启页面表单的Rest功能
    hiddenmethod:
      filter:
        enabled: true

@PathVariable 、 @RequestHeader 、 @ModelAttribute 、 @RequestParam 、 @MatrixVariable、

Spring MVC常用注解 EasonJim - 博客园

3.2 常用注解

注解作用
@RequestParam接受到的参数
@PathVariable映射 URL 获取绑定的占位符的变量
@RequestHeader获取 http 请求头信息
@RequestBody将接受的JSON数据,自动类型转换封装成 JavaBean
@CookieValuecookie的值绑定到方法的参数上
@RequestAttribute获取request域中的数据
@SessionAttribute获取session域中的数据

在这里插入图片描述

示例:

/**
 * @author 韩顺平
 * @version 1.0
 */
@RestController
public class ParameterController {
    /**
     * /monster/{id}/{name} 解读
     * 1. /monster/{id}/{name} 构成完整请求路径
     * 2. {id} {name} 就是占位变量
     * 3. @PathVariable("name"): 这里name 和{name} 命名保持一致
     * 4. String name_ 这里自定义,老师故意这样写下
     * 5. @PathVariable Map<String, String> map 把所有传递的值传入map
     * 6. 可以看下@PathVariable源码
     */
    @GetMapping("/monster/{id}/{name}")
    public String pathVariable(@PathVariable("id") Integer id,
                               @PathVariable("name") String name,
                               @PathVariable Map<String, String> map) {
        System.out.println("id-" + id);
        System.out.println("name-" + name);
        System.out.println("map-" + map);
        return "success";
    }

    /**
     * @RequestHeader("Host") 获取http请求头的 host信息
     * @RequestHeader Map<String, String> header: 获取到http请求的所有信息
     */
    @GetMapping("/requestHeader")
    public String requestHeader(@RequestHeader("host") String host,
                                @RequestHeader Map<String, String> header,
                                @RequestHeader("accept") String accept) {
        System.out.println("host-" + host);
        System.out.println("header-" + header);
        System.out.println("accept-" + accept);
        return "success";
    }

    /**
     * 老师解读
     * 如果我们希望将所有的请求参数的值都获取到,可以通过
     *
     * @param username
     * @param fruits
     * @param paras
     * @return
     * @RequestParam Map<String, String> paras 获取
     */
    @GetMapping("/hi")
    public String hi(@RequestParam(value = "name") String username,
                     @RequestParam("fruit") List<String> fruits,
                     @RequestParam Map<String, String> paras) {

        System.out.println("username-" + username);
        System.out.println("fruit-" + fruits);
        System.out.println("paras-" + paras);
        return "success";
    }

    /**
     * 因为我的浏览器目前没有cookie,我们可以自己设置cookie[技巧还是非常有用]
     * 如果要测试,可以先写一个方法,在浏览器创建对应的cookie
     * 说明 1. value = "cookie_key" 表示接收名字为 cookie_key的cookie
     * 2. 如果浏览器携带来对应的cookie , 那么 后面的参数是String ,则接收到的是对应对value
     * 3. 后面的参数是Cookie ,则接收到的是封装好的对应的cookie
     */
    @GetMapping("/cookie")
    public String cookie(@CookieValue(value = "cookie_key", required = false) String cookie_value,
                         HttpServletRequest request,
                         @CookieValue(value = "username", required = false) Cookie cookie) {
        System.out.println("cookie_value-" + cookie_value);
        if (cookie != null) {
            System.out.println("username-" + cookie.getName() + "-" + cookie.getValue());
        }
        System.out.println("-------------------------");
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie1 : cookies) {
            System.out.println(cookie1.getName() + "=>" + cookie1.getValue());
        }
        return "success";
    }

    /**
     * @RequestBody 是整体取出Post请求内容
     */
    @PostMapping("/save")
    public String postMethod(@RequestBody String content) {
        System.out.println("content-" + content);
        return "success";
    }


    //处理添加monster的方法

    @PostMapping("/savemonster")
    public String saveMonster(Monster monster) {
        System.out.println("monster-" + monster);
        return "success";
    }

}

4、自定义内容转换器

@RequestBody/@ResponseBody 或 HttpEntity/ResponseEntity 时, Spring 首先根据请求头或响应头的 Accept 属性选择 匹配 的 HttpMessageConverter, 进而 根据参 数类型 或泛型 类型的 过滤得 到匹配 的 HttpMessageConverter, 若找不到可用的 HttpMessageConverter 将报错

在这里插入图片描述

示例:

@Configuration(proxyBeanMethods = true)
public class BeanConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Car>() {
                    @Override
                    public Car convert(String s) {
                        if(!ObjectUtils.isEmpty(s)){
                            Car car = new Car();
                            car.setName(s.split(",")[0]);
                            car.setPrice(Double.parseDouble(s.split(",")[1]));
                            return car;
                        }
                        return null;
                    }
                });
            }
        };
    }
}

5、内容协商

根据请求头不同(不同的 Accept 类型),返回对应的json数据 或者 xml 数据

<!-- 引入支持返回 xml 数据格式 -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

修改配置application.yml

spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      parameter-name: format

6、Thymeleaf

Thymeleaf 是服务器渲染技术, 页面数据是在服务端进行渲染的,并不是一个高性能的引擎,适用于单体应用。

pom.xml

 <!-- 引入支持返回 thymeleaf 数据格式 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

6.1 表达式

表达式名字语法用法
变量取值${…}获取请求域、session 域、对象等值
选择变量*{…}获取上下文对象值
消息#{…}获取国际化等值
链接@{…}生成链接
片段表达式~{…}jsp:include 作用,引入公共页面片段

6.2 字面量

文本值: ‘hello’ ,…

数字: 10 , 7 , 36.8 , …

布尔值: true , false

空值: null

变量: name,age,… 变量不能有空格

6.3 th 属性

7、拦截器

自定义的拦截器必须实现 HandlerInterceptor 接口

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor())
        .addPathPatterns("/**") //拦截所有请求
        .excludePathPatterns("/", "/login", "/images/**"); 
    //放行的请求, 可以根据需要增加
   }
}

7.0 自定义拦截器执行流程图

在这里插入图片描述

  • 如果 preHandle 方法 返回 false, 则不再执行目标方法, 可以在此指定返回页面。
  • postHandle 在 目 标 方 法 被 执 行 后 执 行 . 可 以 在 方 法 中 访 问 到 目 标 方 法 返 回 的ModelAndView 对象。
  • 若 preHandle 返回 true, 则 afterCompletion 方法 在渲染视图之后被执行。
  • 若 preHandle 返回 false, 则 afterCompletion 方法和postHandler方法不会被调用。
  • 在配置拦截器时,可以指定该拦截器对哪些请求生效,哪些请求不生效。

7.1 多拦截器执行流程

在这里插入图片描述

7.2 拦截器与过滤器

过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,也就 是说过滤器 Filter 的使用要依赖于 Tomcat 等容器,Filter 只能在 web 程序中使用

拦截器(Interceptor) 它是一个 Spring 组件,并由 Spring 容器管理,并不依赖 Tomcat 等容器,是可以单独使用的。不仅能应用在 web 程序中,也可以用于 Application 等程序中

  1. 过滤器 Filter 是在请求进入容器后, 但在进入servlet 之前进行预处理, 请求结束是在 servlet 处理完以后
  2. 拦截器 Interceptor 是在请求进入 servlet 后, 在进入 Controller 之前进行预处理的, Controller 中渲染了对应的视图之后请求结束

8、 文件上传和下载

applicartion.yaml

spring:
  servlet:
    multipart:
      max-file-size: 1 #指定文件最大值
      max-request-size: 10 #指定每次请求最大值

前端页面:

<div style="text-align: center">
    <h1>注册用户~</h1>
    <form action="#" method="post" th:action="@{/upload}" enctype="multipart/form-data">
        用户名:<input type="text" style="width:150px" name="name"/><br/><br/>
        电 邮:<input type="text" style="width:150px" name="email"/><br/><br/>
        年 龄:<input type="text" style="width:150px" name="age"/><br/><br/>
        职 位:<input type="text" style="width:150px" name="job"/><br/><br/>
        头 像:<input type="file" style="width:150px" name="header"><br/><br/>
        宠 物:<input type="file" style="width:150px" name="photos" multiple><br/><br/>
        <input type="submit" value="注册"/>
        <input type="reset" value="重新填写"/>
    </form>
</div>

上传

 @PostMapping("/upload")
    @ResponseBody
    public String upload(@RequestParam(value = "name") String name,
                       @RequestParam(value = "email") String email,
                       @RequestParam(value = "age") int age,
                       @RequestParam(value = "job") String job,
                       @RequestParam(value = "header") MultipartFile header,
                       @RequestParam(value = "photos") MultipartFile[] photos) throws IOException {
        log.info("name:{} email:{} age:{} job:{}",name,email, age,job);
        String classpath = ResourceUtils.getURL("classpath:").getPath();
        File dir = new File(classpath + WebUtils.getUploadFileDirectory());
        if(!dir.exists()) {
            dir.mkdirs();
        }

        if(!header.isEmpty()) {
            String fileName = UUID.randomUUID().toString() + "_"+ System.currentTimeMillis() + "_" +header.getOriginalFilename();
            header.transferTo(new File( dir, fileName));
        }
        if(photos.length > 0) {
            for (MultipartFile photo : photos) {
                String fileName = UUID.randomUUID().toString() +  "_"+ System.currentTimeMillis() + "_"+ photo.getOriginalFilename();
                photo.transferTo(new File(dir, fileName));
            }
        }
        return "success";
    }

下载


    @RequestMapping("/down/{fileName}")
    public ResponseEntity<byte[]> down(HttpSession session, @PathVariable(name = "fileName") String fileName) throws Exception {
        InputStream is = session.getServletContext().getResourceAsStream("/img/" + fileName);
        byte[] bytes = new byte[is.available()];
        is.read(bytes);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Disposition","attachment;filename="+fileName);
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, httpHeaders, HttpStatus.OK);
        return responseEntity;
    }

笔记

9、异常处理

默认情况下,Spring Boot 提供/error 处理所有错误的映射,对于机器客户端,它将生成 JSON 响应,其中包含错误,HTTP 状态和异常消息的详细信 息。对于浏览器客户端,响应一个"whitelabel"错误视图,以 HTML 格式呈现相同的数据。

9.1 自定义异常页面

在这里插入图片描述

如果发生404错误优先返回404.html,如果没有则返回4xx.html,都没有则默认方式返回错误。

9.2 全局异常

@ControllerAdvice+@ExceptionHandler 处理全局异常,底层是 ExceptionHandlerExceptionResolver 支持

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})
    public String handleArithException(){
        //自定义处理异常代码
        return "error/global";
    }
}

9.3 自定异常

  1. 如果 Spring Boot 提供的异常不能满足开发需求,程序员也可以自定义异常
  2. @ResponseStatus+自定义异常
  3. 底层是 ResponseStatusExceptionResolver ,底层调用 response.sendError(statusCode, resolvedReason)

当抛出自定义异常后,仍然会根据状态码,去匹配使用 x.html

@ResponseStatus(value = HttpStatus.FORBIDDEN)
public class AccessException extends RuntimeException{
    public AccessException() {
    }
    public AccessException(String mes) {
        super(mes);
    }
}

10、 注入 Servlet、Filter、Listener

考虑到实际开发业务非常复杂和兼容,Spring-Boot 支持将 Servlet、Filter、Listener 注入 Spring 容器, 成为 Spring bean

@ServletComponentScan(basePackages = "com.jyu.usersys")

10.1 Servlet

@WebServlet(urlPatterns = {"/servlet01", "/servlet02"}), //不会被 Spring-Boot 拦截器拦截

示例:

/**
 * 解读
 * 1. 通过继承 HttpServlet 来开发原生的Servlet
 * 2. @WebServlet 标识将 Servlet_ 对象/bean注入到容器
 * 3. (urlPatterns = {"/servlet01","/servlet02"} ,对servlet配置了url-pattern
 * 4. 提示: 注入的原生的Servlet_ 不会被spring-boot的拦截器拦截
 * 5. 对于开发的原生的Servlet ,需要使用 @ServletComponentScan指定要扫描的原生Servlet包
 * , 才会注入到spring 容器.
 */
@WebServlet(urlPatterns = {"/servlet01","/servlet02"})
public class Servlet_ extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello,Servlet_!");
    }
}

10.2 Filter

@WebFilter(urlPatterns = {"/css/", "/images/"}),//过滤器配置的 urlPatterns,也会经过 Spring-Boot 拦截器

示例:

/**
 * 解读
 * 1. @WebFilter 表示Filter_是一个过滤器,并注入容器
 * 2. urlPatterns = {"/css/*", "/images/*"} 当请求  /css/目录资源或者 /images/目录下资源的时候,会经过该过滤器
 * 3. 是直接放行后,在经过拦截器, 拦截器是否拦截要根据拦截器的拦截规则
 */
@Slf4j
@WebFilter(urlPatterns = {"/css/*", "/images/*"})
public class Filter_ implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("--Filter_ init--");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        log.info("--Filter_ doFilter--");
        //为了方便观察过滤器处理的资源,我们输出一个uri
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        log.info("过滤器处理的uri={}", httpServletRequest.getRequestURI());
        //我们直接放行-实际开发中,根据自己的业务来决定如何处理
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        log.info("--Filter_ destroy--");
    }
}

10.3 Listener

@WebListener

示例:

@Slf4j
@WebListener
public class Listener_ implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {

        //这里可以加入项目初始化的相关业务代码
        log.info("Listener_ contextInitialized 项目初始化OK~");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        //这里可以加入相应代码...
        log.info("Listener_ contextDestroyed 项目销毁");
    }
}

也可以使用 RegistrationBean 方式注入

11、 内置 Tomcat 配置和切换

11.0 application.yml

常见配置以tomcat为例:

server:
#  端口
  port: 999
  tomcat:
    threads:
      # 最大工作线程数
      max: 10
      # 最小工作线程数
      min-spare: 5
    # tomcat 启动的线程数达到最大时,接受排队的请求个数,默认值为 100
    accept-count: 200
    # 最大连接数
    max-connections: 2000
    # 建立连接超时时间毫秒
    connection-timeout: 10000

SpringBoot 支持的 webServer: Tomcat, Jetty, or Undertow

11.1 pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 引入 undertow -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

12、 数据库操作

12.1 HikariDataSource

12.1.1 pom.xml

<!--进行数据库开发,引入 data-jdbc starter-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

12.1.2 application.yml

spring:
  #数据源配置
  datasource:
    #说明: 如果没有使用useSSL=true, 会报红警告
    url: jdbc:mysql://localhost:3306/spring_boot?useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: ysk
    driver-class-name: com.mysql.jdbc.Driver
    hikari:
      # 连接池名
      pool-name: JYU_Hikari_Pol
      # 最小空闲连接
      minimum-idle: 5
      # 空闲连接最大存活时间 默认 600000(10 分钟)
      idle-timeout: 600000
      # 最大连接数,默认是 10
      maximum-pool-size: 10
      # 从连接池返回的连接 自动提交
      auto-commit: true
      # 连接最长存活时间 0 表示永久存活,默认 1800000(30 分钟)
      max-lifetime: 1800000
      # 连接超时时间,默认30000(30秒)
      connection-timeout: 30000
      # 测试链接是否可用的查询语句
      connection-test-query: select 1

1 JDBC+HikariDataSource

HikariDataSource : 目前市面上非常优秀的数据源, 是 springboot2

12.2 DruidDataSource

常见问题 · alibaba/druid Wiki (github.com)

Druid: 性能优秀,Druid 提供性能卓越的连接池功能外【Java 基础】,还集成了 SQL 监 控,黑名单拦截等功能,强大的监控特性,通过 Druid 提供的监控功能,可以清楚知道连 接池和 SQL 的工作情况,所以根据项目需要,我们也要掌握 Druid 和 SpringBoot 整合

12.2.1 pom.xml

<!--引入druid数据库连接池-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.17</version>
</dependency>

12.2.2 DataSource

@Configuration
public class DruidDataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource dataSource() {
        return new DruidDataSource();
    }
}

12.3 DruidDataSource数据监控功能

12.3.1 使用内置Druid的内置监控页面

@Bean
    public ServletRegistrationBean statViewServlet() {
        StatViewServlet statViewServlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> registrationBean
                = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
        registrationBean.addInitParameter("loginUsername","jyu");
        registrationBean.addInitParameter("loginPassword","ysk123");
        return registrationBean;
    }

13.3.2 sql统计监控信息和防火墙

 @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setFilters("stat, wall");
        return druidDataSource;
    }

13.3.3 web监控

@Bean
    public FilterRegistrationBean<WebStatFilter> 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;
    }

13、Druid-spring-boot-start

pom.xml

<!-- 引入druid场景启动器 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>

application.yml

spring:
  servlet:
    multipart:
      max-request-size: 50MB
      max-file-size: 5MB
  mvc:
    hiddenmethod:
      filter:
        enabled: true
    contentnegotiation:
      favor-parameter: true
      parameter-name: format
  datasource:
    username: root
    password: ysk
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/furn_ssm?useUnicode=true&characterEncoding=utf-8&useSSL=true
    druid:
      #启动druid监控页面
      stat-view-servlet:
        enabled: true
        login-username: jyu
        login-password: 123ysk
        reset-enable: false
      #启动druid的web监控
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
      filter:
        #启动druid的sql监控
        stat:
          enabled: true
          log-slow-sql: true
          slow-sql-millis: 1000
        #启动防火墙
        wall:
          enabled: true
          config:
            drop-table-allow: false
            select-all-column-allow: false
          enabled: true
          config:
            drop-table-allow: false
            select-all-column-allow: false

14、整合Mybatis

pom.xml

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

application.yml

mybatis:
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: com.jyu.jdbc.bean
  configuration:
    map-underscore-to-camel-case: true

XML 映射语句的示例

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
    <select id="selectBlog" resultType="Blog">
        select * from Blog where id = #{id}
    </select>
</mapper>

@MapperScan() 注解

@MapperScan("com.jyu.furn.mapper")
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);

    }
}

15、整合Mybatis-Plus

pom.xml

<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3</version>
</dependency>

定义了很多CRUD,可用于单表操作,

  • Mapper接口可以extend BaseMapper
  • Service接口可以extend IService
  • Service实现类可以继承ServiceImpl<xxMapper.class,xx.class>

15.1 Result类

/**
 * @author xiaoyu
 * @version 1.0
 */
public class Result<T>{
    private String code; //状态码 200:成功, 400:失败
    private String msg; //对状态的说明
    private T data; //返回数据

    public static <T> Result<T> success(T data) {
        Result<T> res = new Result<>(data);
        res.setMsg("success");
        res.setCode("200");
        return res;
    }

    public static Result success() {
        Result result = new Result();
        result.setCode("200");
        result.setMsg("success");
        return result;
    }

    public static <T> Result<T> error(String code, String msg, T data) {
        Result<T> res = new Result<>(data);
        res.setMsg(msg);
        res.setCode(code);
        return res;
    }

    public static Result error(String code,String msg) {
        Result res = new Result();
        res.setCode(code);
        res.setMsg(msg);
        return res;
    }

    public Result() {
    }

    public Result(T data) {
        this.data = data;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

15.2 分页案例

配置类

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

案例

@GetMapping("/list")
public Result<Page<Furn>> list(@RequestParam(defaultValue = "1") Integer pageNum,
                               @RequestParam(defaultValue = "5") Integer pageSize,
                               @RequestParam(defaultValue = "") String search) {
    Page<Furn> page; 
    if (StringUtils.hasText(search)) { //判断查询值是否为空
        LambdaQueryWrapper<Furn> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(Furn::getName,search);
        page = furnService.page(new Page<>(pageNum, pageSize), queryWrapper);
    } else {
        page = furnService.page(new Page<>(pageNum, pageSize));
    }
    return Result.success(page);
}

自定义SpringBoot默认MVC配置

自定义SpringBoot默认MVC配置?好几个坑,这篇文章必须珍藏🙇‍♂️

WebMvcConfigurer

会对Spring Boot默认的MVC配置进行顶替。一旦进行顶替,Spring Boot默认提供的那些约定优于配置的功能可能就会失效,比如静态资源访问不到、返回数据不成功、参数绑定失效的问题。

声明式事务

声明式事务:该事务是建立在AOP之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完目标方法之后根据执行情况提交或回滚事务。

@Transtional(propagation=Propagation.REQUIRED, isolation=Isolation.REPLEATED_READER, timeout=2)
  1. timeout 表示超时回滚,如果一个事务执行的时间超过某个时间限制,就让该事务回滚

  2. propagation表示事务传播机制

  3. isolation表示事务隔离级别

事务传播机制

名称意义
REQUIRED如果有事务在运行,当前方法就在这个事务内运行,否则就启动一个新事务,并在自己的事务内运行。
REQUIRES_NEW当前方法必须启动事务,并在自己的事务内运行,如果有事务正在运行,就应该将其挂起。
SUPPORTS如果有事务在运行,当前方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTED当前方法不应在事务中运行,如果有事务正在运行,将它挂起
MANDATORY当前方法必须运行在事务中运行,如果没有事务正在运行,就抛出异常
NEVER当前方法不应该运行在事务中运行,如果有事务正在运行,就抛出异常
NESTED如果有事务在运行,当前方法就应该在这个事务嵌套的事务内运行,否则就启动一个新事务,并在自己的事务内运行。

事务隔离机制

多个连接开启各自事务,操作数据库中数据时,数据库系统负责隔离操作,保证各个连接获取数据的准确性。

——韩顺平循序渐进学java

名字定义纯白话巧记
脏读(dirty read)一个事务读取到另一个事务未提交的改变。读取到未提交的数据
不可重复读
(norepeatable read)
一个事务中多次进行查询,由于其他已提交的事务进行修改或删除
使其每次查询返回结果集不同。
前后多次读取,数据内容不一致
幻读
(phantom read)
一个事务中多次进行查询,由于其他已提交的事务进行插入
使其每次查询返回结果集不同。
前后多次读取,数据总量不一致

这篇文章推荐给第一次接触事务的同学,该文中主要列举了有关脏读、不可重复读、幻读的相关案例,能帮助新手更好的理解,值得一读。

【数据库】快速理解脏读、不可重复读、幻读_SunAlwaysOnline的博客-CSDN博客

MySQL版本

隔离级别脏读不可重复读幻读加锁读
读未提交(Read uncommitted)✔️✔️✔️不加锁
读已提交(Read committed)✔️✔️不加锁
可重复读(Repeatable read)不加锁
可串行化(Serializable)加锁

注意:SQL92 标准,MySQL数据库改进,解读了不可重复读的幻读问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值