Spring Boot简介及快速搭建

简介

  • SpringBoot它是基于Spring4.0设计,是由Pivotal公司提供的框架。
  • Spring发展史,2004开源。
  • 2008收购Tomcat Apatch Servlet,掌握整个生态。
  • 2009 Spring VMware 4.6美元收购。
  • 先后收购RabbitMQ、Redis。
  • 2014 Spring Boot1基于Spring4.0 2018 Spring Boot2基于Spring5.0
  • 2015 Spring Cloud
  • 2018 上市

SpringBoot基于Spring开发,设计的目的就是简化Spring应用的初始化搭建以及开发过程
SpringBoot约定大于配置,所有你想要继承的常用框架,他都有对应的组件支持,三方库几乎可零配置的开箱即用。
优点

  • 快速的构建一个独立的Spring应用程序
  • 嵌入的Tomcat、jetty或者Undertow,无需部署WAR文件
  • 提供starter POMs来简化Maven配置和减少版本冲突所带来的问题
  • 对于Spring和三方库提供默认配置
  • 提供生产就绪功能,如指标、健康检查和外部配置
  • 无需XMML,无代码生成,开箱即用

为什么使用SpringBoot?
一方面,SpringBoot简化了Spring的开发;另一方面更得力于各位服务组件的支持,这也是说SpringBoot就要谈微服务的原因。可以说是SpringCloud带动了SpringBoot,SpringBoot成就了SpringCloud。
微服务
2014Martin Fowler发表的关于微服务博客,之后被人熟知。
与微服务相对于的就是单体应用,单体应用有它的优势,它的测试与部署比较简单,不涉及多个服务的联调,只需要把一个包上传到服务器上就可以了。并且也方便水平扩展,只需要多复制几份放到不同的服务器上就可以。
缺点就是,牵一发都全身,要改一个小功能就要重新部署整个项目。更大的还是用户的日益增长和用户需求。

快速开始

https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started.html#getting-started maven搭建 https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.maven springboot包 https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters

有手就行
Spring底层在解析配置类回去解析@ComponentScan,读取basePackage,如果没有读取,则将当前配置类所在的包当做扫描包。

Spring Boot的配置和自动配置原理

https://www.yuque.com/docs/share/d955cbdb-ae23-4100-84a5-130b7ba39049?# 《SpringBoot2自动装配》

Spring Initializer创建Spring Boot

手点点,idea已经集成了spring官网提供的自动创建。

自定义SpringApplication

  1. #懒加载
  2. spring.main.lazy-initialization=true
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(SpringInitializrApplication.class);
        //关闭横幅
        springApplication.setBannerMode(Banner.Mode.OFF);
        springApplication.run(args);
    }

自定义横幅装逼用banner.jpg
图片.png

配置文件的使用

SpringBoot使用一个全局的核心配置文件,配置文件的名字在约定的情况下是固定的

  • application.properties:格式采用扁平的k/v格式
  • application.yml:格式采用树形结构
  • application.yaml

配置文件作用:修改SpringBoot的自动配置默认值。

YAML语法

  • k:(空格)v:表示以应对键值对;
  • 以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一层级的
  • 属性与值大小写敏感
    server:
    port: 8080
    servlet:
      context-path: /tong
    

    配置文件加载顺序

    在版本仲裁者下可以看到加载的顺序
    spring-boot-starter-parent—->spring-boot-dependencies
    图片.png

    外部约定文件加载顺序

    SpringBoot启动会扫描一下位置的application.yml,从低到高:
  1. classpath根目录下
    图片.png
  2. 在config文件夹下
    图片.png
  3. 在根目录下
  4. 项目根目录config
  5. 直接子目录/config
    java -jar configuration_file-0.0.1-SNAPSHOT.jar --spring.config.location=D:/application.yml
    

    Profile

    我们需要在良种环境下进行切换,只需要在application.yml加如下配置:
    spring:
    profiles:
     active: dev
    
    图片.png
    或者在启动的时候命令行形式
    java -jar configuration_file-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
    
    在加载到bean容器可以配置:
    @Profile("dev")
    
    所有配置文件按一下顺序考虑:优先级从低到高
  • jar包之外的配置文件
  • 类路径的config
  • 类路径
  • —spirng.config.location:指定了位置不会与其他配置文件进行互补

    配置文件读取方式

    1、@PropertySource

    会和约定的配置文件形成互补 只能是properties的配置文件

