一、springboot入门

优点:

  • 快速创建独立运行的spring项目以及与主流框架集成
  • 使用嵌入式的servlet容器,应用无须打成war包
  • starters自动依赖于版本控制
  • 大量的自动配置,简化开发,也可修改默认值
  • 无须配置XML,无代码生成,开箱即用
  • 准生产环境的运行时应用监控
  • 与云计算的天然集成

微服务
架构风格,一个应用应该是一组小型服务;可以通过http的方式进行互通
每一个功能元素最终都是一个可独立替换和独立升级的软件单元

可以将应用打包成可执行的jar包
<plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

父项目是真正管理springboot应用里面的所有依赖管理,可称为springboot的管理仲裁中心,但如果没有该dependency的版本号,还是需要写

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
    </parent>

安装好这个插件之后,使用maven中的package打成一个jar包
直接使用java -jar xxx.jar运行jar包即可完成部署
spring-boot-starter:spring-boot的场景启动器
spring-boot-starter-web:帮我们导入web模块正常运行所依赖的组件
springboot将所有的功能场景都抽取出来,做成一个个starters,只需要在项目里面引入这些starter相关场景的所有依赖都会导入进来,要用什么功能就导入什么场景的启动器
@SpringBootApplication 来标注一个主程序类,说明这是一个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}
)}
)

@SpringBootConfiguration:springboot的配置类
标注在某个类上,表示这是一个springboot的配置类

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

@Configuration:配置类上来标注这个注解,配置类相当于配置文件;也是容器中的一个组件@Component

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

@EnableAutoConfiguration:开启自动配置功能,以前我们需要配置的东西,springboot帮我们自动配置;EnableAutoConfiguration告诉我们springboot开启自动配置功能,这样自动配置才能生效

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})

@AutoConfigurationPackage:自动配置包

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})

@Import({Registrar.class}):spring的底层注解@Import,给容器中导入一个组件;导入的组件由Registrar.class将主配置类(@springbootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到spring容器中
@Import({EnableAutoConfigurationImportSelector.class})
选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中,会给容器导入很多自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置号这些组件
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class)
classLoader.getResources("META-INF/spring.factories")
springboot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类生效,帮我们进行自动配置工作
javaee的整体整合方案和自动配置都在spring-boot-autoconfigure-1.5.9.RELEASE.jar
@RestController就是@ResponseBody和@Controller的合体

使用spring initializer快速创建spring boot项目

默认生成的springboot项目

  • 主程序已经生成好了
  • resources文件夹中的目录结构

    • static:保存所有的静态资源:js,css,images
    • templates:保存所有的模板页面,springboot默认jar包使用嵌入式的Tomcat,默认不支持jsp页面,可以使用模板引擎(freemarker,thymeleaf)
    • application.properties:springboot应用的配置文件,可以修改默认配置

二、springboot配置

  • 配置文件
    springboot用一个全局配置文件,配置文件名是固定的

    • application.properties
    • aplication.yml
      yaml是一个标记语言,也不是一个标记语言,是以数据为中心,比json,xml更适合作配置文件

      server:
           port: 8081
      • yaml的基本语法
        k:(空格)v,是一种以空格来控制缩进来表示数据的语言,只要左对齐就是在同一级
      • yaml中的数据类型

        • 字面量(数字,字符串,布尔等类型)
          默认字符串不能加双引号或者单引号,加了双引号的字符串不会转义,也就是说如果在字符串中有n,不会转义,会换行;加了单引号的字符串会转义,如果有n不会换行,会以n的字符串形式输出
        • 对象
          在yaml中表示一个对象有两种方式

          • 换行表示

            student: 
               name: 张三
               age: 18
          • 行内表示

            student: {name: 张三,age: 18}

            注意:k: v中有一个空格

        • 数组(list等)
          数组也有两种表示方式

          • 换行表示,每个数据前面加上-
      arrs:
         —— 小明
         —— 小强
         —— 小张
      • 行内表示
    arrs: [1,2,3]

获取yaml中的数据

application.yaml

person:
  name: 小三
  age: 18
  brithday: 2018/1/1
  maps: {k1: v1,ke2: v2}
  dog:
    name: 小狗
    age: 2

创建pojo

@ConfigurationProperties(prefix = "person")//将配置文件中配置的每一个属性的值,映射到这个组件中,前缀就是指定哪个数据
@Component
public class Person {
    private String name;
    private Integer age;
    private Date brithday;
    private Map<String,Object> maps;
    private Dog dog;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBrithday() {
        return brithday;
    }

