SpringBoot学习

SpringBoot2

springboot在版本3以上最低支持jdk17,所以jdk8只能用springboot2.x.x

注意: 导入他人的项目,如果依赖成功加载,却仍显示找不到,可以在 文件—清除缓存,并重启。

概念

SpringBoot 对 spring 的简化,也是其核心功能和优点:

  • 起步依赖
  • 自动配置
  • 辅助功能(内置服务器…)

具体体现在:

  • parent
  • starter
  • 引导类
  • 内嵌tomcat

parent

由于依赖的版本之间有一定的固定搭配,所以 SpringBoot 制作了多个技术版本搭配的列表,就叫做 parent。所以 ,使用parent可以帮助开发者进行版本的统一管理。

在pom.xml中继承了一个坐标:

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

打开后,可以看到主要有两组信息:

  • 第一组是 各式各样的依赖版本号属性;

  • 第二组是 各式各样的的依赖坐标信息,其中依赖坐标定义是应用了第一组信息中定义的依赖版本属性值; 且是在<dependencyManagement> 标签中,所以这些定义的依赖还未导入,SpringBoot会在starter中导入一些固定的依赖组合。

    <!-- 第一组信息 版本属性值 -->
    <properties>
        <activemq.version>5.16.3</activemq.version>
        <aspectj.version>1.9.7</aspectj.version>
        .....
        .....
    </properties>
    <!-- 第二组信息 依赖坐标 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>${hibernate.version}</version>
            </dependency>
            ......
            ......
        </dependencies>
    </dependencyManagement>
    

starter

依赖坐标的使用往往有一些固定的组合方式,SpringBoot 定义了starter: starter是指使用某种技术时 依赖的固定搭配格式,也是一种最佳解决方案。 使用starter可以帮助开发者减少依赖配置。

​ 所以,实际开发中如果需要用什么技术,先去找有没有这个技术对应的starter:

  • 如果有对应的starter,直接写starter,而且无需指定版本,版本由parent提供;
  • 如果没有对应的starter,再手写坐标;

SpringBoot 对于 starter 的名称都是如下格式:

命名规则:spring-boot-starter-技术名称

所有的starter中都会依赖一个starter,叫做spring-boot-starter,它是所有的SpringBoot的starter的基础依赖,里面定义了SpringBoot相关的基础配置。

小结:

  1. 开发SpringBoot程序需要导入坐标时通常导入对应的starter
  2. 每个不同的starter根据功能不同,通常包含多个依赖坐标
  3. 使用starter可以实现快速配置的效果,达到简化配置的目的

引导类

程序运行的入口是 SpringBoot工程创建时自带的有main方法的那个类,运行这个类就可以启动SpringBoot工程的运行。

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

Spring程序运行的基础是需要创建自己的Spring容器对象(IoC容器)并将所有的对象交给Spring的容器管理,也就是一个一个的Bean。而SpringBoot本身是为了加速Spring程序的开发的,所以创建了这个 引导类,它是所有功能的入口。

内嵌tomcat

在导入的依赖中:

  • spring-boot-starter-web
    • spring-boot-starter-tomcat
      • tomcat-embed-core(tomcat内嵌核心)

就是 tomcat-embed-core 把tomcat功能引入到程序中。

内嵌Tomcat运行原理:Tomcat服务器是一款使用java语言开发的软件,那么tomcat的运行肯定也是以对象的形式运行的,即tomcat可以交给Spring容器管理。

更换内嵌的tomcat:

  • tomcat(默认):apache出品,粉丝多,应用面广,负载了若干较重的组件
  • jetty:更轻量级,负载性能远不及tomcat
  • undertow:负载性能勉强跑赢tomcat

只有加入对应的依赖坐标,并排除tomcat坐标即可:

<dependencies>
    <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>
</dependencies>

配置文件

SpringBoot将配置信息集中在一个文件 application.properties 中写,不管是服务器的配置,还是数据库的配置,都是写在一起的。

配置的书写有格式和规范,具体的可以参考官方文档(的附录Application Properties):https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties

导入的spring-boot-starter-web依赖提供了 配置书写 的提示。

支持的配置文件格式:(一般使用 yml格式)

  • properties格式
  • yml格式
  • yaml格式

yaml只是后缀名和yml不一样,其实是同一种格式,虽然 .yaml 才是YAML官方认定的文件拓展名,但实际上,未认定的 .yml 用的人用多得多。

如果三个文件共存的话,不同的配置都会生效,而相同、冲突的配置类型的 优先级从高到低:.properties > .yml > .yaml

YAML