@SpringBootApplication
@PropertySource("classpath:appSource.properties")
public class ConfigurationFileApplication {
    public static void main(String[] args) throws IOException {
        SpringApplication.run(ConfigurationFileApplication.class, args);
    }
}

2、默认属性(springApplication.setDefaultProperties)

会与约定的配置文件形成互补

@SpringBootApplication
public class ConfigurationFileApplication {
    public static void main(String[] args) throws IOException {
        SpringApplication springApplication = new SpringApplication(ConfigurationFileApplication.class);
        //创建Properties
        Properties properties = new Properties();
        //通过当前类的ClassLoader
        InputStream inputStream = ConfigurationFileApplication.class.getClassLoader()
                .getResourceAsStream("app.properties");
        //将输入流都城properties
        properties.load(inputStream);
        springApplication.setDefaultProperties(properties);
        springApplication.run(args);
    }
}

3、配置数据(application.properties)

约定的配置文件

4、操作系统环境变量

不会与约定的配置文件互补

idea设置图片.pngwindows设置

set spring.config.location=D:\config/

5、Java系统属性(System.getProperties())

会使约定的配置文件失效

idea图片.png命令行设置

java -Dspring.config.location=D:/config\ -jar configuration_file-0.0.1-SNAPSHOT.jar

代码

        System.setProperty("spring.config.location", "D:/config\\");

6、命令行参数

会使命令行参数失效

java -jar configuration_file-0.0.1-SNAPSHOT.jar --spring.config.location=D:/application.yml

7、单元测试使用

@TestPropertySource("classpath:appSource.properties")

配置文件注入

https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.yaml

user:
  username: 同哥
  age: 18
  birthday: 2020/01/01
  hobbies: [ 唱歌,跳舞 ]
#    - 唱歌
#    - 跳舞
  girl-friends: { 18: 小魔女, 20: 云韵 }
#    18: 小魔女
#    20: 云韵
  address:
    id: 1
    desc: 大别墅
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
@ConfigurationProperties(prefix = "user")
public class User {
    @Value("${username}")
    private String username;

    private Integer age;

    private Date birthday;

    private List<String> hobbies;

    private Map<Integer, String> girlFriends;

    private Address address;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    private Integer id;
    private String desc;
}
@ConfigurationProperties @Value
功能 批量注入配置文件中的属性 一个个指定
松散绑定 支持 支持有限
SpEL 不支持 支持
自动提示 支持 不支持
JSR303数据校验 支持 支持

配置@ConfigurationProperties之后配置yml自动提示

        <!--会生成META-INF元数据,用于提供idea自动提示配置文件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <!--依赖不会传播-->
            <optional>true</optional>
        </dependency>

图片.png随机属性与属性引用

my:
  secret: "${random.value}"
  number: "${random.int}"
  bignumber: "${random.long}"
  uuid: "${random.uuid}"
  number-less-than-ten: "${random.int(10)}"
  number-in-range: "${random.int[1024,65536]}"

数据校验

支持JSR-3030 @Validated

Spring Boot 2 - 图10
Spring Boot 2 - 图11

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

指定自定义的配置文件

//只支持properties
@PropertySource("classpath:data/user.properties")

Spring Boot的配置和自动配置原理

配置清单 https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.core

SpringBootApplication

@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}
)}
)
public @interface SpringBootApplication {
  • @Target:设置当前注解课标记在哪
  • @Retention:当前注解标注的类编译以什么方式保留
    • SOURCE:编译后不会保留,不会加载源文件
    • CLASS:不会加载到虚拟机中,通过反射是找不到
    • RUNTIME:编译会保留,也会加载到源文件
  • @Documented:java doc会生成注解信息
  • @Inherited:是否会被继承
  • @SpringBootConfiguration:设置为配置类
  • @EnableAutoConfiguration:开启自动配置功能
    • @Import({AutoConfigurationImportSelector.class})
  • @ComponentScan:扫描包