    public void setBrithday(Date brithday) {
        this.brithday = brithday;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", brithday=" + brithday +
                ", maps=" + maps +
                ", dog=" + dog +
                '}';
    }
}

导入提示的处理器

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

配置properties文件和配置其编码
idea中的properties默认使用的的是utf-8,而properties使用的是scall码,所以要转换以下编码,在settings里面的file encoding
编码.PNG

@ConfigurationProperties和@Value(spring的一个注解)的区别

功能@ConfigurationProperties@Value
配置的范围争对所有属性某个属性
是否具有松散性
能否表示SPEL表达式语言
JSR303数据校验支持不支持
复杂类型封装不支持支持

总结:在获取某个属性值的时候使用@value,在从配置文件中获取所有属性与javabean映射时使用@ConfigurationProperties

package cn.wj.springboot.pojo;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Email;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Validated//配置校验
@ConfigurationProperties(prefix = "person")//将配置文件中配置的每一个属性的值,映射到这个组件中
@Component
public class Person {
    @Email//在@Value时是无效的
    //@Value("${person.name}")
    private String name;
    //@Value("${person.age}")
    private Integer age;
   // @Value("${person.brithday}")
    private Date brithday;
    private Map<String,Object> maps;
    private Dog dog;
    private List<Integer> list;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBrithday() {
        return brithday;
    }

    public void setBrithday(Date brithday) {
        this.brithday = brithday;
    }

@PropertySource和@ImportSource注解
使用@PropertySource注解导入自定义的properties文件(只能是properties文件)

package cn.wj.springboot.pojo;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Email;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Validated
@ConfigurationProperties(prefix = "person")
@PropertySource(value="classpath:person.properties")//引入person.properties配置文件
@Component
public class Person {
    private String name;
    private Integer age;
    private Date brithday;
    private Map<String,Object> maps;
    private Dog dog;
    private List<Integer> list;

    public List<Integer> getList() {
        return list;
    }

