SpringBoot

SpringBoot

什么是SpringBoot?

SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,对比其他javaweb框架的好处,官方说是简化开发,约定大于配置, you can “just run”,能迅速的开发web应用,几行代码开发一个http接口。

所有的技术框架的发展似乎都遵循了一条主线规律:从一个复杂应用场景 衍生 一种规范框架,人们只需要进行各种配置而不需要自己去实现它,这时候强大的配置功能成了优点;发展到一定程度之后,人们根据实际生产应用情况,选取其中实用功能和设计精华,重构出一些轻量级的框架;之后为了提高开发效率,嫌弃原先的各类配置过于麻烦,于是开始提倡“约定大于配置”,进而衍生出一些一站式的解决方案。

是的这就是Java企业级应用->J2EE->spring->springboot的过程。

随着 Spring 不断的发展,涉及的领域越来越多,项目整合开发需要配合各种各样的文件,慢慢变得不那么易用简单,违背了最初的理念,甚至人称配置地狱。Spring Boot 正是在这样的一个背景下被抽象出来的开发框架,目的为了让大家更容易的使用 Spring 、更容易的集成各种常用的中间件、开源软件;

Spring Boot 基于 Spring 开发,Spirng Boot 本身并不提供 Spring 框架的核心特性以及扩展功能,只是用于快速、敏捷地开发新一代基于 Spring 框架的应用程序。也就是说,它并不是用来替代 Spring 的解决方案,而是和 Spring 框架紧密结合用于提升 Spring 开发者体验的工具。

Spring Boot 以约定大于配置的核心思想,默认帮我们进行了很多设置,多数 Spring Boot 应用只需要很少的 Spring 配置。同时它集成了大量常用的第三方库配置(例如 Redis、MongoDB、Jpa、RabbitMQ、Quartz 等等),Spring Boot 应用中这些第三方库几乎可以零配置的开箱即用。

简单来说就是SpringBoot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架 。

Spring Boot 出生名门,从一开始就站在一个比较高的起点,又经过这几年的发展,生态足够完善,Spring Boot 已经当之无愧成为 Java 领域最热门的技术。

Spring Boot的主要优点:

  • 为所有Spring开发者更快的入门
  • 开箱即用,提供各种默认配置来简化项目配置
  • 内嵌式容器简化Web项目
  • 没有冗余代码生成和XML配置的要求

HelloSpringBoot

项目创建方式:(使用 IDEA 直接创建项目)

1、创建一个新项目;

2、选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现;

3、填写项目信息;

4、选择初始化的组件(勾选 Web );

5、填写项目路径;

6、等待项目构建成功;

项目结构分析:

通过上面步骤完成了基础项目的创建。就会自动生成以下文件。

1、程序的主启动类;

2、一个 application.properties 配置文件;

3、一个 测试类;

4、一个 pom.xml;

HelloSpringBoot测试:

1、在主程序的同级目录下,新建一个controller包,一定要在同级目录下,否则识别不到!

2、在包中新建一个HelloController类;

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

3、编写完毕后,从主程序启动项目,浏览器发起请求,看页面返回;控制台输出了 Tomcat 访问的端口号!

pom.xml 分析:打开pom.xml,看看Spring Boot项目的依赖;


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.6.5version>
        <relativePath/> 
    parent>
    <groupId>com.atayingroupId>
    <artifactId>demoartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>springbootname>
    <description>Demo project for Spring Bootdescription>
    <properties>
        <java.version>1.8java.version>
    properties>
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>
    dependencies>

    <build>
        
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

如上所示,主要有四个部分:

  • 项目元数据信息:在创建包的时候Project Matadata部分,也就是Maven的基本元素,说白了这SpringBoot默认创建的格式就是一个Maven项目!
  • 父依赖:可以看到pom.xml在根目录下仍然有个parent标签,继承了spring-boot-starter-parent的依赖管理,控制版本和打包信息。
  • 具体依赖:这里包含了spring-boot-starter-web用于实现Http接口(该依赖也包含了SpringMVC),官网对它的描述为:使用SpringMVC构建Web(包括RestFul),应用程序的入门者,而且它默认所使用Tomcat作为嵌入式容器(也就是说这个部分可以进行更改);而spring-boot-starter-test则是用于单元测试的依赖包。
  • build:构建配置部分,默认使用了spring-boot-maven-plugin,配合父依赖就可以通过Maven中的打包指令将一个SpringBoot应用打包成jar来直接运行(java -jar .\jar包名称)!

题外话:

如何更改启动时显示的字符拼成的字母,SpringBoot呢?也就是 banner 图案?只需一步:到项目下的 resources 目录下新建一个banner.txt 即可。

图案可以到:https://www.bootschool.net/ascii 这个网站生成,然后拷贝到文件中即可!

SpringBoot运行原理

父依赖

其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.6.5version>
    <relativePath/> 
parent>

点进去,发现还有一个父依赖:

<parent>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-dependenciesartifactId>
  <version>2.6.5version>
parent>

这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

启动器 spring-boot-starter

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

springboot-boot-starter-xxx:就是spring-boot的场景启动器。

spring-boot-starter-web:帮我们导入了web模块正常运行所依赖的组件。

SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ;我们未来也可以自己自定义 starter。

默认的主启动类

//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }
}

SpringApplication.run分析

分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;

这个类主要做了以下四件事情:

1、推断应用的类型是普通的项目还是Web项目;

2、查找并加载所有可用初始化器,设置到initializers属性中;

3、找出所有的应用程序监听器,设置到listeners属性中;

4、推断并设置main方法的定义类,找到运行的主类。

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {    
    // ......    
	this.webApplicationType = WebApplicationType.deduceFromClasspath();   
    this.setInitializers(this.getSpringFactoriesInstances();
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));                            	this.mainApplicationClass = this.deduceMainApplicationClass();
}

SpringBoot_第1张图片