    • TypeExcludeFilter:SpringBoot提供的扩展类,可以按照我们的方式进行排除
    • AutoConfigurationExcludeFilter:排除所有配置类并且是自动配置类中里面的其中一个
      @Configuration(
      proxyBeanMethods = false
      )
      @EnableConfigurationProperties({ServerProperties.class})
      @ConditionalOnWebApplication(
      type = Type.SERVLET
      )
      @ConditionalOnClass({CharacterEncodingFilter.class})
      @ConditionalOnProperty(
      prefix = "server.servlet.encoding",
      value = {"enabled"},
      matchIfMissing = true
      )
      public class HttpEncodingAutoConfiguration {
      
  • @Configuration(proxyBeanMethods = false):底层会创建cglib动态代理,防止每次调用Bean重新创建对象

  • @EnableConfigurationProperties({ServerProperties.class}):启用在配置类中设置的类

    Spring Boot热部署与日志

    热部署

    1、pom
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-devtools</artifactId>
              <optional>true</optional>
          </dependency>
    
    2、idea设置图片.png3、ctrl+alt+shift+/

    图片.png日志

    | 日志实现 | 日志门面 | | —- | —- | | log4j | JCL | | jul lang.util.logging | SJF4J | | log4j2 | | | logback | |

开发标准:记录日志不能直接使用日志实现框架,必须通过日志门面来实现。

logback日志的集成

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

  • SpringBoot底层使用slf4j+logback的方式进行日志记录
    • logback桥接:logback-classic
  • SpringBoot也把其他的日志替换成了slf4j
    • log4j适配:log4j-over-slf4j
    • jul适配:jul-tos-slf4j
    • 这两个适配器都是为了适配Spring默认的日志:jcl

日志级别
可设置TRACE,DEBUG,INFO,WRAN,ERROR,FATAL,OFF之一。
详细介绍

logging:
  pattern:
    console: '%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}'
  • 2022-02-21 07:45:1.234
    • 日期和时间:毫秒精度,易于排序
    • %clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint}
      • %clr 当前内容颜色 {faint}
    • %d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}
      • ${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}
        • ${value:value2}SpringBoot的占位符+null条件的表达式(如果value为null使用value2)
        • LOG_DATEFORMAT_PATTERN:系统环境变量中的值,底层将对应的项设置到环境变量中
      • %d{-yyyy-MM-dd HH:mm:ss.SSS}
        • %d:logback的日期显示方式
        • {-yyyy-MM-dd HH:mm:ss.SSS}:日期格式
  • 日志级别可设置
    • TRACE,DEBUG,INFO,WRAN,ERROR
    • -%5p:当前内容所占的长度
  • 进程ID
  • 一个分离器来区分实际日志消息的开始
  • 线程名称:用括号括起来(对于控制台输出可能会被截断)
  • 记录器名称:这通常是源类名称(通常缩写)
  • 日志消息

文件输出
默认情况下,SpringBoot仅记录到控制台,不写日志文件。日过写日志文件,设置logging.file.name或logging.file.path。

  • logging.file.name
    • 可以设置文件的名称
    • 指定路径+文件名
  • logging.file.path
    • 不可指定名称,必须指定一个物理文件路径

日志迭代
如果使用的是Logback,可以用使用application.yml文件微调日志轮播设置。对于其他记录系统,需要直接自己配置轮转设置(例如,Log4J2,则可以添加log4j.xml文件)

名称 描述
logging.logback.rollingpolicy.file-name-pattern 归档的文件名
logging.logback.rollingpolicy.clean-history-on-start 应用程序启动时进行日志归档清理
logging.logback.rollingpolicy.max-file-size 归档前日志文件的大小
logging.logback.rollingpolicy.total-size-cap 删除日志归档之前可以使用的最大大小
logging.logback.rollingpolicy.max-history 保留日志归档的天数(默认七天)

自定义日志