    public void setList(List<Integer> list) {
        this.list = list;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

使用@ImporResource读取外部配置文件
1.创建外部配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean class="cn.wj.springboot.service.UserService" id="userService"></bean>
</beans>

2.在配置类上使用@ImportResource(location={"classpath;配置文件"})引入

package cn.wj.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

@ImportResource(locations = "classpath:beans.xml")
@SpringBootApplication
public class SpringBoot01HelloworldQuickApplication {

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

}

3.通过test测试

package cn.wj.springboot;

import cn.wj.springboot.pojo.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * springboot的单元测试
 * 可以在测试期间可以类似编码一样注入
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBoot01HelloworldQuickApplicationTests {

    @Autowired
    private ApplicationContext ioc;//ioc容器

    @Test
    public void testBean(){
        boolean b = ioc.containsBean("userService");
        System.out.println(b);
    }
  
}

但springboot不建议使用xml配置,建议使用注解类配置,创建一个注解类,使用@Configuration关键字,告诉springboot容器这是一个注解类,再使用@bean可以标注在方法上,也可以单独使用,相当于就是<bean>注入容器中,其中默认的id名是方法的名字,将方法的返回值注入到spring容器中

package cn.wj.springboot.config;

import cn.wj.springboot.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration//告诉springboot这是一个配置类
public class SpringBootConfig {
@Bean//创建bean注入spring容器中
    public UserService userService(){
    return new UserService();
}
}

@Configuration和@Bean都是spring的底层注解

配置文件的占位符

#使用properties注入参数
person.name=${random.uuid}张三
person.age=${random.int(100)}
person.maps.m1=num1
person.maps.m2=num2
person.brithday=2018/1/1
#如果没获取到默认是输入的参数名称,可以在:后面指定默认值
person.dog.name=${person.hello:小明}的小狗
#获取前面的属性值,需要注意的是,如果前面的值是随机数,那么获取到的将会是另一个随机数
person.dog.age=${person.age}
person.list=1,2,3

多个配置文件的切换

  • 创建多个proflies,规则是application-{proifile}.properties,默认是application.properties
  • 多prifile文档块模式
person:
  name: 小三
  age: 18
  brithday: 2018/1/1
  maps: {k1: v1,ke2: v2}
  dog:
    name: 小狗
    age: 2

spring:
  profiles:
    active: dev

---

server:
  port: 8082
spring:
  profiles: prod
---

server:
  port: 8081
spring:
  profiles: dev
  • 激活方式

    • 命令行
      profile.PNG
    • 配置文件
      在application.properties中指定spring.profiles.active=dev
    • jvm参数
      虚拟机.PNG

配置文件的加载位置顺序

优先级从高到低排序

  • file:./config(相对于该项目的根目录)
  • file:./
  • classpath:./config(在resources下)
  • classpath:./
    四者的关系是互补,都会加载,如果重复,虽然都会加载,但是最终会被优先级高的覆盖掉

可以通过spring.config.location修改默认的配置文件路径,但是不能直接在配置文件中修改,一般都是在运维的时候通过输入参数的方式在命令行中输入: --spring.config.location=地址

外部配置加载顺序

优先级从高到低,高优先级的配置覆盖低优先级的配置,所有配置形成互补配置

  • 命令行参数,多个配置用空格分开
    D:ideaspacespring-boot-01-helloworld-quicktarget>java -jar spring-boot-01-helloworld-quick-0.0.1-SNAPSHOT

.jar --server.port=8088 --server.context.path=/yanyi

  • 来自java:comp/env的NDIs属性
  • java系统属性(System.getProperties())
  • 操作系统环境变量
  • RandomValuePropertySource配置的random.*属性值
  • jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
  • jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件

有jar包外向jar包内进行寻找,优先加载带profile

  • jar包外部的application-{profile}.properties或application.yml(不带spring.profile)配置文件
  • jar包内部的application-{profile}.properties或application.yml(不带spring.profile)配置文件
  • @Configuration注解类上的@PropertySource
  • 通过SpringApplication.setDefaultProperties指定的默认属性

自动配置原理

官方文档配置文件能配置的属性参照
1.springboot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration
2.@EnableAutoConfiguration作用:利用AutoConfigurationImportSelector.class选择器给容器中导入一些组件?可以查看selectImports方法的内容

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);//利用
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes)//获取候选的配置,在该方法中

Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");

将META-INF/spring.factories中EnableAutoConfiguration的值获取并以List<String>的集合返回,最终将这些自动配置类放到容器中
以HttpEncodingAutoConfiguration为例

@Configuration//表名这是一个配置类
@EnableConfigurationProperties({HttpProperties.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({CharacterEncodingFilter.class})
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }

    @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;
    }

1.先@Configuration声明这是一个配置类
2.@EnableConfigurationProperties({HttpProperties.class})指定要与配置文件绑定的配置类,同时将配置类放入容器中
3.@ConditionalOnWebApplication(type = Type.SERVLET)判断是否是web应用,如果不是,配置类不能生效
4.@ConditionalOnProperty判断在与其绑定的配置文件中是否有spring.http.encoding.enabled配置,没有默认成立
5.与配置文件绑定的properties类,可以通过其中的set/get方法对其赋值,从而达到自动配置的目的
6.当放入容器中的组件只有一个有参构造器的时候,参数的值就会从容器中拿,曾浩该properties类也放进去了
7.通过@bean给容器中添加一个组件,这个组件的某些值需要从properties类中获取
一旦这个配置类生效,这个配置类就会给容器中添加各种组件,这些组件的属性是从对应的Properties类中获取的,这些类里面的每个属性又是和配置文件绑定的.

springboot的精髓

1.springboot启动会加载大量的自动配置类
2.我们看我们需要的功能有没有springboot默认写好的自动配置类
3.我们再看这个自动配置类中配置了哪些组件(如果没有我们需要的组件就自己配置)
4.自动配置类给容器中添加组件时,会从propeties类中获取某些属性,我们就可以在配置文件中指定这些属性的值
xxxAutoConfiguration:自动配置类,给容器中添加组件
xxxProperties:封装配置文件中相关属性

@Conditional自动配置报告

@Conditional(条件判断类.class):条件判断类中有一个match方法,返回true就成功
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置里面的所有内容才生效
自动配置类必须在一定的条件下才能生效
在配置文件中
dedug=true,告诉哪些类用了哪些类没用,打印自动配置报告,就可以很方便的知道哪些自动配置类生效

三、springboot与日志

springboot:底层是spring框架,spring框架默认是JCL;springboot选用SLF4j和logback
SLF4j日志的抽象门面,logback实现
每个日志的实现框架都有自己的配置文件,使用slf4j以后,配置文件还是做成日志实现框架的配置文件

统一日志记录

即使是别的框架,和我一起统一使用slf4j进行输出?
1.将系统中其他日志框架先排除出去
2.用中间包来替换原有的日志框架
3.再来导入slf4j的其他的实现
springboot能自动适配所有的日志,而且底层使用slf4j-logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉

日志的使用

@Test
    public void contextLoads() {
        //记录器
        Logger logger = LoggerFactory.getLogger(SpringBootLoggingApplicationTests.class);
        //日志的级别,由低到高,trace<debug<warn<error
        //可以调整输出的级别,日志就只会再这个级别及以后的高级别生效
        //springboot默认给我们使用的是info级别的,没有指定级别就使用默认级别
        //跟踪轨迹
        logger.trace("这是trace日志");
        //调试日志
        logger.debug("debug日志");
        //自己定义的信息
        logger.info("自己定义的信息日志");
        //警告
        logger.warn("warn日志");
        //错误
        logger.error("错误日志");
    }

springboot默认帮我们配置好了日志

springboot默认设置

#修改springboot的默认日志级别
#trace<debug<info<error
logging.level.cn.wj=trace

#指定日志文件的路径,如果是window就是当前磁盘的根路径下的path
logging.path=/logging
#指定日志文件名
#logging.file=d:/hhh.log
#当path和file同时存在时,使用的是file,path默认的文件名为spring.log;都没有就只在控制台输出默认格式

#指定控制台输出格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} %msg%n
logging.pattern.file=%d{yyyy-MM-dd hh:mm:ss}==>[%thread]==>%-5level==>%logger{50}==>%msg%n

这里提供一个logback模板
logback.xml

springboot指定日志配置文件和日志profile功能

将logback.xml放置在resources路径下(classpath路径下),可以被logback日志框架自动识别,如果要加强日志的profile功能,就要将logback改为logback-spring被springboot识别

<layout class="ch.qos.logback.classic.PatternLayout">
            <!--通过springProfile指定profile环境-->
            <springProfile name="dev">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}--[%thread]--%-5level==>%logger{50}--%msg%n</pattern>
            </springProfile>
            <springProfile name="prod">
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}==>[%thread]==>%-5level==>%logger{50}==>%msg%n</pattern>
            </springProfile>
        </layout>