YAML(YAML Ain’t Markup Language),一种数据序列化格式。

  • 具有容易阅读、容易与脚本语言交互、以数据为核心,重数据轻格式的特点。

  • 具有严格的语法格式要求:

    1. 大小写敏感
    2. 属性层级关系使用多行描述,每行结尾使用冒号":" 结束
    3. 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
    4. 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
    5. #号 表示注释
    # 常见的数据书写格式
    
    boolean: TRUE  		#TRUE,true,True,FALSE,false,False均可
    float: 3.14    		#6.8523015e+5  #支持科学计数法
    int: 123       		#0b1010_0111_0100_1010_1110    #支持二进制、八进制、十六进制
    null: ~        		#使用~表示null
    string: HelloWorld      			#字符串可以直接书写
    string2: "Hello World"  			#可以使用双引号包裹特殊字符
    date: 2018-02-17        			#日期必须使用yyyy-MM-dd格式
    datetime: 2018-02-17T15:02:31+08:00  #时间和日期之间使用T连接,最后使用+代表时区
    
    # 此外,还可以表示数组,用减号作为数据开始符号
    subject:
    	- Java
    	- 前端
    	- 大数据
    enterprise:
    	name: itcast
        age: 16
        subject:
        	- Java
            - 前端
            - 大数据
    likes: [java,前端,大数据]	#数组书写缩略格式
    #对象数组格式一
    users:							 
      - name: Tom
       	age: 4
      - name: Jerry
        age: 5
    #对象数组格式二
    users:							 
      -  
        name: Tom
        age: 4
      -   
        name: Jerry
        age: 5			    
    users2: [ { name:Tom , age:4 } , { name:Jerry , age:5 } ]	#对象数组缩略格式
    

注:

yaml文件对于数字的定义支持多种进制格式,如果是使用字符串,要用引号明确标注。

如: password: 0127 ,这里开头是0,又没有引号,所以会被识别为八进制,转为十进制就是87。

读取数据

读取单个数据:

yaml中保存的单个数据,可以使用Spring中的注解直接读取,使用**@Value** 可以读取单个数据,属性名引用方式:${一级属性名.二级属性名……}

读取全部数据:

SpringBoot提供了一个对象叫 Environment,使用自动装配注解可以将所有的yaml数据封装到这个对象中,然后使用 具体方法getProperties(String),参数填写属性名即可。

@Autowired
private Environment env;

env.getProperty("xxx")

读取对象数据:

由于单一数据读取太麻烦,Environment又封装的太厉害。所以一般都是将一组数据封装成一个对象。

步骤:

  1. 定义一个对象,并将该对象纳入Spring管控的范围,也就是定义成一个bean;
  2. 然后使用注解@ConfigurationProperties指定该对象加载哪一组yaml中配置的信息。(需要告知加载的数据前缀)
@Component
@ConfigurationProperties(prefix = "book")
public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
}
book:
  id: 12
  type: abcd
  name: zzc
  description: hhhhh

数据的引用:

对于重复出现的数据,可以使用 ${} 进行引用:

center:
	dataDir: D:/usr/local/fire/data
    tmpDir: D:/usr/local/fire/tmp
    logDir: D:/usr/local/fire/log
    msgDir: D:/usr/local/fire/msgDir

# 使用引用减少重复书写
baseDir: D:/usr/local/fire
center:
    dataDir: ${baseDir}/data
    tmpDir: ${baseDir}/tmp
    logDir: ${baseDir}/log
    msgDir: ${baseDir}/msgDir

注:如果需要解析转义字符,需要用双引号包裹起来作为字符 才能解析。

更多数据配置

@ConfigurationProperties

@ConfigurationProperties除了给自定义的bean加载属性值,也可以为第三方bean加载属性,不过格式会特殊些。

  1. 在yml中定义要绑定的属性,注意datasource此时全小写

    datasource:
      driverClassName: com.mysql.jdbc.Driver
    
  2. 使用@Bean注解定义第三方bean,使用@ConfigurationProperties注解为第三方bean进行属性绑定,注意前缀是全小写的datasource

        @Bean
        @ConfigurationProperties(prefix = "datasource")
        public DruidDataSource datasource(){
            DruidDataSource ds = new DruidDataSource();
            return ds;
        }
    

问题:@ConfigurationProperties注解可以为bean进行属性绑定, 所以它可以写在 类和方法上,找起来比较麻烦。

所以有一个注解:@EnableConfigurationProperties,可以标注使用@ConfigurationProperties注解绑定属性的bean是哪些。使用如下:

  1. 在配置类上开启@EnableConfigurationProperties注解,并标注要使用@ConfigurationProperties注解绑定属性的类

    @SpringBootApplication
    @EnableConfigurationProperties(ServerConfig.class)
    public class Springboot13ConfigurationApplication {
    }
    
  2. 在对应的类上直接使用@ConfigurationProperties进行属性绑定

    @Data
    @ConfigurationProperties(prefix = "servers")
    public class ServerConfig {
        private String ipAddress;
        private int port;
        private long timeout;
    }
    

    注:这里不需要@Component 来标注为bean, 因为使用@EnableConfigurationProperties注解时,spring会默认将其标注的类定义为bean,因此无需再次声明@Component注解了。 (一起用会标注重复的bean,会报错)

  3. 在yml中定义要绑定的属性:

    servers:
      ip-address: 192.168.0.1 
      port: 2345
      timeout: 3000
    

如果使用@ConfigurationProperties注解时,出现一个标红的坐标提示信息,可以添加一个坐标以解决:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
宽松绑定

进行数据绑定时, yml文件和@ConfigurationProperties(prefix = “xx”)的 前缀必须是小写 的,否则会出错。

而前缀所管辖的数据则非常宽松,也就是,配置文件中的命名格式与变量名的命名格式可以进行格式上的最大化兼容。只要字母对的上,不论大小写不一致还是加了 “_” 或 "-“ 都能正确加载。

springboot推荐使用烤肉串模式/中划线模式,如 ip-address ,data-source。