<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,比如: 如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">
    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="/var/log/myapp"/>
    <!--0. 日志格式和颜色渲染 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
    <conversionRule conversionWord="wex"
                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
    <conversionRule conversionWord="wEx"
                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    <!--1. 输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <!--2. 输出到文档-->
    <!-- 2.1 level为 DEBUG 日志,时间滚动输出  -->
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${log.path}/debug.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志归档 -->
            <fileNamePattern>${log.path}/debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录debug级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 2.2 level为 INFO 日志,时间滚动输出  -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${log.path}/info.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 每天日志归档路径以及格式 -->
            <fileNamePattern>${log.path}/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 2.3 level为 WARN 日志,时间滚动输出  -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${log.path}/warn.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 2.4 level为 ERROR 日志,时间滚动输出  -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${log.path}/error.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档只记录ERROR级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 2.5 所有 除了DEBUG级别的其它高于DEBUG的 日志,记录到一个文件  -->
    <appender name="ALL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文档的路径及文档名 -->
        <file>${log.path}/all.log</file>
        <!--日志文档输出格式-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/all-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--日志文档保留天数-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- 此日志文档记录除了DEBUG级别的其它高于DEBUG的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
    </appender>
    <!-- 4  最终的策略:
                 基本策略(root级) + 根据profile在启动时, logger标签中定制化package日志级别(优先级高于上面的root级)-->
    <springProfile name="dev">
        <root level="info">
            <appender-ref ref="CONSOLE"/>
<!--            <appender-ref ref="DEBUG_FILE"/>-->
<!--            <appender-ref ref="INFO_FILE"/>-->
<!--            <appender-ref ref="WARN_FILE"/>-->
<!--            <appender-ref ref="ERROR_FILE"/>-->
<!--            <appender-ref ref="ALL_FILE"/>-->
        </root>
        <logger name="com.tong.demo" level="debug"/> <!-- 开发环境, 指定某包日志为debug级 -->
    </springProfile>
    <springProfile name="test">
        <root level="info">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="DEBUG_FILE"/>
            <appender-ref ref="INFO_FILE"/>
            <appender-ref ref="WARN_FILE"/>
            <appender-ref ref="ERROR_FILE"/>
            <appender-ref ref="ALL_FILE"/>
        </root>
        <logger name="com.tong.demo" level="info"/> <!-- 测试环境, 指定某包日志为info级 -->
    </springProfile>
    <springProfile name="pro">
        <root level="info">
            <!-- 生产环境最好不配置console写文件 -->
            <appender-ref ref="DEBUG_FILE"/>
            <appender-ref ref="INFO_FILE"/>
            <appender-ref ref="WARN_FILE"/>
            <appender-ref ref="ERROR_FILE"/>
            <appender-ref ref="ALL_FILE"/>
        </root>
        <logger name="com.tong.demo" level="warn"/> <!-- 生产环境, 指定某包日志为warn级 -->
        <logger name="com.tong.demo.MyApplication" level="info"/> <!-- 特定某个类打印info日志, 比如application启动成功后的提示语 -->
    </springProfile>
</configuration>

Spring Boot与Web开发

RestTemplate调用REST服务

适用于微服务架构下服务之间的远程调用 ps:以后使用微服务架构spring cloud feign

图片.png
WebClient也可以调用远程服务,区别:webclient依赖webflux,webclient请求远程服务是无阻塞的,响应的。
RestTemplate它是阻塞的,需要等待请求响应后才能执行下一句代码。

通过MorkMvc测试

MockMvc是有spring-test包提供,实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller调用,是的测试熟读看、不依赖网络环境。同事提供了一套验证的工具,结果的验证十分方便。

        <!--junit5 spring-test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcTests {
    @Autowired
    MockMvc mockMvc;

    @Test
    void testMockMvcTest() throws Exception {
        //发送一个模拟请求,不依赖网络,不依赖web服务,不需要启动web应用
        mockMvc.perform(
                MockMvcRequestBuilders.get("/test")
                        //设置响应文本类型
                        .accept(MediaType.APPLICATION_JSON_VALUE)
                        //设置请求文本类型
                        .contentType(MediaType.APPLICATION_JSON_VALUE)
        )
                //响应断言
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }
}

swagger调用

        <!--swagger2的依赖-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("test-1")
                .pathMapping("/")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.my.controller"))
                .paths(PathSelectors.any())
                .build().apiInfo(new ApiInfoBuilder()
                        .title("tong")
                        .description("详细信息")
                        .version("version")
                        .contact(new Contact("tong", "url", "email"))
                        .build());
    }
}