切换日志框架

以log4j为例,先排除不必要的日志框架,将其他日志框架转换成slf4j;再转化成log4j

<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>

排除无关框架

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>logback-core</artifactId>
                    <groupId>ch.qos.logback</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>log4j-to-slf4j</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>logback-classic</artifactId>
                    <groupId>ch.qos.logback</groupId>
                </exclusion>
            </exclusions>
        </dependency>

四、springboot与web开发

springboot对静态资源的映射处理
在WebMvcAutoConfiguration.class中

//获取
static String[] getResourceLocations(String[] staticLocations) {
        String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
        System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
        System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
        return locations;
    }

1.第一种方式,加载webjars依赖
在classpath:/META-INF/resources/webjars/路径下的静态资源
https://www.webjars.org/ 网站可以查看maven依赖等
可以通过http://localhost:8080//webjars/jquery/3.4.1/jquery.js访问静态资源
所有的/webjars/**相当于classpath:/META-INF/resources/webjars/下找资源
webjars:以jar包的方式引入静态资源

public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }

2.第二种方式/**,访问当前项目的任何资源(静态资源的文件夹)
可以通过spring.resources配置静态资源的相关信息
从java和resources下都是类路径的根目录(不用加静态资源文件夹的名,因为默认就是在该文件夹下找的,意思就是说在写url的时候不需要加static等)
http://localhost:8080/asserts/js/bootstrap.min.js

@ConfigurationProperties(
    prefix = "spring.resources",
    ignoreUnknownFields = false
)
public class ResourceProperties {
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
    private String[] staticLocations;//设置静态资源加载位置
    private boolean addMappings;
    private final ResourceProperties.Chain chain;
    private final ResourceProperties.Cache cache;//设置缓存相关信息

有以下几个路径可以加载静态资源

classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
/ 项目根目录下

3.配置欢迎页映射,静态资源文件夹下的所有index.html页面;被/**映射

@Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) {
            WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
            welcomePageHandlerMapping.setInterceptors(this.getInterceptors());
            return welcomePageHandlerMapping;
        }

        private Optional<Resource> getWelcomePage() {
            String[] locations = WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations());//还是在staticLocations下面找的
            return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
        }

        private Resource getIndexHtml(String location) {
            return this.resourceLoader.getResource(location + "index.html");
        }

4.配置图标favicon.ico
还是在静态资源文件夹下**/favicon.ico

 @Configuration
        @ConditionalOnProperty(
            value = {"spring.mvc.favicon.enabled"},
            matchIfMissing = true
        )
        public static class FaviconConfiguration implements ResourceLoaderAware {
            private final ResourceProperties resourceProperties;
            private ResourceLoader resourceLoader;

            public FaviconConfiguration(ResourceProperties resourceProperties) {
                this.resourceProperties = resourceProperties;
            }

            public void setResourceLoader(ResourceLoader resourceLoader) {
                this.resourceLoader = resourceLoader;
            }

            @Bean
            public SimpleUrlHandlerMapping faviconHandlerMapping() {
                SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
                mapping.setOrder(-2147483647);
                mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", this.faviconRequestHandler()));
                return mapping;
            }

改变默认的静态资源文件夹路径,改变了就不能访问默认的了

application.proerties
#修改默认的静态资源文件夹
spring.resources.static-locations=classpath:/img,classpath:/css,classpath:/js

http://localhost:8080/bootstrap.min.js

引入thymeleaf

thymeleaf的使用和语法
springmvc自动配置原理
官方文档如何使用springmvc

spring-boot中对springmvc的auto-configuration

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

    • 自动配置了ViewResolver(视图解析器:根据方法的返回值的得到视图对象(view),视图对象决定如何渲染)
    • ContentNegotiatingViewResolver:组合所有视图解析器的
    • 如何定制:可以自己给容器中添加一个视图解析器,自动的将器组合进来
      在DispatcherServlet类中,所有请求一进来会来到doDispatch
  • Support for serving static resources, including support for WebJars (covered later in this document)).
  • Automatic registration of Converter, GenericConverter, and Formatter beans.
  • Support for HttpMessageConverters (covered later in this document).
  • Automatic registration of MessageCodesResolver (covered later in this document).
  • Static index.html support.
  • Custom Favicon support (covered later in this document).
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
    If you want to keep Spring Boot MVC features and you want to add additional MVC configuration (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

国际化配置

1.编写国际化配置文件
2.使用ResourceBundleMessageSource管理国际化资源文件

//ResourceBundleAutoConfiguration自动配置类,这个类是根据配置自动创建msssageSource
 @Bean
    @ConfigurationProperties(
        prefix = "spring.messages"
    )
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
//国际化资源的基础名baseName(去掉语言代号和国家代号)
        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;
    }