@SpringBootApplication

作用:标注在某个类上说明这个类是SpringBoot的主配置类 , SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;

进入这个注解:可以看到上面还有很多其他注解!

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

@ComponentScan

这个注解在Spring中很重要 ,它对应XML配置中的元素。

作用:自动扫描并加载符合条件的组件或者bean , 将这个bean定义加载到IOC容器中;

@SpringBootConfiguration

作用:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类;

我们继续进去这个注解查看:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed

可以看到一个Configuration的注解,继续往下查看:

@Component

这里的 @Configuration,说明这是一个配置类 ,配置类就是对应Spring的xml 配置文件;里面的 @Component 这就说明,启动类本身也是Spring中的一个组件而已,负责启动应用!

我们回到 SpringBootApplication 注解中继续看。

@EnableAutoConfiguration

@EnableAutoConfiguration :开启自动配置功能

以前我们需要自己配置的东西,而现在SpringBoot可以自动帮我们配置 ;@EnableAutoConfiguration告诉SpringBoot开启自动配置功能,这样自动配置才能生效。

点进注解接续查看:

@AutoConfigurationPackage

@AutoConfigurationPackage :自动配置包

@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

@import :Spring底层注解@import , 给容器中导入一个组件;

Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器 ;

这个分析完了,退到上一步,继续看;

@Import({…})

@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;

**AutoConfigurationImportSelector :**自动配置导入选择器,那么它会导入哪些组件的选择器呢?我们点击去这个类看源码:

1、这个类中有一个这样的方法;

// 获得候选的配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 这里的getSpringFactoriesLoaderFactoryClass()方法
    // 返回的就是我们最开始看的启动自动导入配置文件的注解类;EnableAutoConfiguration
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

2、这个方法又调用了 SpringFactoriesLoader 类的静态方法!我们进入SpringFactoriesLoader类loadFactoryNames() 方法;

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) {
        // 这里它又调用了 loadSpringFactories 方法
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }

3、我们继续点击查看 loadSpringFactories 方法;

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
   // 获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身
   Map<String, List<String>> result = cache.get(classLoader);
   if (result != null) {
      return result;
   }

   result = new HashMap<>();
   try {
      // 可以发现这里所取的资源为一个常量,点进这个常量之后可以发现,
      // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
      // 也就是说,它去获取一个资源 "META-INF/spring.factories"
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
         // 将读取到的资源遍历,封装成为一个Properties
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
            String factoryTypeName = ((String) entry.getKey()).trim();
            String[] factoryImplementationNames =
                  StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
            for (String factoryImplementationName : factoryImplementationNames) {
               result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                     .add(factoryImplementationName.trim());
            }
         }
      }

      // Replace all lists with unmodifiable lists containing unique elements
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
      cache.put(classLoader, result);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}

4、发现一个多次出现的文件:spring.factories,全局搜索它;

spring.factories

我们根据源头在项目文件中打开jar包目录(依赖目录)打开SpringBoot有关于自动装配的jar包(auto-configuration),就可以发现spring.factories这个文件,点开这个文件之后,可以看到很多自动配置的文件;这就是自动配置根源所在!

SpringBoot_第2张图片

我们在上面的自动配置类随便找一个打开看看,比如 :WebMvcAutoConfiguration:

SpringBoot_第3张图片

可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean,可以找一些自己认识的类,看着熟悉一下!

所以,自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。

那么这就产生了一个问题:既然已经把所有的自动配置都导出来了,那为什么有些自动装备并未生效?而是需要导入对应的start,也就是对应的启动类才能生效呢? 例如:点进有关于AOP的自动配置,却发现源文件报错!

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
	...
}

通过分析,可以发现该类中有一个名为@ConditionalOnProperty的注解,它会判断一些条件是否满足,当条件全部满足时,才会开启自动配置!

结论:

  1. SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值;
  2. 将这些值作为自动配置类导入容器 ,自动配置类就生效 ,帮我们进行自动配置工作;
  3. 整个JavaEE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 容器中也会存在着非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景下所需要的所有组件并自动配置!
  5. 有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作。

SpringBoot_第4张图片

yaml概述

YAML是 “YAML Ain’t a Markup Language” (YAML不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)

这种语言以数据为中心,而不是以标记语言为重点!

以前的配置文件,大多数都是使用xml来配置;比如一个简单的端口配置,我们来对比下yaml和xml

传统xml配置:

<server>    
	<port>8081<port>
server>

yaml配置:

server:  
 prot: 8080

1 yaml基础语法

说明:语法要求严格!

1、空格不能省略

2、以缩进来控制层级关系,只要是左边对齐的一列数据都是同一个层级的。

3、属性和值的大小写都是十分敏感的。

字面量:普通的值 [ 数字,布尔值,字符串 ]

字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;

k: v

注意:

  • “ ” 双引号,不会转义字符串里面的特殊字符 , 特殊字符会作为本身想表示的意思;

    比如 :name: “kuang \n shen” 输出 :kuang 换行 shen

  • ‘’ 单引号,会转义特殊字符 , 特殊字符最终会变成和普通字符一样输出

    比如 :name: ‘kuang \n shen’ 输出 :kuang \n shen

对象、Map(键值对)

person:
  name: "Ayin"
  age: 18
  happy: true
  birth: 1999/6/20
map: {k1: v1, k2: v2}

数组( List、set )

list: [code, music]

2 yaml注入配置文件

yaml文件更强大的地方在于,他可以给我们的实体类直接注入匹配值!

1、在springboot项目中的resources目录下新建一个文件 application.yml;

2、编写一个实体类 Dog;

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dog {
    private String name;
    private Integer age;
}

3、编写一个复杂一点的实体类:Person 类

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private String name;
    private Integer age;
    private boolean happy;
    private Date birth;
    private Map<String, Object> map;
    private List<String> list;
    private Dog dog;
}