注:宽松绑定仅针对springboot中@ConfigurationProperties注解进行属性绑定时有效,对@Value是无效的

常用计量单位绑定

对于yml文件中的需要规定单位的配置值,如时间,存储大小。springboot利用了JDK8中提供的用来表示计量单位的数据类型 来绑定计量单位。

  • Duration:表示时间间隔,可以通过 @DurationUnit 注解描述时间单位,例如上例中描述的单位为小时(ChronoUnit.HOURS)

    常用单位:

    DAYS,SECONDS,MINUTES,HALF_DAYS,CENTURIES,

    DECADES,ERAS,FOREVER,HOURS,MICROS,MILLENNIA,

    MILLIS,MONTHS,NANOS,WEEKS,YEARS

  • DataSize:表示存储空间,可以通过 @DataSizeUnit 注解描述存储空间单位,例如上例中描述的单位为MB(DataUnit.MEGABYTES)

    常用单位:MEGABYTES,BYTES,GIGABYTES,KILOBYTES,TERABYTES

@Component
@Data
@ConfigurationProperties(prefix = "servers")
public class ServerConfig {
    @DurationUnit(ChronoUnit.HOURS)
    private Duration serverTimeOut;
    
    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize dataSize;
}
校验

对于配置中的数据和代码中的定义的数据,可能存在数据类型不匹配的问题,这样会无法正常绑定。所以需要校验功能。

在JAVAEE的JSR303规范中给出了具体的数据校验标准,开发者可以根据自己的需要选择对应的校验框架。
此处使用Hibernate提供的校验框架来作为实现进行数据校验。

  1. 开启校验框架

    <!--1.导入JSR303规范-->
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
    </dependency>
    <!--使用hibernate框架提供的校验器做实现-->
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>
    
  2. 在需要开启校验功能的类上使用注解@Validated开启校验功能

    @Component
    @Data
    @ConfigurationProperties(prefix = "servers")
    @Validated   //开启对当前bean的属性注入校验
    public class ServerConfig {
    }
    
  3. 对具体的字段设置校验规则

    @Component
    @Data
    @ConfigurationProperties(prefix = "servers")
    @Validated	//开启对当前bean的属性注入校验
    public class ServerConfig {
        //设置具体的规则
        @Max(value = 8888,message = "最大值不能超过8888")
        @Min(value = 202,message = "最小值不能低于202")
        private int port;
    }
    

    通过设置数据格式校验,就可以有效避免非法数据加载。

DTO部分

DTO,即数据传输对象,用于表现层和应用层之间的数据交互。

简单来说Model面向业务,我们是通过业务来定义Model的。而DTO是面向界面UI,是通过UI的需求来定义的。 通过DTO我们实现了表现层与Model之间的解耦。

例子:

  • 表现层一致性处理: Result 结果类

    一般后端传给前端的数据,都应该封装成一个同一的结果类,所以需要定义一个dto,统一格式,如:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Result {
        private Boolean success;    //业务是否成功
        private String errorMsg;    //出错时要传递的消息
        private Object data;        //业务数据
        private Long total;         //
    
        public static Result ok(){
            return new Result(true, null, null, null);
        }
        public static Result ok(Object data){
            return new Result(true, null, data, null);
        }
        public static Result ok(List<?> data, Long total){
            return new Result(true, null, data, total);
        }
        public static Result fail(String errorMsg){
            return new Result(false, errorMsg, null, null);
        }
    }
    
  • 实体类与要传给前端时,有些变量不一致时,也要定义一个dto。

    如:实体类User中有密码等隐私字段,而传出去的数据不能包含这些信息,所以可以定义一个UserDTO,只定义需要的变量即可。

    @Data
    public class UserDTO {
        private Long id;
        private String nickName;
        private String icon;
    }
    
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Accessors(chain = true)
    @TableName("tb_user")
    public class User implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        /**
         * 主键
         */
        @TableId(value = "id", type = IdType.AUTO)
        private Long id;
    
        /**
         * 手机号码
         */
        private String phone;
    
        /**
         * 密码,加密存储
         */
        private String password;
    
        /**
         * 昵称,默认是随机字符
         */
        private String nickName;
    
        /**
         * 用户头像
         */
        private String icon = "";
    
        /**
         * 创建时间
         */
        private LocalDateTime createTime;
    
        /**
         * 更新时间
         */
        private LocalDateTime updateTime;
    }
    

SpringBoot整合

SpringBoot整合其他技术,都是先导入对应的starter或坐标,再去使用对应技术。

整合JUnit

JUnit的依赖默认导入了。

使用注解@SpringBootTest替换了原来的两个注解。如果要手动加载引导类,就写上属性 classes = 引导类.class

@SpringBootTest
class Tests {
    //注入你要测试的对象
    @Autowired
    private BookDao bookDao;
    
    @Test
    void contextLoads() {
        //执行要测试的对象对应的方法
        bookDao.save();
    }
}

整合Mybatis

原来的Spring整合Mybatis的步骤需要有:

  • 导入坐标
  • Spring核心配置:SpringConfig
  • MyBatis要交给Spring接管的bean:MyBatisConfig
  • 数据源对应的bean
  • 数据库连接信息:jdbc.properties