在WebMvcAutoConfiguration中,如果没有自己创建fixed LocaleResolver就返回一个自动创建的LocaleResolver

    @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            prefix = "spring.mvc",
            name = {"locale"}
        )
        public LocaleResolver localeResolver() {
            if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
                return new FixedLocaleResolver(this.mvcProperties.getLocale());
            } else {
                AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
                localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
                return localeResolver;
            }
        }

将自己创建的LocaleResolver

package cn.wj.springbootweb.component;

import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String language = request.getParameter("l");
        Locale l = Locale.getDefault();
        if (!"".equals(language) && language != null) {
            String[] s = language.split("_");
            l = new Locale(s[0], s[1]);
        }
        return l;
    }

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

    }
}

注册到容器中

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

3.如果是jsp使用fmt:message取出国际化的内容

小知识

关闭模板引擎的缓存
spring.thymeleaf.cache=false
页面修改完成后ctrl+f9将页面重新编译一下

登录错误消息的显示
<p th:text="${msg}" style="color: #f00;" th:if="${not #strings.isEmpty(msg)}"></p>
拦截器进行登录检查
必须实现HandlerInterceptor接口
crud-员工列表
实验要求:
1.restfulCRUD:CRUD满足rest风格
uri:/资源名称/资源标识 http请求方式区分对资源crud操作

crud普通CRUD(uri来区分操作)RestfulCRUD
查询getEmpemp---GET
添加addEmp?xxxemp---POST
修改updateEmp?id=xxx&emp/{id}--PUT
删除deleteEmp?id=1emp/{id}--DELETE

thymeleaf公共页抽取

1.抽取公共页面片段
2.引入公共片段
抽取公共页面片段的两种方式

1.通过th:fragment
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topBar">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginuser}]]</a>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
    <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
            <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
        </li>
    </ul>
</nav>
2.通过设置id

引入公共片段的六种方式

通过片段名引入(模板名::片段名)模板名会根据thymeleaf前后缀的配置规则解析
1.<div th:replace="~{commons/top::topBar}"></div>相当于,将包括th:fragment所在的标签全部替换div(不包括div)
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topBar">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginuser}]]</a>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
    <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
            <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
        </li>
    </ul>
</nav>
2.<div th:insert="~{commons/top::topBar}"></div>相当于,将包括th:fragment所在的标签全部插入到div中(包括div)
<div>
    <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topBar">
    <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginuser}]]</a>
    <input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
    <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
            <a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">Sign out</a>
        </li>
    </ul>