4、使用yaml配置的方式进行注入,大家写的时候注意区别和优势,编写一个yaml配置!

person:
  name: "Ayin"
  age: 18
  happy: true
  birth: 1999/6/20
  map: {k1: v1, k2: v2}
  list: [code, music]
  dog:
    name: "Dim"
    age: 3

5、将yaml配置文件注入Person类中!只需要添加一行注释:

@ConfigurationProperties(prefix = "person")

注意,添加这行注释之后,IDEA上方会弹出一个错误提示(springboot配置注解处理器没有找到),按照这个错误提示,导入所对应的包,便可解决!



<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-configuration-processorartifactId>
dependency>

6、确认以上配置都OK之后,去测试类中测试一下:

@SpringBootTest
class Springboot02ApplicationTests {
    @Autowired
    private Person person;
    @Test
    void contextLoads() {
        System.out.println(person);
    }
}

可以发现,在yaml配置文件中的值成功注入到了Person类中,这里其实还可以使用properties配置文件来进行注入,但是现在官方建议使用yaml来进行配置!

1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加;

2、松散绑定:这个什么意思呢? 比如我的yml中写的last-name,这个和lastName是一样的, - 后面跟着的字母默认是大写的。这就是松散绑定。可以测试一下;

3、JSR303数据校验 , 这个就是我们可以在字段是增加一层过滤器验证 , 可以保证数据的合法性;

4、复杂类型封装,yml中可以封装对象 , 使用value就不支持;

假设日后需要用一个JavaBean来和配置文件进行一一映射,不用考虑,直接只用yaml即可!

3 JSR303数据校验

Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。我们这里来写个注解让我们的name只能支持Email格式;不过需要注意的是,新版本的SpringBoot在使用@Validated需要在依赖里导入有关于数据校验的启动器:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-validationartifactId>
dependency>
@Validated
public class Person {
    @Email
    private String name;
}

运行结果 :default message [不是一个合法的电子邮件地址]

使用数据校验,可以保证数据的正确性。

@NotNull(message="名字不能为空")
private String userName;

@Max(value=120,message="年龄最大不能查过120")
private int age;@Email(message="邮箱格式错误")
private String email;

空检查
@Null       
验证对象是否为null
@NotNull    
验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   
检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   
检查约束元素是否为NULL或者是EMPTY.    

Booelan检查
@AssertTrue     
验证 Boolean 对象是否为true  
@AssertFalse    
验证 Boolean 对象是否为false    

长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

日期检查
@Past       
验证 Date和Calendar对象是否在当前时间之前  
@Future     
验证 Date和Calendar对象是否在当前时间之后  
@Pattern    
验证 String 对象是否符合正则表达式的规则

.......等等除此以外,我们还可以自定义一些数据校验规则

4 多配置文件

我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml , 用来指定多个环境版本;

例如:

application-test.properties 代表测试环境配置

application-dev.properties 代表开发环境配置

但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件

我们需要通过一个配置来选择需要激活的环境:

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

5 yaml的多文档块

和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !

server:  
 port: 8081
#选择要激活那个环境块
spring:  
 profiles:    
  active: prod
---
server:  
 port: 8083
spring:  
 profiles: dev #配置环境的名称

---
server:  
 port: 8084
spring:  
 profiles: prod  #配置环境的名称

自动配置原理

我们以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;

//表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
@Configuration 

//启动指定类的ConfigurationProperties功能;  
//进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;  
//并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

//Spring底层@Conditional注解  
//根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;  
//这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效

@ConditionalOnWebApplication(    
    type = Type.SERVLET
)

//判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

//判断配置文件中是否存在某个配置:spring.http.encoding.enabled;  
//如果不存在,判断也是成立的  
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;

@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {    
//他已经和SpringBoot的配置文件映射了    
    private final Encoding properties;    
    //只有一个有参构造器的情况下,参数的值就会从容器中拿    
    public HttpEncodingAutoConfiguration(HttpProperties properties) {        
    	this.properties = properties.getEncoding();    
}        
//给容器中添加一个组件,这个组件的某些值需要从properties中获取    
    @Bean    
    @ConditionalOnMissingBean 
    //判断容器没有这个组件?    
    public CharacterEncodingFilter characterEncodingFilter() {        
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(
            this.properties.getCharset().name()
        );
        filter.setForceRequestEncoding(
            this.properties.shouldForce(
                org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST)
        ); 
        filter.setForceResponseEncoding(
            this.properties.shouldForce(
                org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE)
        ); 
        return filter;    
	}    
//。。。。。。。
}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {    
// .....
}

我们去配置文件里面试试前缀,看提示!

SpringBoot_第5张图片

这就是自动装配的原理!

1、SpringBoot启动会加载大量的自动配置类

2、我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

3、我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

4、给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

**xxxxAutoConfigurartion:自动配置类;**给容器中添加组件!

xxxxProperties:封装配置文件中相关属性;

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效!

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;**那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。**我们怎么知道哪些自动配置类生效?

可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug=true
  • Positive matches:(自动配置类启用的:正匹配)

  • Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

  • Unconditional classes: (没有条件的类)

掌握吸收理解原理,即可以不变应万变!

SpringBoot Web开发

1 静态资源映射规则

首先,我们搭建一个普通的SpringBoot项目,回顾一下HelloWorld程序!

写请求非常简单,那我们要引入我们前端资源,我们项目中有许多的静态资源,比如css,js等文件,这个SpringBoot怎么处理呢?

如果我们是一个web应用,我们的main下会有一个webapp,我们以前都是将所有的页面导在这里面的,对吧!但是我们现在的pom呢,打包方式是为jar的方式,那么这种方式SpringBoot能不能来给我们写页面呢?当然是可以的,但是SpringBoot对于静态资源放置的位置,是有规定的!

我们先来聊聊这个静态资源映射规则:

SpringBoot中,SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;