访问:http://localhost:8080/swagger-ui.html
注解:

  • 用于controller类上
    • @API
  • 用于方法上
    • @ApiOperation 方法说明
    • @ApilmplicitParams、@ApilmplicitParam 方法参数说明
    • @ApiResponses、@ApiResponse 方法返回值说明
  • 对象类

    • @ApiModel 用于JavaBean类上
    • @ApiModelProperty 用于JavaBean类的属性上

      AOP

         <!--aop的场景启动器-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-aop</artifactId>
         </dependency>
      
      @Aspect
      @Component
      public class LogAspect {
      Logger log = LoggerFactory.getLogger(LogAspect.class);
      
      @Around("execution(* com.my.controller.*.*(..))&&@annotation(apiOperation)")
      public Object around(ProceedingJoinPoint joinPoint, ApiOperation apiOperation) throws Throwable {
         StringBuilder logInfo = new StringBuilder("用户访问了:");
         Class<?> aClass = joinPoint.getThis().getClass();
         Api api = aClass.getAnnotation(Api.class);
         if (api != null) {
             logInfo.append("class:" + api.value());
         }
         String value = apiOperation.value();
         logInfo.append("method:" + value);
         log.info(logInfo.toString());
         return joinPoint.proceed();
      }
      }
      

      自定义配置

      拦截器

      perHandle:方法执行前
      postHandle:方法执行后,视图渲染前
      afterCompletion:试图渲染后

      public class TimeInterceptor implements HandlerInterceptor {
      LocalDateTime begin;
      LocalDateTime end;
      Logger log = LoggerFactory.getLogger(TimeInterceptor.class);
      
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         //开始时间
         begin = LocalDateTime.now();
         return true;
      }
      
      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
         end = LocalDateTime.now();
         Duration between = Duration.between(begin, end);
         log.info("当前请求执行了:" + between.toMillis() + "ms");
      }
      }
      
      @Configuration
      public class MyWebMvcConfigurer implements WebMvcConfigurer {
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
         //拦截规则
         registry.addInterceptor(new TimeInterceptor())
                 //拦截路径
                 .addPathPatterns("/**");
         //.excludePathPatterns("/**");//排除路径
      }
      }
      

      跨域请求处理

      全局跨域配置

      @Override
      public void addCorsMappings(CorsRegistry registry) {
         //允许跨域的路径
         registry.addMapping("/**")
                 //配置跨域地址
                 .allowedOrigins("http://localhost:8081")
                 //跨域方法
                 .allowedMethods("GET", "POST", "DELETE", "PUT");
      }
      

      方法添加

      @GetMapping("/test")
      @CrossOrigin
      public String test() {
         return "string";
      }
      

      JSON

  • Gson

  • Jackson
  • JSON-B

Jackson使用:

  • @JsonIgnore:加载属性上,将不会进行json转换
  • @JsonFormat(pattern=”yyyy-M-dd hh:mm:ss”,locale=”zh”):进行日期格式化
  • @JsonInclude(JsonInclude.Include.NON_NULL):当属性值为null不被包含
  • @JsonProperty:为属性添加别名

SpringBoot提供自定义json序列化与反序列化

@JsonComponent
public class UserJsonCustom {
    public static class Serializer extends JsonObjectSerializer<User> {

        @Override
        protected void serializeObject(User value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
        }
    }

    public static class Deserializer extends JsonObjectDeserializer<User> {

        @Override
        protected User deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, JsonNode tree) throws IOException {
            return null;
        }
    }
}

国际化

spring:
  messages:
    basename: i18n.message

图片.png

    @Autowired
    MessageSource messageSource;

    @GetMapping("/test")
    public String test() {
        String message = messageSource.getMessage("user.query.success", null, LocaleContextHolder.getLocale());
        return message;
    }

自定义国际化

    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver localeResolver = new CookieLocaleResolver();
        //设置过期时间
        localeResolver.setCookieMaxAge(60 * 60 * 24 * 30);
        localeResolver.setCookieName("locale");
        return localeResolver;
    }
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加国际化拦截器
        registry.addInterceptor(new LocaleChangeInterceptor())
                .addPathPatterns("/**");
    }
}