</nav>
</div>
3.<div th:include="~{commons/top::topBar}"></div>相当于,将不包括th:fragment所在的标签的其他标签插入到div中(包括div)
通过选择器引入
在要引入的资源上添加id属性
<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="side">


<div th:replace="~{commons/bar::#side}"></div>

参数化的片段签名
在引入片段的时候传入参数
<div th:replace="~{commons/bar::#side(uri='emps'}"></div>

提交的数据格式不对

日期的格式化:springmvc将页面提交的值需要转换为指定的类型
默认日期是按照/的方式
可以通过修改dateFormatter

put请求

1.springmvc中配置HiddenHttpMethodFilter(springboot自动配置好了,webmvcAutoConfiguration)

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    private static final List<String> ALLOWED_METHODS;
    public static final String DEFAULT_METHOD_PARAM = "_method";
    private String methodParam = "_method";

在webmvcAutoConfiguration中
2.页面创建一个post表单
3.创建一个input项,name="_method" value="put(不区分大小写)"

<form method="post" th:action="@{/emp}">
                <input type="hidden" th:if="${emp!=null}" name="id" th:value="${emp.id}"/>
                <input type="hidden" th:if="${emp!=null}" name="_method" value="put"/>

关于springboot默认的错误处理机制

浏览器默认返回错误页面,客户端默认返回json数据
原理:springboot默认错误处理的自动配置

@Configuration
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class, WebMvcProperties.class})
public class ErrorMvcAutoConfiguration {
    private final ServerProperties serverProperties;
    private final DispatcherServletPath dispatcherServletPath;
    private final List<ErrorViewResolver> errorViewResolvers;

在ErrorMvcAutoConfiguration中注册的几个比较重要的组件

  • DefaultErrorAttributes
    帮我们在页面共享信息
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";
    private final boolean includeException;

    public DefaultErrorAttributes() {
        this(false);
    }

public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, webRequest);
        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        this.addPath(errorAttributes, webRequest);
        return errorAttributes;
    }

    private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");
        if (status == null) {
            errorAttributes.put("status", 999);
            errorAttributes.put("error", "None");
        } else {
            errorAttributes.put("status", status);

            try {
                errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
            } catch (Exception var5) {
                errorAttributes.put("error", "Http Status " + status);
            }

        }
    }
.....
  • BasicErrorController
    处理默认/error请求
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

//产生html类型的数据
@RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
//去哪个页面作为错误页面;包含页面地址和页面内容
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);//会拿到所有异常视图的解析器,最后得到modelAndView,如果得到了就返回,得不到就为null
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }
//产生json数据
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        return new ResponseEntity(body, status);
    }
  • ErrorPageCustomizer
    一旦系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误页面的响应规则),系统出现错误以后来到/error请求进行处理
  • DefaultErrorViewResolver
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
    private static final Map<Series, String> SERIES_VIEWS;
    private ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final TemplateAvailabilityProviders templateAvailabilityProviders;
    private int order = 2147483647;
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
        }

        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默认springboot可以找到一个error/404(状态码)
        String errorViewName = "error/" + viewName;
//模板引擎可以解析这个页面地址就用模板引擎解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
//如果模板引擎可用的情况下返回到errorViewName指定的视图地址,不可用就在静态资源文件夹下找errorViewName对应的页面
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

    private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
        String[] var3 = this.resourceProperties.getStaticLocations();
        int var4 = var3.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];

            try {
                Resource resource = this.applicationContext.getResource(location);
                resource = resource.createRelative(viewName + ".html");
                if (resource.exists()) {
                    return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
                }
            } catch (Exception var8) {
            }
        }

        return null;
    }

步骤;
一旦系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误页面的响应规则),系统出现错误以后来到/error请求;然后被BasicErrorController处理,BasicErrorController会做自适应效果,响应的数据可以由getErrorAttributes得到(是AbstractErrorController规定的方法)
定制错误响应:

  • 定制错误页面
    有模板引擎的情况下error/状态码,将错误页面命名为错误状态码.html放置模板引擎文件夹中的error文件夹下;有精确的错误状态码,就来到指定的转台码页面,没有就通过4xx和5xx作为错误页面的文件夹来匹配这个类型的所有错误(精确优先);模板引擎找不到就在静态资源文件夹下找;所有都没有就是默认来到springboot默认的错误提示页面
  • 定制错误json数据
    通过异常处理器处理异常,这种方法没有自适应效果,浏览器和客户端返回的都是json数据
@ControllerAdvice
public class CommonsErrorHandler {