我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;

有一个方法:addResourceHandlers 添加资源处理

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
        return;
    }
    addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
    addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
        registration.addResourceLocations(this.resourceProperties.getStaticLocations());
        if (this.servletContext != null) {
            ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
            registration.addResourceLocations(resource);
        }
    });
}

读一下源代码:比如所有的 /webjars/** , 都需要去 classpath:/META-INF/resources/webjars/ 找对应的资源;

Webjars本质就是以jar包的方式引入我们的静态资源 , 我们以前要导入一个静态资源文件,直接导入即可。

使用SpringBoot需要使用Webjars,我们可以去搜索一下:https://www.webjars.org

要使用jQuery,我们只要要引入jQuery对应版本的pom依赖即可!

那我们项目中要是使用自己的静态资源该怎么导入呢?

我们去找staticPathPattern发现第二种映射规则 :/** , 访问当前的项目任意资源,它会去找 resourceProperties 这个类,我们可以点进去看一下分析:

// 进入方法
public String[] getStaticLocations() {
    return this.staticLocations;
}
// 找到对应的值
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { 
    "classpath:/META-INF/resources/",
     "classpath:/resources/", 
    "classpath:/static/", 
    "classpath:/public/" 
};

ResourceProperties 可以设置和我们静态资源有关的参数;这里面指向了它会去寻找资源的文件夹,即上面数组的内容。

所以得出结论,以下四个目录存放的静态资源可以被我们识别:

"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"

我们可以在resources根目录下新建对应的文件夹,都可以存放我们的静态文件;

比如我们访问 http://localhost:8080/1.js , 他就会去这些文件夹中寻找对应的静态资源文件!

2 模板引擎

前端交给我们的页面,是html页面。如果是我们以前开发,我们需要把他们转成jsp页面,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。

jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的

那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?

SpringBoot推荐你可以来使用模板引擎:

模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:

SpringBoot_第6张图片

模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。

而这些值,从哪来呢,就是我们在后台封装一些数据。然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。

只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。

其他的我就不介绍了,我主要来介绍一下SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。

我们呢,就来看一下这个模板引擎,那既然要看这个模板引擎。首先,我们来看SpringBoot里边怎么用。

对于springboot来说,什么事情不都是一个start的事情嘛,我们去在项目中引入一下。给大家三个网址:

Thymeleaf 官网:https://www.thymeleaf.org/

Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf

Spring官方文档:找到我们对应的版本:https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-thymeleafartifactId>
dependency>

题外话:在早期SpringBoot版本,默认的thymeleaf版本为2.x,而开发则是基于3.x版本的,所以需要手动导入thymeleaf的依赖!

3 Thymeleaf分析

前面呢,我们已经引入了Thymeleaf,那这个要怎么使用呢?

我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。

我们去找一下Thymeleaf的自动配置类:ThymeleafProperties

@ConfigurationProperties(
    prefix = "spring.thymeleaf"
)
public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";
    public static final String DEFAULT_SUFFIX = ".html";
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
}

我们可以在其中看到默认的前缀和后缀!

我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。

使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!

测试:在页面中展示数组信息

1、修改测试请求,增加数据传输;

@RequestMapping("/t1")
public String test1(Model model){
    //存入数据
    model.addAttribute("msg","Hello,Thymeleaf");
    //classpath:/templates/test.html
    return "test";
}

2、我们要使用thymeleaf,需要在html文件中导入命名空间的约束,方便提示,可以去官方文档的#3中看一下命名空间拿来过来:

 xmlns:th="http://www.thymeleaf.org"

3、编写下前端页面;

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Ayintitle>
head>
<body>
<h1>测试页面h1>


<div th:text="${msg}">div>
body>
html>

更多表达式可以查看官网:https://www.thymeleaf.org/!

4、Thymeleaf的遍历:

假设Controller层传来被封装的数据局为一个集合,集合名为emps,使用each遍历如下:

<tr th:each="emp:${emps}">
   <td th:text="${emp.getId()}">td>
   <td th:text="${emp.getName()}">td>
   <td th:text="${emp.getEmail()}">td>
   <td th:text="${emp.getGender()==1?'':''}">td>
   <td th:text="${emp.getDepartment().getDepartmentName()}">td>
   <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}">td>
   <td>
      <a class="btn btn-sm btn-primary" th:href="@{'/toUpdateEmp/' + ${emp.getId()}}">编辑a>
      <a class="btn btn-sm btn-danger" th:href="@{'/deleteEmp/' + ${emp.getId()}}">删除a>
   td>
tr>

4 MVC的自动配置

链接:https://mp.weixin.qq.com/s?__biz=Mzg2NTAzMTExNg==&mid=2247483819&idx=1&sn=b9009aaa2a9af9d681a131b3a49d8848&scene=19#wechat_redirect

@Controller
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/index.html").setViewName("index");
    }
}

员工管理系统

在进行一系列操作之前所需要的静态资源:

链接:https://pan.baidu.com/s/1k_Nwbeus8rsKLrDhG8qmRA
提取码:ayin

解压后的静态资源需要将html页面放在template目录下,而asserts资源则需要放在static目录下!

由于本次实例所使用的模版为Thymeleaf,所以需要在导入静态资源之后,对静态资源进行一些修改工作,让页面中的样式生效!

<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">

注意:@{/}代表着默认走入资源目录下的static文件下,而且这个可以在你更改项目工程路径后,不更改静态资源路径的情况下依然生效!具体如何操作,在此便不再过多赘述,当启动Springboot之后,发现页面正常展示时,便可开始接下来的内容!

1 更改页面左侧小图标

1、将准备好的图标文件放在/static/img/目录下:

SpringBoot_第7张图片

2、在每一个页面都将该图标导入:

<link rel="icon" th:href="@{/img/favicon.ico}">

2 页面国际化

有的时候,我们的网站会去涉及中英文甚至多语言的切换,这时候我们就需要学习国际化了!在进行操作之前,需要在IDEA中统一设置properties的编码问题!将编码格式统一改成UTF-8!

1、配置文件编写

  1. 我们在resources资源文件下新建一个i18n目录,存放国际化配置文件!

  2. 建立一个login.properties文件,还有一个login_zh_CN.properties;IDEA会自动识别我们要做国际化操作!

  3. **我们可以看到idea下面有另外一个视图!**点击Resource Bundle便可打开可视化编辑配置文件!

2、配置文件生效探究

我们去看一下SpringBoot对国际化的自动配置!这里又涉及到一个类:MessageSourceAutoConfiguration!

里面有一个方法,这里发现SpringBoot已经自动配置好了管理我们国际化资源文件的组件 ResourceBundleMessageSource;

// 获取 properties 传递过来的值进行判断
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    if (StringUtils.hasText(properties.getBasename())) {
        // 设置国际化文件的基础名(去掉语言国家代码的)
        messageSource.setBasenames(
            StringUtils.commaDelimitedListToStringArray(
                                       StringUtils.trimAllWhitespace(properties.getBasename())));
    }
    if (properties.getEncoding() != null) {
        messageSource.setDefaultEncoding(properties.getEncoding().name());
    }
    messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
    Duration cacheDuration = properties.getCacheDuration();
    if (cacheDuration != null) {
        messageSource.setCacheMillis(cacheDuration.toMillis());
    }
    messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
    messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
    return messageSource;
}

我们真实 的情况是放在了i18n目录下,所以我们要去配置这个messages的路径;

spring.messages.basename=i18n.login

3、配置页面国际化值

去页面获取国际化的值,查看Thymeleaf的文档,找到message取值操作为:#{…}。

<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>

按照上述配置启动测试,可以发现原本页面为Please sign in变更为“请登录”!

4、配置国际化解析

在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!

我们去我们webmvc自动配置文件,寻找一下!看到SpringBoot默认配置:

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
    // 容器中没有就自己配,有的话就用用户配置的
    if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
        return new FixedLocaleResolver(this.mvcProperties.getLocale());
    }
    // 接收头国际化分解
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
    return localeResolver;
}

AcceptHeaderLocaleResolver 这个类中有一个方法:

public Locale resolveLocale(HttpServletRequest request) {
    Locale defaultLocale = this.getDefaultLocale();
    // 默认的就是根据请求头带来的区域信息获取Locale进行国际化
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    } else {
        Locale requestLocale = request.getLocale();
        List<Locale> supportedLocales = this.getSupportedLocales();
        if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
            Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
            if (supportedLocale != null) {
                return supportedLocale;
            } else {
                return defaultLocale != null ? defaultLocale : requestLocale;
            }
        } else {
            return requestLocale;
        }
    }
}

那假如我们现在想点击链接让我们的国际化资源生效,就需要让我们自己的Locale生效!

我们去自己写一个自己的LocaleResolver,可以在链接上携带区域信息!

修改一下前端页面的跳转连接:


<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">Englisha>

编写一个处理的组件类:

//可以在链接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {

    //解析请求
    @Override
    public Locale resolveLocale(HttpServletRequest request) {

        String language = request.getParameter("l");
        Locale locale = Locale.getDefault(); // 如果没有获取到就使用系统默认的
        //如果请求链接不为空
        if (!StringUtils.isEmpty(language)){
            //分割请求参数
            String[] split = language.split("_");
            //国家,地区
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的MvcConofig下添加bean:

@Bean
public LocaleResolver localeResolver(){
    return new MyLocaleResolver();
}

需要注意的是,在页面中要拿取配置中的值,thymeleaf需要使用符号#,具体的页面展示如下:

<form class="form-signin" th:action="@{/login}">
   <img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
   <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign inh1>
   <p style="color: red" th:text="${msg}">p>
   <label class="sr-only">Usernamelabel>
   <input type="text"
         name="username"
         class="form-control"
         th:placeholder="#{login.username}"
         required=""
         autofocus="" th:value="${username}">
   <label class="sr-only">Passwordlabel>
   <input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">
   <div class="checkbox mb-3">
      <label >
         <input type="checkbox" value="remember-me"> [[#{login.remember}]]
      label>
   div>
   <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign inbutton>
   <p class="mt-5 mb-3 text-muted">© 2017-2018p>
   <a class="btn btn-sm" th:href="@{/index.html/(lang = 'zh_CN')}">中文a>
   <a class="btn btn-sm" th:href="@{/index.html/(lang = 'en_US')}">Englisha>
form>

3 登录功能与注销功能

1、页面编写跳转路径:

登录

<form class="form-signin" th:action="@{/login}">
	...
form>

注销

<a class="nav-link" th:href="@{/loginOut}">注销a>

2、编写对应Controller类:

@Controller
public class LoginController {
    @RequestMapping("/login")
    public String login(Model model,
                        @RequestParam("username") String username,
                        @RequestParam("password") String password,
                        HttpSession session) {
        if ("as1234".equals(password)) {
            session.setAttribute("loginUser", username);
            return "redirect:/main.html";
        } else {
            model.addAttribute("msg", "用户名或密码错误");
            model.addAttribute("username", username);
            return "index";
        }
    }
    @RequestMapping("/loginOut")
    public String loginOut(HttpSession session) {
        session.removeAttribute("loginUser");
        return "redirect:/index.html";
    }
}

4 遍历功能

1、遍历路径(这里使用了三元运算符,当前页面点击时会传一个命名为active的参数,判断参数的值,使得链接样式是否高亮):

<a th:class="${active =='list'?'nav-link active':'nav-link'}" th:href="@{/emps}">

2、编写遍历功能的Controller:

@RequestMapping("/emps")
public String employee(Model model) {
    Collection<Employee> employees = employeeDao.getAll();
    model.addAttribute("emps", employees);
    return "list";
}

5 增加功能与删除功能

1、编写一个add页面来接收所需要添加的员工的数据,add页面中的提交表单样式可以直接从bootstrap模版中查找,具体再更改所需要的值便可!

这里需要注意的是,input标签所提交的数据必须要有一个name的属性,而且这个name属性必须和自定义实体类的属性名一致!而且,由于在员工实体类中,有个department属性为一个对象!这也是需要关注的一个易错点!

<form th:action="@{/addEmp}" method="post">
   <div class="form-group">
      <label>Namelabel>
      <input type="text" name="name" class="form-control" placeholder="海绵宝宝">
   div>
   <div class="form-group">
      <label>Emaillabel>
      <input type="email" name="email" class="form-control" placeholder="[email protected]">
   div>
   <div class="form-group">
      <label>Genderlabel><br>
      <div class="form-check form-check-inline">
         <input class="form-check-input" type="radio" name="gender" value="1">
         <label class="form-check-label">label>
      div><div class="form-check form-check-inline">
      <input class="form-check-input" type="radio" name="gender" value="0">
      <label class="form-check-label">label>
   div>
   div>
   <div class="form-group">
      <label>departmentlabel>
      <select class="form-control" name="department.id">
         <option th:each="dept:${department}" th:text="${dept.getDepartmentName()}" th:value="${dept.getId()}">option>
      select>
   div>
   <div class="form-group">
      <label>Birthlabel>
      <input type="text" class="form-control" placeholder="1999-6-20" name="birth">
   div>
   <button type="submit" class="btn btn-primary">添加button>
form>

2、编写增加功能的Controller类:

@GetMapping("/toAddEmp")
public String toAddEmp(Model model) {
    Collection<Department> departments = departmentdao.getDepartments();
    model.addAttribute("department", departments);
    return "input/add";
}
@PostMapping("/addEmp")
public String addEmployee(Employee employee) {
    System.out.println(employee);
    employeeDao.save(employee);
    return "redirect:/emps";
}

3、删除员工的跳转路径:

<a class="btn btn-sm btn-danger" th:href="@{'/deleteEmp/' + ${emp.getId()}}">删除a>

4、删除员工功能的Controller类:

@RequestMapping("/deleteEmp/{id}")
public String deleteEmp(@PathVariable("id") Integer id) {
    System.out.println("success");
    employeeDao.delete(id);
    return "redirect:/emps";
}

6 更新员工数据

1、编写一个更新页面,用来接受前端发来的,需要更新的员工数据,但在此之前,需要编写一个跳转Controller(这个跳转会将需要更新的员工ID接收,并使用dao层的方法找出指定员工,封装至model传递给更新页面):

@GetMapping("/toUpdateEmp/{id}")
public String toUpdateEmp(@PathVariable("id") Integer id, Model model) {
    Employee employee = employeeDao.getEmployeeByID(id);
    Collection<Department> departments = departmentdao.getDepartments();
    model.addAttribute("emp", employee);
    model.addAttribute("department", departments);
    return "input/update";
}

请求的路径(使用RESTful风格进行传参!):

<a class="btn btn-sm btn-primary" th:href="@{'/toUpdateEmp/' + ${emp.getId()}}">编辑a>

2、编写更新页面接受参数(具体操作和增加功能的实现差不多,但是为了优化用户体验,需要返回更新员工的数据,并且需要注意的是,这里需要一个隐藏域来存放员工的ID):

<input type="hidden" name="id" th:value="${emp.getId()}">

老生常谈的问题了(笑~)。

3、编写更新员工的Controller(form表单需要填入跳转的路径):

@PostMapping("/updateEmp")
public String updateEmp(Employee employee) {
    System.out.println(employee);
    employeeDao.save(employee);
    return "redirect:/emps";
}

整合JDBC

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理。

Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring Boot、Spring Cloud 等齐名的知名项目。

Sping Data 官网:https://spring.io/projects/spring-data

数据库相关的启动器 :可以参考官方文档:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

1 测试数据源

1、新建一个项目测试:springboot-data-jdbc ; 引入相应的模块!基础模块!

SpringBoot_第8张图片

2、项目建好之后,发现自动帮我们导入了如下的启动器:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <scope>runtimescope>
dependency>

3、编写yaml配置文件连接数据库;


spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

4、配置完这一些东西后,我们就可以直接去使用了,因为SpringBoot已经默认帮我们进行了自动配置;去测试类测试一下:

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入数据源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下默认数据源
        System.out.println(dataSource.getClass());
        //获得连接
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);
        //关闭连接
        connection.close();
    }
}

结果:我们可以看到他默认给我们配置的数据源为 : class com.zaxxer.hikari.HikariDataSource , 我们并没有手动配置

我们来全局搜索一下,找到数据源的所有自动配置都在 :DataSourceAutoConfiguration文件:

@Import(
    {Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {
    protected PooledDataSourceConfiguration() {
    }
}

这里导入的类都在 DataSourceConfiguration 配置类下,可以看出 Spring Boot 2.2.5 默认使用HikariDataSource 数据源,而以前版本,如 Spring Boot 1.5 默认使用 org.apache.tomcat.jdbc.pool.DataSource 作为数据源;

HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;

可以使用 spring.datasource.type 指定自定义的数据源类型,值为 要使用的连接池实现的完全限定名。

2 JDBCTemplate

1、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),有了连接,就可以使用原生的 JDBC 语句来操作数据库;

2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。

3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。

4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用

5、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类

JdbcTemplate主要提供以下几类方法:

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
  • query方法及queryForXXX方法:用于执行查询相关语句;
  • call方法:用于执行存储过程、函数相关语句。

测试:

编写一个Controller,注入 jdbcTemplate,编写测试方法进行访问测试;

package com.kuang.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/jdbc")
public class JdbcController {

    /**
     * Spring Boot 默认提供了数据源,默认提供了 org.springframework.jdbc.core.JdbcTemplate
     * JdbcTemplate 中会自己注入数据源,用于简化 JDBC操作
     * 还能避免一些常见的错误,使用起来也不用再自己来关闭数据库连接
     */
    @Autowired
    JdbcTemplate jdbcTemplate;

    //查询employee表中所有数据
    //List 中的1个 Map 对应数据库的 1行数据
    //Map 中的 key 对应数据库的字段名,value 对应数据库的字段值
    @GetMapping("/list")
    public List<Map<String, Object>> userList(){
        String sql = "select * from employee";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
    
    //新增一个用户
    @GetMapping("/add")
    public String addUser(){
        //插入语句,注意时间问题
        String sql = "insert into employee(last_name, email,gender,department,birth)" +
                " values ('狂神说','[email protected]',1,101,'"+ new Date().toLocaleString() +"')";
        jdbcTemplate.update(sql);
        //查询
        return "addOk";
    }

    //修改用户信息
    @GetMapping("/update/{id}")
    public String updateUser(@PathVariable("id") int id){
        //插入语句
        String sql = "update employee set last_name=?,email=? where id="+id;
        //数据
        Object[] objects = new Object[2];
        objects[0] = "秦疆";
        objects[1] = "[email protected]";
        jdbcTemplate.update(sql,objects);
        //查询
        return "updateOk";
    }

    //删除用户
    @GetMapping("/delete/{id}")
    public String delUser(@PathVariable("id") int id){
        //插入语句
        String sql = "delete from employee where id=?";
        jdbcTemplate.update(sql,id);
        //查询
        return "deleteOk";
    }
    
}