同意异常处理

  • SpringBoot有统一异常处理了的配置类

    • ErrorMvcAutoConfiguration
      @ControllerAdvice
      public class GeneralExceptionHandler {
      @ExceptionHandler(Exception.class)
      public String handleException(Exception ex) {
         ex.printStackTrace();
         String resultStr = "异常:Exception";
         return resultStr;
      }
      }
      

      SpringBoot的嵌入式Servlet容器

      SpringBoot默认的Servlet容器是Tomcat。

  • 嵌入式Servlet容器配置修改

    • server.*来进行web服务配置
    • 会根据配置文件形成互补
  • 注册Servlet的三大组件

    • servlet listener filter
    • servlet3.0提供的注册

      @WebServlet
      @WebListener
      @WebFilter
      public class HelloServlet extends HttpServlet {
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         super.doGet(req, resp);
      }
      }
      
    • springboot提供的注册

      public class BeanServlet extends HttpServlet {
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         super.doGet(req, resp);
      }
      }
      
      @Bean
      public ServletRegistrationBean myServlet() {
         ServletRegistrationBean servletServletRegistrationBean = new ServletRegistrationBean<>();
         servletServletRegistrationBean.setServlet(new BeanServlet());
         servletServletRegistrationBean.setName("BeanServlet");
         servletServletRegistrationBean.addUrlMappings("/BeanServlet");
         return servletServletRegistrationBean;
      }
      
  • 切换其他servlet容器

    • SpringBoot包含对嵌入式Tomcat、Jetty和Undertow服务器支持
    • Tomcat(默认)
    • Jetty(socket)
    • Undertow(响应式)
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
             <exclusions>
                 <exclusion>
                     <groupId>org.springframework.boot</groupId>
                     <artifactId>spring-boot-starter-tomcat</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-jetty</artifactId>
         </dependency>
      
  • 嵌入式servlet容器自动配置原理

    • ServletWebServerFactoryAutoConfiguration
  • 使用外部servlet容器
    • 安装tomcat 环境变量
    • war tomcat webapp starup.sh 启动

      Spring Boot集成MyBatis

      Druid

         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-jdbc</artifactId>
         </dependency>
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>druid-spring-boot-starter</artifactId>
             <version>1.2.8</version>
         </dependency>
      
      spring:
      datasource:
      username: root
      password: root
      url: jdbc:mysql://localhost:3306/test?useUnicode=true&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
      #   数据源其他配置
      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,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,wall
      maxPoolPreparedStatementPerConnectionSize: 20
      useGlobalDataSourceStat: true
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
      druid:
       stat-view-servlet:
         enabled: true
         login-username: root
         login-password: root
      #初始时运行sql脚本
      sql:
      init:
       schema-locations: classpath:sql/schema.sql
       mode: always
      

      MyBatis

         <dependency>
             <groupId>org.mybatis.spring.boot</groupId>
             <artifactId>mybatis-spring-boot-starter</artifactId>
             <version>2.2.1</version>
         </dependency>
      
      自动生成代码
      <!--mybatis-generator插件,自动生成代码-->
      <build>
         <plugins>
             <plugin>
                 <groupId>org.mybatis.generator</groupId>
                 <artifactId>mybatis-generator-maven-plugin</artifactId>
                 <version>1.4.0</version>
                 <configuration>
                     <configurationFile>${project.basedir}/src/main/resources/generatorConfig.xml</configurationFile>
                     <verbose>true</verbose>
                     <overwrite>true</overwrite>
                 </configuration>
                 <dependencies>
                     <dependency>
                         <groupId>mysql</groupId>
                         <artifactId>mysql-connector-java</artifactId>
                         <version>8.0.28</version>
                     </dependency>
                 </dependencies>
             </plugin>
         </plugins>
      </build>
      
      #驼峰命名
      mybatis:
      configuration:
      map-underscore-to-camel-case: true
      

      Spring Boot启动原理源码剖析

      https://www.yuque.com/docs/share/56012c8f-b238-4da6-95d3-ad1e723812d1?# 《SpringBoot2启动原理》

通过spring-boot-plugin生成MANIFEST.MF文件,其中有main-class指定运行java -jar的主程序,把依赖的jar文件打包在fat jar
JarLauncher通过加载BOOT-INF/classes目录以及BOOT-INF/lib目录下jar文件,实现了 fat jar的启动。
SpringBoot通过扩展JarFile、JarURLConnection及URLStreamHandler,实现了jar in jar中资源的加载。
SpringBoot通过扩展URLClassLoader-LuancherURLClassLoader,实现了jar in jar中class文件的加载。

Spring Boot自定义starters

https://gitee.com/tongtonghushen/tong-springboot.git

Spring Boot集成常用中间件