    @ResponseBody
    @ExceptionHandler(RuntimeException.class)
    public String runtimeExeption(Exception ex){
        Map<String,String> map = new HashMap<String,String>();
//Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");自定义状态码,否则不会进入定制错误页面的解析流程
        map.put("errorCode","101");
        map.put("errorMsg",ex.getMessage());
        return "forward:/error";
    }
}

转发到/error进行自适应响应效果处理

将我们的定制数据携带出去

  • 编写ErrorController的实现类(或者编写AbstractErrorController的子类),放置容器中
  • 页面上能用的数据或者是json返回的数据都是通过this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace)得到的;容器中是通过DefaultErrorAttributes.getErrorAttributes默认进行数据处理的;给容器中加入我们自己定义的ErrorAttributes,通过继承DefaultErrorAttributes或者实现ErrorAttributes

嵌入式servlet

springboot默认使用的式嵌入式的servlet容器(tomcat)
问题

  • 如何定制和修改servlet容器的相关配置?

    • 修改和server有关的配置(ServerProperties)
server.port=8081
server.tomcat.uri-encoding=utf-8
  • 编写一个嵌入式的servlet容器的定制器,来修改servlet容器的配置
    在springboot1.0是通过注册EmbeddedServletContainerCustomizer(嵌入式servlet容器定制器),同时调用里面的customize方法来修改默认的servlet容器的配置信息;但是在springboot2.0后就改了一下类的实现,通过WebServerFactoryCustomizer<ConfigurableWebServerFactory>中的customize方法来修改默认的配置信息,注意方法名,@Bean是以方法名作为id存在容器中的

注册sevlet、filter、listener

xxxRegistrationBean

使用其他servlet容器

默认使用tomcat容器
jetty(长连接),undertow(不支持jsp,并发)
先引入相关dependence,排除tomcat

嵌入式servlet容器自动配置原理

springboot通过嵌入式servlet容器自动配置类,根据我们导包情况,创建对应的嵌入式servlet容器工厂放在容器中;只要容器中某个组件(就是那个工厂)要创建对象就会惊动后置处理器;后置处理器判断是否是嵌入式servlet容器工厂,是的话就工作,从容器中获取所有的嵌入式servlet容器定制器,然后调用定制器的定制方法,定制方法中通过绑定serverproperties或者自己设置就能为嵌入式容器初始化

嵌入式servlet容器启动原理

什么时候创建嵌入式的servlet容器工厂?什么时候获取嵌入式的servlet容器并启动tomcat?
获取嵌入式的servlet容器工厂
1.springboot启动运行run方法
2.refreshContext(context);刷新ioc(context)容器(创建ioc容器对象并初始化容器,创建容器中的每一个组件这里是调用create方法创建context容器),如果是web应用创建web容器,否则创建默认的
3.在调用refreshContext的时候,会调用refresh(context)方法,刷新刚才创建好的ioc容器
4.webioc容器刷新的时候先调用父容器的ioc刷新方法
。。。。。
先启动嵌入式servlet容器,再将ioc容器中剩下没有创建的对象获取出来,ioc容器启动创建嵌入式servlet容器

使用外置的servlet容器

嵌入式servlet容器:
优点:简单、便捷
缺点:默认不支持jsp,优化定制比较复杂(使用定制器[serverproperties]、自定义、自己编写嵌入式servlet容器的容器工厂)
外置的servlet容器:外面安装servlet容器
步骤:
1.创建一个war项目
2.将嵌入式的tomcat指定未provided
3.必须编写一个SpringBootServletInitializer的实现类,调用configure方法

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(SpringbootWebApplication.class);
    }

原理:
jar包的方式:直接执行springboot的主类的main方法,先启动ioc容器,创建嵌入式的servlet容器(ioc容器将servlet容器带起来)
war包的方式:启动服务器,服务器启动springboot应用[SpringBootServletInitializer],启动ioc容器
servlet3.0后有规则

  • 服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面servletContainerInitializer实例
  • servletContainerInitializer的实现放置jar包的META-INF/services文件夹下,有一个明为javax.servlet.servletContainerInitializer的文件,内容就是servletContainerInitializer的实现类的全类名
  • 还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类
    例子:

1.启动tomcat
2.找找找,来每个jar包的META-INF/services找

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
//感兴趣的所有类,拿来判断
        if (webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                   initializers.add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}

3.SpringServletContainerInitializer将handletypes标注的所有类型的类都传入到onStartup第一个参数的set集合里面,为这些WebApplicationInitializer类型的类,只要不是接口不是抽象的就创建对象
4.每一个WebApplicationInitializer的实例都调用自己的onStartup方法
initializer.PNG
5.我们自己写的SpringBootServletInitializer创建对象,并执行onstartup方法
6.SpringBootServletInitializer实例在指定onStartup方法时会创建一个根容器