整合Mybatis

官方文档:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

Maven仓库地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.1

1、导入 MyBatis 所需要的依赖:


<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.2.2version>
dependency>

2、配置数据库连接信息(不变):

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

3、测试数据库是否连接成功!

4、创建实体类,导入 Lombok!

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String password;
}

5、创建mapper目录以及对应的 Mapper 接口;

// 这个注解表示了这是一个mybatis的Mapper类
@Mapper
@Repository
public interface UserMapper {
    List<User> queryUser();
    void addUser(User user);
}

6、对应的Mapper映射文件(该文件应该放在项目的resource目录下!);


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atayin.dao.UserMapper">
    <select id="queryUser" resultType="User">
        select * from mybatis.user
    select>
mapper>

7、maven配置资源过滤问题

<resources>
    <resource>
        <directory>src/main/javadirectory>
        <includes>
            <include>**/*.xmlinclude>
        includes>
        <filtering>truefiltering>
    resource>
resources>

8、编写Controller进行测试!

@Controller
public class UserController {
    @Autowired
    UserMapper userMapper;
    @RequestMapping("/list")
    @ResponseBody
    public List<User> test1() {
        List<User> list = userMapper.queryUser();
        return list;
    }
}

异步任务

异步处理还是非常常用的,比如我们在网站上发送邮件,后台会去发送邮件,此时前台会造成响应不动,直到邮件发送完毕,响应才会成功,所以我们一般会采用多线程的方式去处理这些任务。

1、创建一个service包,并在该包下创建一个类AsyncService;

2、在类AsyncService编写方法,假装正在处理数据,使用线程设置一些延时,模拟同步等待的情况;

@Service
public class AsyncService {
    public void Async() {
        try {
            Thread.sleep(3_000
            );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("数据正在处理......");
    }

3、创建controller包并在该包编写AsyncController类:

@Controller
public class AsyncController {
    @RequestMapping("/hello")
    @ResponseBody
    public String hello() {
        asyncService.Async();
        return "数据处理完成!";
    }
}

4、运行项目进行测试,可以发现请求/hello页面时,页面转圈圈转了三秒钟才响应,而且后台也是三秒之后才返回"数据处理完成!"的信息,去掉模拟延时的代码后,页面会瞬间响应!

根据以上测试可以得到一个问题:

我们如果想让用户直接得到消息,就在后台使用多线程的方式进行处理即可,但是每次都需要自己手动去编写多线程的实现的话,太麻烦了,我们只需要用一个简单的办法,在我们的方法上加一个简单的注解即可,如下:

6、给hello方法添加@Async注解;

//告诉Spring这是一个异步方法
@Async
public void Async() {
    try {
        Thread.sleep(3_000
                    );
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("数据正在处理......");
}

SpringBoot就会自己开一个线程池,进行调用!但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;

@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTaskApplication {
   public static void main(String[] args) {
       SpringApplication.run(SpringbootTaskApplication.class, args);
  }
}

7、重启测试,请求网页后瞬间响应,但后台代码依旧需要等待三秒之后执行!

同步任务

项目开发中经常需要执行一些定时任务,比如需要在每天凌晨的时候,分析一次前一天的日志信息,Spring为我们提供了异步执行任务调度的方式,提供了两个接口。

  • TaskExecutor接口
  • TaskScheduler接口

两个注解:

  • @EnableScheduling
  • @Scheduled

cron表达式:

1、结构

corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份

2、各字段的含义

SpringBoot_第9张图片

测试步骤:

1、创建一个ScheduledService

我们里面存在一个hello方法,他需要定时执行,怎么处理呢?

@Service
public class ScheduleService {
    //秒   分   时   日   月   周几
    @Scheduled(cron = "0/2 * * * * *")	// 以下方法每两秒执行一次
    public void schedule() {
        System.out.println("该段代码被执行");
    }
}

2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能

@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {

   public static void main(String[] args) {
       SpringApplication.run(SpringbootTaskApplication.class, args);
  }

}

3、测试:运行项目之后,可以发现后台控制端每两秒输出一次信息~

邮件任务

邮件发送,在我们的日常开发中,也非常的多,Springboot也帮我们做了支持

  • 邮件发送需要引入spring-boot-start-mail
  • SpringBoot 自动配置MailSenderAutoConfiguration
  • 定义MailProperties内容,配置在application.yml中
  • 自动装配JavaMailSender
  • 测试邮件发送

测试:

1、引入pom依赖

<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-mailartifactId>
dependency>

看它引入的依赖,可以看到 jakarta.mail

<dependency>
   <groupId>com.sun.mailgroupId>
   <artifactId>jakarta.mailartifactId>
   <version>1.6.4version>
   <scope>compilescope>
dependency>

2、查看自动配置类:MailSenderAutoConfiguration,可以看到类中导入了一个名为MailSenderJndiConfiguration的类

image-20220408173137280

点进该类查看,可以发现在该类中注入了一个名为JavaMailSenderImpl的Bean,这个Bean可以协助我们来实现绝大部分关于邮件任务的功能。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Session.class)
@ConditionalOnProperty(prefix = "spring.mail", name = "jndi-name")
@ConditionalOnJndi
class MailSenderJndiConfiguration {

    private final MailProperties properties;

    MailSenderJndiConfiguration(MailProperties properties) {
        this.properties = properties;
    }

    @Bean
    JavaMailSenderImpl mailSender(Session session) {
        JavaMailSenderImpl sender = new JavaMailSenderImpl();
        sender.setDefaultEncoding(this.properties.getDefaultEncoding().name());
        sender.setSession(session);
        return sender;
    }

继续点进配置类MailProperties查看配置:

@ConfigurationProperties(
   prefix = "spring.mail"
)
public class MailProperties {
   private static final Charset DEFAULT_CHARSET;
   private String host;
   private Integer port;
   private String username;
   private String password;
   private String protocol = "smtp";
   private Charset defaultEncoding;
   private Map<String, String> properties;
   private String jndiName;
}

配置类中的前缀为spring.mail,各类能够配置的属性也展示了出来,在了解以上内容之后,可以去我们的项目中的配置类进行配置了!

3、配置文件:

[email protected]
# 获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务
spring.mail.password=xxxxxxxx
# 发送的服务器(QQ邮箱)
spring.mail.host=smtp.qq.com
# QQ邮箱需要开启加密验证
spring.mail.properties.mail.smtp.ssl.enable=false

4、Spring单元测试

@Autowired
JavaMailSenderImpl mailSender;

@Test
public void contextLoads() {
   //邮件设置1:一个简单的邮件
   SimpleMailMessage message = new SimpleMailMessage();
   message.setSubject("通知");
   message.setText("今晚7:30开会");

   message.setTo("[email protected]");
   message.setFrom("[email protected]");
   mailSender.send(message);
}

@Test
public void contextLoads2() throws MessagingException {
   //邮件设置2:一个复杂的邮件
   MimeMessage mimeMessage = mailSender.createMimeMessage();
   MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

   helper.setSubject("通知");
   helper.setText("今天 7:30来开会",true);

   //发送附件
   helper.addAttachment("1.jpg",new File(""));
   helper.addAttachment("2.jpg",new File(""));

   helper.setTo("[email protected]");
   helper.setFrom("[email protected]");

   mailSender.send(mimeMessage);
}

5、查看邮箱,邮件接收成功!测试成功之后,也就代表着,我们只需要使用Thymeleaf进行前后端结合即可开发自己网站邮件收发功能了!

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Session.class)
@ConditionalOnProperty(prefix = "spring.mail", name = "jndi-name")
@ConditionalOnJndi
class MailSenderJndiConfiguration {

    private final MailProperties properties;

    MailSenderJndiConfiguration(MailProperties properties) {
        this.properties = properties;
    }

    @Bean
    JavaMailSenderImpl mailSender(Session session) {
        JavaMailSenderImpl sender = new JavaMailSenderImpl();
        sender.setDefaultEncoding(this.properties.getDefaultEncoding().name());
        sender.setSession(session);
        return sender;
    }

继续点进配置类MailProperties查看配置:

@ConfigurationProperties(
   prefix = "spring.mail"
)
public class MailProperties {
   private static final Charset DEFAULT_CHARSET;
   private String host;
   private Integer port;
   private String username;
   private String password;
   private String protocol = "smtp";
   private Charset defaultEncoding;
   private Map<String, String> properties;
   private String jndiName;
}

配置类中的前缀为spring.mail,各类能够配置的属性也展示了出来,在了解以上内容之后,可以去我们的项目中的配置类进行配置了!

3、配置文件:

[email protected]
# 获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务
spring.mail.password=xxxxxxxx
# 发送的服务器(QQ邮箱)
spring.mail.host=smtp.qq.com
# QQ邮箱需要开启加密验证
spring.mail.properties.mail.smtp.ssl.enable=false

4、Spring单元测试

@Autowired
JavaMailSenderImpl mailSender;

@Test
public void contextLoads() {
   //邮件设置1:一个简单的邮件
   SimpleMailMessage message = new SimpleMailMessage();
   message.setSubject("通知");
   message.setText("今晚7:30开会");

   message.setTo("[email protected]");
   message.setFrom("[email protected]");
   mailSender.send(message);
}

@Test
public void contextLoads2() throws MessagingException {
   //邮件设置2:一个复杂的邮件
   MimeMessage mimeMessage = mailSender.createMimeMessage();
   MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

   helper.setSubject("通知");
   helper.setText("今天 7:30来开会",true);

   //发送附件
   helper.addAttachment("1.jpg",new File(""));
   helper.addAttachment("2.jpg",new File(""));

   helper.setTo("[email protected]");
   helper.setFrom("[email protected]");

   mailSender.send(mimeMessage);
}

5、查看邮箱,邮件接收成功!测试成功之后,也就代表着,我们只需要使用Thymeleaf进行前后端结合即可开发自己网站邮件收发功能了!

你可能感兴趣的:(笔记,springboot)