而使用 SpringBoot 整合 Mybatis,只需要:

  • 创建模块时勾选对应技术:Mybatis和Mysql

    或者手工导入:

    <dependencies>
        <!--1.导入对应的starter-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
    
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>
    </dependencies>
    
  • 配置数据源信息:

    # 配置相关信息
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia
        username: root
        password: zzc
    # mybatis
    mybatis:
      typeAliasesPackage: com.zzc.library.po
      mapperLocations: classpath:mapper/*.xml
      configuration:
        map-underscore-to-camel-case: true	# 驼峰映射
    # 分页插件
    pagehelper:
      helper-dialect: mysql
      reasonable: true
      support-methods-arguments: true
    

到此,就配置完毕了。之后只需书写 实体类和映射接口(Dao)即可:

//实体类
public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;
}
//映射接口
@Mapper
public interface BookDao {
    @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id);
}
//测试类
@SpringBootTest
class Springboot05MybatisApplicationTests {
    @Autowired
    private BookDao bookDao;
    @Test
    void contextLoads() {
        System.out.println(bookDao.getById(1));
    }
}

整合MybatisPlus

  • 导入对应的starter:

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

    第三方提供的starter,命名为:**-spring-boot-starter 或者 **-boot-starter

  • 配置数据源信息

    #2.配置相关信息
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ssm_db
        username: root
        password: root
    

同样两步配置完毕,之后写MybatisPlus的程序:

//映射接口 
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
//测试类
.....

整合Druid

  • 导入对应坐标

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
    </dependencies>
    
  • 修改数据源:

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
        username: root
        password: root
        type: com.alibaba.druid.pool.DruidDataSource
    

    在数据源配置中的type属性,用于指定数据源类型(默认指定的数据源时HiKari)

    如果数据源需要进行个性化的配置,就需要导入对应数据源的starter,如导入Druid的:

    <dependencies>
     <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid-spring-boot-starter</artifactId>
         <version>1.2.6</version>
     </dependency>
    </dependencies>
    

    修改配置:

    spring:
    datasource:
     druid:
       driver-class-name: com.mysql.cj.jdbc.Driver
       url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
       username: root
       password: root
    

运维事项

工程打包

  1. 使用maven的package进行打包,会打包至target目录下, 后缀为 .jar
  2. 使用 "java -jar 打包的文件名"命令运行工程即可。

注:jar支持命令启动需要依赖maven插件,需要确定有导入以下插件。

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

临时属性

  1. 使用jar命令启动SpringBoot工程时可以使用临时属性替换配置文件中的属性
  2. 临时属性添加方式:java –jar 工程名.jar –-属性名=值
  3. 多个临时属性之间使用空格分隔
  4. 临时属性必须是当前boot工程支持的属性,否则设置无效
java -jar 项目打包文件.jar --server.port=8080 --spring.datasource.druid.password=123

在开发环境jdea中测试临时属性:在运行/调试配置中的“程序参数”进行输入。

在“程序参数”中输入的数据,其实会保存到启动程序的参数args中:

@SpringBootApplication
public class XxxApplication {
    public static void main(String[] args) {
        SpringApplication.run(XxxApplication.class, args); //args参数可以不传,相当于断掉外部临时参数的入口,使得临时参数无法生效
    }
}

配置文件

配置文件4级分类: 满足不同阶段对配置的设置需求

  • SpringBoot中4级配置文件

    1级: file :config/application.yml 【级别最高】 (file是指打包文件.jar所在的目录)

    2级: file :application.yml

    3级:classpath:config/application.yml (classpath是源根,即工程的resources目录)

    4级:classpath:application.yml 【级别最低】

  • 作用:

    • 1级与2级留做系统打包后设置通用属性,1级常用于运维经理进行线上整体项目部署方案调控。2级用于运维人员配置涉密的线上环境。

    • 3级与4级用于系统开发阶段设置通用属性,3级常用于项目经理进行整体项目属性调控,4级是程序员开发时用的。

自定义配置文件:

在运行/调试配置中的“程序参数”中输入自定义的配置文件:

--spring.config.name=文件名
--spring.config.location=classpath:/文件名.yml

多配置文件常用于将配置进行分类,进行独立管理,或将将可选配置单独制作便于上线更新维护:

--spring.config.location=classpath:/文件名.yml,classpath:/文件名.properties
  • 单服务器项目:使用自定义配置文件需求较低
  • 多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理
  • 基于SpringCloud技术,所有的服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息

多环境开发配置

多环境开发:

1.单文件:

# 应用哪个环境, 以及公共的配置
spring:
	profiles:
		active: pro
		
# 使用 --- 区分不同的环境, spring.profiles配置环境名
---
# 生产环境
spring:
	profiles: pro
server:
	port: 80

---
# 开发环境
spring:
	profiles: dev
server:
	port: 81
	
---
# 测试环境
spring:
	profiles: test
server:
	port: 82

2.多文件:

把多个环境的配置放在一个文件里,安全性不好,可以采用多文件的配置方式:

  1. 主启动文件 application.yml

    # 应用哪个环境, 以及公共的配置
    spring:
    	profiles:
    		active: pro
    

    环境分类文件的命名为: application-环境名.yml

  2. 环境分类文件 application-pro.yml

    server:
    	port: 80
    
  3. 环境分类文件 application-dev.yml

    server:
    	port: 81
    
  4. 环境分类文件 application-test.yml

    server:
    	port: 82
    

多环境分组管理:

根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下

  • application-devDB.yml
  • application-devRedis.yml
  • application-devMVC.yml

主启动文件application.yml中使用include属性进行指定:

spring:
    profiles:
        active: dev
        include: devDB,devRedis,devMVC

从Spring2.4版开始使用group属性替代include属性,降低了配置书写量 —— 使用group属性定义多种主环境与子环境的包含关系

spring:
    profiles:
        active: dev
        group:
            "dev": devDB,devRedis,devMVC
            "pro": proDB,proRedis,proMVC
            "test": testDB,testRedis,testMVC

Maven与SpringBoot多环境兼容:

当Maven与SpringBoot同时对多环境进行控制时,以Mavn为主, SpringBoot使用@…@占位符读取Maven对应的配置属性值:

  1. Maven中设置多环境属性(pom.xml):

    <profiles> 
        <profile> 
            <id>dev_env</id> 
            <properties> 
                <profile.active>dev</profile.active> 
            </properties> 
            <activation> 
                <activeByDefault>true</activeByDefault> 
            </activation> 
        </profile> 
        <profile> 
            <id>pro_env</id> 
            <properties> 
                <profile.active>pro</profile.active> 
            </properties> 
            </profile> 
        <profile> 
            <id>test_env</id> 
            <properties> 
                <profile.active>test</profile.active> 
            </properties> 
        </profile>
    </profiles
    
  2. Spring中引用Maven属性:

    spring:
        profiles:
            active: @profile.active@
    
  3. 执行Maven打包,可在生成的boot打包.jar文件中查看。

日志

日志作用:

  • 编程期调试代码
  • 运营期记录信息:
    • 记录日常运营重要信息(峰值流量、平均响应时长……)
    • 记录应用报错信息(错误堆栈)
    • 记录运维过程数据(扩容、宕机、报警……)

使用:

1.导入依赖

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

2.yml配置文件中可以设置日志级别:debug,info,warn,error

# 开启debug模式,输出调试信息,常用于检查系统运行状况
debug: true
# 设置日志级别,root表示根节点,即整体应用日志级别
logging:
    # 设置日志组
    group:
        # 自定义组名,设置当前组中所包含的包
        ebank: com.zzc.controller,com.zzc.dao
    level:
        root: warn
        # 为对应组设置日志级别
        ebank: debug
        # 为对包设置日志级别
        com.zzc.controller: debug

3.在对应类上添加注解:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
    public void test(){
        log.debug("debug ...");
        log.info("info ...");
        log.warn("warn ...");
        log.error("error ...");
    }
}

或者:

private static final Logger log = LoggerFactory.getLogger(UserController.class);
log.debug("debug ...");
log.info("info ...");
log.warn("warn ...");
log.error("error ...");

日志输出格式控制:

logging:
    pattern:
        console: "%d %clr(%p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"

默认的是: 时间,级别,PID,所属线程,所属类/接口名,日志信息

设置记录日志的文件:

logging:
	# 单一日志文件,日志都记在一个文件,会导致文件过大
    file:
        name: server.log
    # 滚动日志文件,超过指定大小就再新建一个日志文件
    logback:
        rollingpolicy:
        	# 指定的文件大小
            max-file-size: 3KB
            # 文件名
            file-name-pattern: server.%d{yyyy-MM-dd}.%i.log

开发使用

热部署

热部署,就是修改程序后,服务器只修改更新后的程序,不用重启服务器。

springboot项目设置热部署:

部署原理:

一个springboot项目在运行时实际上是分两个过程进行的,根据加载的东西不同,划分成base类加载器与restart类加载器。

  • base类加载器:用来加载jar包中的类,jar包中的类和配置文件由于不会发生变化,因此不管加载多少次,加载的内容不会发生变化
  • restart类加载器:用来加载开发者自己开发的类、配置文件、页面等信息,这一类文件受开发者影响

​ 当springboot项目启动时,base类加载器执行,加载jar包中的信息后,restart类加载器执行,加载开发者制作的内容。当执行构建项目后,由于jar中的信息不会变化,因此base类加载器无需再次执行,所以仅仅运行restart类加载即可,也就是将开发者自己制作的内容重新加载就行了,这就完成了一次热部署的过程。

​ 也可以说热部署的过程实际上是重新加载restart类加载器中的信息。

  • 手动启动热部署:

    1. 导入对应坐标:

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <optional>true</optional>
      </dependency>
      
    2. 手动构建项目: bulid——Build Project,或 CTRL+F9

  • 自动启动热部署:

    1. 打开 File —— settings —— Build,Execution,Deployment —— Compiler —— 勾选【Build project automatically】,意思是自动构建项目
    2. 使用快捷键 Ctrl+Alt+Shift+/ ,打开维护面板,选择第1项【Registry…】,搜索compiler,勾选。这样就完成了。

    idea设置当idea工具失去焦点5秒后进行热部署。就是你从idea工具中切换到其他工具时进行热部署,比如改完程序需要到浏览器上去调试,这个时候idea就自动进行热部署操作。

设置热部署监控的文件范围(有些文档文件的修改不需要热部署):

—— 开发者工具中有一组配置,当满足了配置中的条件后,才会启动热部署,配置中默认不参与热部署的目录信息如下:

  • /META-INF/maven
  • /META-INF/resources
  • /resources
  • /static
  • /public
  • /templates

​ 以上目录中的文件如果发生变化,是不参与热部署的。如果想修改配置,可以通过application.yml文件进行设定哪些文件不参与热部署操作:

spring:
  devtools:
    restart:
      # 设置不参与热部署的文件或文件夹
      exclude: static/**,public/**,config/application.yml

关闭热部署:

线上环境运行时是不可能使用热部署功能的,所以需要强制关闭此功能,通过配置可以关闭此功能(关闭了监视代码变动的功能):

spring:
  devtools:
    restart:
      enabled: false

或者(优先级比配置文件高,不容易被其他配置文件覆盖):

@SpringBootApplication
public class XxxApplication {
    public static void main(String[] args) {
        System.setProperty("spring.devtools.restart.enabled","false");
        SpringApplication.run(XxxApplication.class);
    }
}

测试

加载测试用属性

测试过程需要模拟一些线上情况,或者模拟一些特殊情况。如果当前环境按照线上环境已经设定好了,例如是下面的配置:

env:
  maxMemory: 32GB
  minMemory: 16GB

但是如果想测试对应的兼容性,需要测试如下配置:

env:
  maxMemory: 16GB
  minMemory: 8GB

但测试时不能随意修改源码,所以需要在测试环境中创建一组临时属性,去覆盖源码中设定的属性。

  • 临时属性

    在测试用例中,springboot可以通过注解@SpringBootTest添加属性来模拟临时属性:

    //properties属性可以为当前测试用例添加临时的属性配置
    @SpringBootTest(properties = {"test.prop=testValue1"})
    public class PropertiesAndArgsTest {
    
        @Value("${test.prop}")
        private String msg;
        
        @Test
        void testProperties(){
            System.out.println(msg);
        }
    }
    
  • 临时参数

    springboot可以通过命令行参数输入配置信息,测试用例通过@SpringBootTest(args={}) 也可以:

    //args属性可以为当前测试用例添加临时的命令行参数
    @SpringBootTest(args={"--test.prop=testValue2"})
    public class PropertiesAndArgsTest {
        
        @Value("${test.prop}")
        private String msg;
        
        @Test
        void testProperties(){
            System.out.println(msg);
        }
    }
    
加载测试用bean

测试时,可能需要独立的bean,专门应用于测试环境:

  1. 在测试包test中创建专用的测试环境配置类

    @Configuration
    public class MsgConfig {
        @Bean
        public String msg(){
            return "bean msg";
        }
    }
    
  2. 在启动测试环境处,导入测试环境专用的配置类,使用@Import注解即可实现

    @SpringBootTest
    @Import({MsgConfig.class})
    public class ConfigurationTest {
    
        @Autowired
        private String msg;
    
        @Test
        void testConfiguration(){
            System.out.println(msg);
        }
    }
    
Web环境模拟测试

在测试中对表现层功能进行测试需要一个基础和一个功能。

  • 一个基础是:运行测试程序时,必须启动web环境,不然没法测试web功能。
  • 一个功能是:必须在测试程序中具备发送web请求的能力,不然无法实现web功能的测试。

测试类中启动web环境:

​ 在测试类上方的@SpringBootTest注解,有一个属性叫做webEnvironment。通过该属性就可以设置在测试用例中启动web环境:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WebTest {	
}

springboot提供了4中设置值:

  • MOCK:根据当前设置确认是否启动web环境,例如使用了Servlet的API就启动web环境,属于适配性的配置
  • DEFINED_PORT:使用自定义的端口作为web服务器端口
  • RANDOM_PORT:使用随机端口作为web服务器端口
  • NONE:不启动web环境

通过上述配置,启动测试程序时就可以正常启用web环境,建议测试时使用RANDOM_PORT,避免代码中因为写死设定引发线上功能打包测试时由于端口冲突导致意外现象的出现。

测试类中发送请求:

  1. 在测试类中通过注解@AutoConfigureMockMvc 开启web虚拟调用功能:

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    //开启虚拟MVC调用
    @AutoConfigureMockMvc
    public class WebTest {
    }
    
  2. 定义发起虚拟调用的对象MockMVC,通过自动装配的形式初始化对象:

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    //开启虚拟MVC调用
    @AutoConfigureMockMvc
    public class WebTest {
    //    @Autowired
    //    private MockMvc mvc;
        
        @Test
        void testWeb(@Autowired MockMvc mvc) {
        }
    }
    
  3. 创建一个虚拟请求对象,封装请求的路径,并使用MockMVC对象发送对应请求:

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    //开启虚拟MVC调用
    @AutoConfigureMockMvc
    public class WebTest {
    
        @Test
        void testWeb(@Autowired MockMvc mvc) throws Exception {
            //http://localhost:8080/books
            //创建虚拟请求,当前访问/books
            MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
            //执行对应的请求
            mvc.perform(builder);
        }
    }
    
  4. 发完请求会得到一个响应对象,测试该响应对象的各种信息是否符合要求:

    @Test
    void testStatus(@Autowired MockMvc mvc) throws Exception {
        MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books");
        ResultActions action = mvc.perform(builder);
        
        //设定预期值 与真实值进行比较,成功测试通过,失败测试失败
        
        //对比状态码:
        //定义本次调用的预期值
        StatusResultMatchers status = MockMvcResultMatchers.status();
        //预计本次调用时成功的:状态200
        ResultMatcher ok = status.isOk();
        //添加预计值到本次调用过程中进行匹配
        action.andExpect(ok);
        
        //对比响应头信息
        HeaderResultMatchers header = MockMvcResultMatchers.header();
        ResultMatcher contentType = header.string("Content-Type", "application/json");
        action.andExpect(contentType);
        
       	//对比响应体内容(非json格式)
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.string("springboot2");
        action.andExpect(result);
        
        //对比响应体内容(json格式)
        ContentResultMatchers content = MockMvcResultMatchers.content();
        ResultMatcher result = content.json("{\"id\":1,\"name\":\"springboot2\",\"type\":\"springboot\"}");
        action.andExpect(result);
    }
    
数据层测试回滚

打包的阶段的test生命周期属于必须被运行的生命周期,如果跳过会给系统带来极高的安全隐患,所以测试用例必须执行。

不过为了避免测试过程对数据库产生影响,所以需要让测试程序不提交事务:

  • 在测试用例中添加注解@Transactional 即可实现当前测试用例的事务不提交。
@SpringBootTest
@Transactional
@Rollback(true)
public class DaoTest {
    @Autowired
    private BookService bookService;

    @Test
    void testSave(){
        Book book = new Book();
        book.setName("springboot3");
        book.setType("springboot3");
        book.setDescription("springboot3");

        bookService.save(book);
    }
}

如果想提交事务,可以修改为:@Rollback(false),即可正常提交事务。

随机测试数据

对于测试用例的数据固定书写肯定是不合理的,springboot提供了在配置中使用随机值的机制,确保每次运行程序加载的数据都是随机的。具体如下:

testcase:
  book:
    id: ${random.int}
    id2: ${random.int(10)}
    type: ${random.int!5,10!}
    name: ${random.value}
    uuid: ${random.uuid}
    publishTime: ${random.long}

​ 当前配置就可以在每次运行程序时创建一组随机数据,避免每次运行时数据都是固定值的尴尬现象发生,有助于测试功能的进行。数据的加载按照之前加载数据的形式,使用@ConfigurationProperties注解即可

@Component
@Data
@ConfigurationProperties(prefix = "testcase.book")
public class BookCase {
    private int id;
    private int id2;
    private int type;
    private String name;
    private String uuid;
    private long publishTime;
}

​ 对于随机值的产生,还有一些小的限定规则,比如产生的数值性数据可以设置范围等:

testcase:
  book:
    id: ${random.int}	#随机数
    id2: ${random.int(10)}	# 10以内的随机数
    type: ${random.int!5,10!}	#5到10的随机数

初始化阶段

在 Spring Boot 应用程序中,ApplicationRunnerCommandLineRunnerApplicationListener 都可用于在应用程序启动时执行一些特定操作。

以下是它们之间的主要区别:

  1. ApplicationRunner: ApplicationRunner 是一个接口,用于在 Spring Boot 应用程序启动时运行特定代码。当实现 ApplicationRunner 接口时,需要重写 run 方法。该方法接收一个 ApplicationArguments 类型的参数,可以用于处理传递给应用程序的命令行参数。

    public class MyAppRunner implements ApplicationRunner {
        @Override
        public void run(ApplicationArguments args) {
            System.out.println("ApplicationRunner running...");
        }
    }
    
  2. CommandLineRunner: CommandLineRunner 也是一个接口,与 ApplicationRunner 类似,用于在 Spring Boot 应用程序启动时运行特定代码。实现 CommandLineRunner 接口时,也需要重写 run 方法。不同的是,该方法接收一个字符串数组(String[])作为参数,用于处理传递给应用程序的命令行参数。

    public class MyCommandLineRunner implements CommandLineRunner {
        @Override
        public void run(String... args) {
            System.out.println("CommandLineRunner running...");
        }
    }
    
  3. ApplicationListener: ApplicationListener 是一个泛型接口,用于在 Spring Boot 应用程序中监听特定类型的事件。实现 ApplicationListener 接口时,需要指定监听的事件类型,并重写 onApplicationEvent 方法。在此方法中处理事件。与 ApplicationRunner 和 CommandLineRunner 不同,ApplicationListener 可用于监听各种事件,而不仅仅是应用程序启动事件。

    例如,要在应用程序启动时执行特定操作,可以实现 ApplicationListener<ApplicationReadyEvent>

    public class MyAppListener implements ApplicationListener<ContextRefreshedEvent> {
        @Override
        public void onApplicationEvent(ApplicationReadyEvent event) {
            // 只有当前ApplicationContext是根上下文时,此时程序处于根上下文初始化阶段
            if (event.getApplicationContext().getParent() == null) {
               	......
            }
        }
    }
    

总结:

  • ApplicationRunner 和 CommandLineRunner 都用于在 Spring Boot 应用程序启动时执行特定代码,但它们处理命令行参数的方式不同。ApplicationRunner 使用 ApplicationArguments 对象,而 CommandLineRunner 使用字符串数组。
  • ApplicationListener 用于监听应用程序中的特定类型事件,不仅限于启动事件。可以通过实现 ApplicationListener 并指定相应事件类型来处理不同的事件。

数据层解决方案

自定义序列化

  1. 构建要进行序列化的实体类User:

    会用到的常见json注解:

    • @JsonIgnoreProperties:
      此注解是类注解,作用是在json序列化时将Java bean中的某些属性忽略掉,序列化和反序列化都受影响。
    • @JsonIgnore:
      此注解用于属性或者方法上(最好是属性上),作用和上面的@JsonIgnoreProperties一样。
    • @JsonFormat:
      此注解用于属性或者方法上(最好是属性上),可以方便的把Date类型直接转化为我们想要的模式,比如@JsonFormat(pattern = “yyyy-MM-dd HH-mm-ss”)
    • @JsonSerialize:
      此注解用于属性或者getter方法上,用于在序列化时嵌入我们自定义的序列化器,比如序列化一个double时在其后面限制两位小数点。
    • @JsonDeserializ:
      此注解用于属性或者setter方法上,用于在反序列化时嵌入我们自定义的反序列化器,比如反序列化一个Date类型的时间字符串。
    • @JsonCreator与@JsonProperty:
      该注解的作用就是指定反序列化时替代无参构造函数,构造方法的参数前面需要加上@JsonProperty注解。
    @Data
    @ToString
    @JsonIgnoreProperties(value = {"word"})
    public class User {
    
        /**
         * 注意:在进行JSON序列化和反序列化时,要么提供一个无参的构造方法,要么在其他构造方法上添加@JsonCreator注解.
         */
        private String name;
        private int age;
        private boolean sex;
        private Date birthday;
        private String word;
        private double salary;
    
        // @JsonCreator指定反序列化时用于替代无参构造函数, 参数前要加上@JsonProperty
        @JsonCreator
        public User(@JsonProperty("name") String name, @JsonProperty("age") int age, @JsonProperty("sex") boolean sex, @JsonProperty("birthday") Date birthday,
                    @JsonProperty("word") String word, @JsonProperty("salary") double salary) {
            super();
            this.name = name;
            this.age = age;
            this.sex = sex;
            this.birthday = birthday;
            this.word = word;
            this.salary = salary;
        }
    
        /**
         * 反序列化一个固定格式的Date
         */
        @JsonDeserialize(using = CustomeJackson.MyDeserializer.class)
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
    
        /**
         * 序列化指定格式的double格式
         */
        @JsonSerialize(using = CustomeJackson.MySerializer.class)
        public double getSalary() {
            return salary;
        }
    
    }
    
  2. 编写自己的JsonSerializerJsonDeserializer类,来进行自定义的序列化和反序列操作。并使用**@JsonComponent注解**将其注册为spring beans(原来是用是通过Module方式注册到Jackson中)。

    @JsonComponent
    public class CustomeJackson {
    
        /**
         * 自定义序列化器,格式化数值
         */
        public static class MySerializer extends JsonSerializer<Double> {
    
            private DecimalFormat df = new DecimalFormat("##.00");
    
            /**
             * 序列化操作,继承JsonSerializer,重写Serialize函数
             */
            @Override
            public void serialize(
                    Double value,
                    JsonGenerator jsonGenerator,
                    SerializerProvider serializerProvider) throws IOException {
                jsonGenerator.writeString(df.format(value));
            }
        }
    
        /**
         * 自定义反序列化器,格式化时间
         */
        public static class MyDeserializer extends JsonDeserializer<Date> {
    
            private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    
            @Override
            public Date deserialize(
                    JsonParser jsonParser,
                    DeserializationContext deserializationContext) throws IOException {
                Date date = null;
                try {
                    date = sdf.parse(jsonParser.getText());
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                return date;
            }
        }
    
    }
    
  3. 直接编写controller即可,会自动进行序列化操作

    @RestController
    public class UserController {
    
        /**
         * 将对象转为json字符串-->序列化
         */
        @GetMapping("/user/{salary}")
        public User home(@PathVariable("salary") Long salary) {
            return new User("一一哥", 30, true, new Date(), "程序员", salary);
        }
    
        /**
         * 将一个json转化为对象-->反序列化
         */
        @RequestMapping(value = "user")
        public String getValue(@RequestBody User user) {
            log.warn("user=" + user.toString());
            return user.toString();
        }
    
    }
    

httpClient

点击接口 xxxMapping处的图标的“在HTTP客户端中打开”可以跳转到 httpClient,输入测试数据并运行即可。

推荐将测试数据的文件单独保存到一处:

  • 新建一个文件保存端口数据:

    {
      "dev": {
        "access_token": "",
        "gateway_host": "localhost:63010",
        "content_host": "localhost:63040",
        "system_host": "localhost:63110",
        "media_host": "localhost:63050",
        "search_host": "localhost:63080",
        "auth_host": "localhost:63070",
        "checkcode_host": "localhost:63075",
        "learning_host": "localhost:63020"
      }
    }
    
  • 新建另一个测试w文件,如xc-content-api.http,输入测试数据:

    ###
    POST http://{{content_host}}/content/course/list?pageNo=1&pageSize=2
    Content-Type: application/json
    
    {
      "auditStatus": "",
      "courseName": "测试",
      "publishStatus": ""
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值