public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
    protected Log logger;
    private boolean registerErrorPageFilter = true;

    public SpringBootServletInitializer() {
    }

    protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) {
        this.registerErrorPageFilter = registerErrorPageFilter;
    }

    public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
//创建一个根容器
        WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
        if (rootAppContext != null) {
            servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                public void contextInitialized(ServletContextEvent event) {
                }
            });
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }

在createRootApplicationContext中

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
//先创建springboot的构建器
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        builder.main(this.getClass());
        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
        }

        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
//调用configure放啊,子类重写了这个方法,将springboot的主程序类传入了进来
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }
//通过run方法跑起来了
        return this.run(application);
    }

五、springboot与Docker

docker是一个开源的应用容器引擎,基于go语言,可以让开发这打包应用及其依赖包到一个轻量级,可一致的容器中,沙箱机制,相互直接不会有任何接口,容器性能开销极低,支持将软件编译成一个镜像

docker核心概念

  • docker主机(host):安装了docker程序的机器(直接安装在操作系统上)
  • docker客户端(client):连接docker主机操作
  • docker仓库(registry):用来保存各种打包好的软件镜像
  • docker镜像(images):软件打包好的镜像,放置docker仓库中
  • docker容器(container):镜像启动后的实例称为一个容器,容器时独立运行的一个或一组应用

步骤:
1.安装docker
2..docker仓库找到这个软件对应的镜像
3.使用docker运行这个镜像,这个镜像就会生成一个docker容器
4.对容器的启动停止就是对软件的启动和停止

linux安装docker

uname -r:检查内核版本必须是3.1以上
yum update 升级版本
yum install docker暗转docker
systemctl start docker启动docker
systemctl enable docker 设置未开机启动

docker常用操作

1.镜像操作

  • docker search 关键字 :搜寻(去docker hub上搜索)
  • docker pull 镜像名[:tag]他跟可选,标识标签,多为软件的版本,默认未latest
  • docker images 查看所有本地镜像
  • docker rmi image-id 删除指定的本地镜像

2.容器操作
软件镜像-->运行镜像-->产生一个容器

  • docker run --name 容器名 -d(后台运行) 指定镜像模板
    eg:docker run --name myredis -d redis
  • docker ps查看运行中的容器,加上-a可以查看所有容器
  • docker stop 容器名/容器id 停止
  • start ...启动
  • rm ...删除
  • -p 主机端口:容器内部端口
    eg:docker run -d -p 6379:6379 --name myredis docker.io/redis
  • 容器日志
  1. logs container-name/container-id

可以访问docker官网查看详细命令

springboot数据访问

六、springboot与数据访问

对于数据访问层,无论是sql还是nosql,springboot默认采用整合spring data的方式进行同一处理添加大量自动配置,屏蔽了很多设置,引入各种xxxTemplate,xxxRepository来接话对数据访问层的操作

  • jdbc
  • mybatis
  • jpa

都在DataSourceProperties里面配置的
自定义数据源类型

@Configuration
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"}
    )
    static class Generic {
        Generic() {
        }

        @Bean
        public DataSource dataSource(DataSourceProperties properties) {
//利用datasourceBuilder创建数据源,利用反射创建相应type的数据源,并且绑定相关属性
            return properties.initializeDataSourceBuilder().build();
        }
    }

DataSourceInitializer帮我们运行schema-.sql(建表);data-.sql(插入数据)
也可以直接指定schema属性,即schema-*.sql的位置

操作数据库

自动配置了JdbcTemplate

配置druid监控

1.配置一个管理后台的servlet
StatViewServlet
2.配置一个监控的filter
WebStatFilter

整合mybatis

1.引入mybatis-spring-boot-starter
配置文件版:
指定全局配置文件的位置
指定mapper文件的位置

springdata

springdata为了简化构建基于spring框架应用的数据访问技术,宝购非关系型数据库、Map-Reduce框架、云数据服务等
特点:为我们提供同一的api来对数据访问层进行操作

七、springboot启动配置原理

启动流程:
1.创建springapplication对象
2.运行run方法

八、springboot自定义starters

模式:
1.启动器模式:
启动器只用来做依赖导入,专门来写一个自动配置模块,启动器依赖自动配置;别人只需要引入启动器(starter)

九、springboot与缓存

十、springboot与消息

十一、springboot与检索

十二、springboot与任务

十三、springboot与安全

十四、springboot与分布式

十五、springboot与开发热部署

十六、springboot与监控管理