转载

SpringBoot1.5.x版本回忆录之配置文件

前言

2018年因为业务上需要选择了微服务架构,时间飞逝,转眼来到了2020年。当初的springboot版本也从1.5.x更新到了2.1.x。今天在这里想留下点springboot1.5.17版回忆,以纪念曾经的学习和方便工作上旧框架的使用。

微服务进入历史

我们来追溯一下web的发展历史:

曾几何时,web打天下的是SSH框架/SSM框架,静态页面的代码和操作数据库的逻辑都在同一个工程里面,架构大概是如图所示这样子的:

SpringBoot1.5.x版本回忆录之配置文件

这种架构的特点:

  1. 开发:所有功能集中在一个项目中,前端代码,逻辑代码,数据库代码在同一个工程的不同目录下。
  2. 扩展:如果么一个模块出现性能瓶颈,这个模块可能是整个业务中的么个复杂逻辑功能,可能是数据库的部分查询语句无法更有效的优化,可能是么页面访问量大等等。扩展性能的主要方法是将相同代码部署到多台服务器上,然后在使用负载均衡的方法将流量分发到多个服务器上。
  3. 升级:项目中一个模块出现问题,那么整个项目都需要升级。

想想看这样的架构有什么问题?

如果么个模块出现性能问题,直接将出现性能问题的模块单独增加服务器资源即可,可现在的处理方案却是将所有模块都升级。

如果公司业务庞大,不同业务部分之前需要共享数据;例如处理日志数据的部门,要把日志数据给财务部门让他们统计客户的消费,要把日志数据给前端展示部门供他们展示客户的最近消费情况等等,以前的架构又如何处理呢?

SpringBoot1.5.x版本回忆录之配置文件

微服务进入历史:微服务顾名思义即微小的服务,一个业务模块就可以是一个服务,换言之一个业务模块就是一个项目工程。他有何特点呢?来看如下:

  1. 灵活配置服务器的资源,出现性能瓶颈的服务可以分配更多的服务器资源。
  2. 通过HTTP互相通信,每一个服务最终都是一个可独立替换和独立升级的软件单元
  3. 单个服务出现问题不会影响其他业务服务的运行
  4. 根据业务提供不同的数据服务给不同的业务部门使用比较方便
  5. 如果公司有需要,在进行数据共享的时候,权限,日志,审查等等比单个应用更为健全和灵活

微服务架构看着很好,那它有没有什么缺陷呢?很明显,服务一旦很多,开发(搭建项目)、管理和升级这些服务就比较麻烦了。由此引入我们的springboot。

hello springboot

为了让我们更好的使用微服务,spring官方给了我们一站式解决方案:

SpringBoot1.5.x版本回忆录之配置文件

我们借助于 springboot 帮助我们实现快速开发,借助于 spring cloud 进行部署便能解决微服务项目数量众多的问题。我们这里先来看下springboot的第一个案例,向浏览器发送"hello springboot":

1、创建maven工程,注意是 jar工程

2、导入依赖的jar包

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.17.RELEASE</version>
    </parent> 
    
    <dependencies> 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

3、编写主程序,启动springboot项目

@SpringBootApplication
public class SpringBootStudy {

    public static void main(String[] args) {
        // Spring应用启动起来
        SpringApplication.run(SpringBootStudy.class,args);
    }

}

4、编写相关controller

@RestController
public class HelloControlle {
    
    @RequestMapping("/hello")
    public String hello(){
        return "hello springboot";
    }
}

5、运行主程序,即 main函数 (因为是jar工程)

6、在浏览器中阅览效果:

http://localhost:8080/hello

如果此时你想打包项目,用命令行的方式运行,那么需要配置如下插件:

//如果想以java -jar xxx.jar方式运行jar工程,需要添加如下插件
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                  <executions>
                    <execution>
                      <goals>
                        <goal>repackage</goal>
                      </goals>
                    </execution>
                  </executions>
            </plugin>    
            
        </plugins>
    </build>

SpringBoot1.5.x版本回忆录之配置文件

配置文件和文件值注入

springboot的配置文件名默认是固定的,放在resources目录下:

application.properties或者application.yml

先来看下application.properties的用法

假如我的目标是让第一个案例的项目以端口8085启动,那么我们可以在文件中添加如下配置:

server.port=8085

再次访问该案例的服务就需要将端口修改为8085

http://localhost:8085/hello

其它可配置的参数请 参考文档 Part X. Appendices部分内容

上述案例我们体验了在 application.properties 修改配置参数来配置系统环境。那接下来我们在体验一下将application.properties配置注入到 Bean 对象中。

假如我们有如下Person对象:

/**
 * @Component将Person注入到spring环境中
 * @ConfigurationProperties指定配置文件中的那部分属性注入到该对象中,prefix表示注入以person开头的属性
 * @author wuxf2
 *
 */
@Component
@ConfigurationProperties(prefix="person")
public class Person {
    
    private String name;

    private Integer age;
    
    private Boolean bool;
    
    private List<Object> lists;
    
    private String[] arr;

    private Friend friend;

    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 Boolean getBool() {
        return bool;
    }

    public void setBool(Boolean bool) {
        this.bool = bool;
    }

    public List<Object> getLists() {
        return lists;
    }

    public void setLists(List<Object> lists) {
        this.lists = lists;
    }

    public String[] getArr() {
        return arr;
    }

    public void setArr(String[] arr) {
        this.arr = arr;
    }

    public Friend getFriend() {
        return friend;
    }

    public void setFriend(Friend friend) {
        this.friend = friend;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", bool=" + bool + ", lists=" + lists + ", arr="
                + Arrays.toString(arr) + ", friend=" + friend + "]";
    } 
    
}

作为 Person 对象属性的 Friend 对象:

public class Friend {

    private String name;

    private Integer age;

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

    @Override
    public String toString() {
        return "Friend [name=" + name + ", age=" + age + "]";
    }

    
}

我们想为上述对象注入application.properties文件中的属性,我们就可以这样写:

server.port=8085  
  
person.name=wuxiaofeng33333333
person.age=28
person.bool=true
person.lists=list1,list2,list3
person.arr=arr1,arr2,arr3
person.friend.name=wangcai
person.friend.age=19

在再HelloControlle文件中添加访问该person的 URL

@RestController
public class HelloControlle {
    
    @Autowired
    Person person;
    
    @RequestMapping("/person")
    public String hello(){
        return person.toString();
    }
    
}

最终的效果如下:

//浏览器输入
http://localhost:8085/person

//浏览器的访问结果
Person [name=wuxiaofeng33333333, age=28, bool=true, lists=[list1, list2, list3], arr=[arr1, arr2, arr3], friend=Friend [name=wangcai, age=19]]

另一个配置文件application.yml

yml 文件基本语法:

  1. k:(空格)v:表示一对键值对(空格必须有)
  2. 以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级的
  3. 属性和值大小写敏感

值的写法:

1、字面量:普通的值(数字,字符串,布尔)

k: v:字面直接来写;

字符串默认不用加上单引号或者双引号;

"":加了双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思,例如:
name: "zhangsan /n lisi"
//输出;zhangsan 换行 lisi

'':单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据,例如:
name: ‘zhangsan /n lisi’
//输出;zhangsan /n lisi

2、对象、Map:属性和值,键值对

k: v:在下一行来写对象的属性和值的关系;

注意缩进对象还是k: v的方式

friend:
    name: wangcai
    age: 19
    
//行内写法
friend: {name: wangcai,age: 19}

3、数组(List、Set):

用- 值表示数组中的一个元素;

arr:
    - arr1
    - arr2
    - arr3
 
//行内写法
arr: [arr1, arr2, arr3]

综合案例,上述案例 application.properties 配置文件注入bean换成 ym l文件的写法就是:

server:
  port: 8084  
  
person:
  name: wuxiaofeng2
  age: 28
  bool: true
  lists: 
    - list1
    - list2
    - list3
  arr:
    - arr1
    - arr2
    - arr3
  friend:
    name: wangcai
    age: 19

@Value字段注入

1、注入普通字符串

@Value("wangcai2")
private String name;

@Value注解用在成员变量name上,表明当前注入name的值为"wangcai2"

2、注入表达式

@Value("#{18 + 12}")
private Integer age;

@Value("#{1 == 1}")
private Boolean bool;

双引号中需要用到#{},可以进行加减法运算,也可以进行逻辑运算。

3、注入配置文件

@Value("${propertiesConfigValue}")
private String propertiesConfigValue;

双引号中为$符号而非#符号,{}中为配置文件中的 key

上面的说明可能比较抽象,下面我们就来具体看一个案例:我们把一个用@Value各种方式配置的bean对象返回到浏览器。

创建要返回到浏览器的ValuePropertiesConfig对象

/**
 * @Configuration作为配置文件
 * @PropertySource指定其它位置的配置文件,encoding标识编码方式,以防乱码
 */
@Component
@Configuration
@PropertySource(value="classpath:value.properties", encoding = "UTF-8")
public class ValuePropertiesConfig {
    
    @Value("wangcai2")
    private String name;

    @Value("#{18 + 12}")
    private Integer age;
    
    @Value("#{1 == 1}")
    private Boolean bool;
    
    @Value("${propertiesConfigValue}")
    private String propertiesConfigValue;

    @Override
    public String toString() {
        return "ValuePropertiesConfig [name=" + name + ", age=" + age + ", bool=" + bool + ", propertiesConfigValue="
                + propertiesConfigValue + "]";
    }    
    
}

value.properties配置文件内容

propertiesConfigValue="This come from Properties Config"

访问控制器代码

@RestController
public class HelloControlle {
    
    @Autowired
    ValuePropertiesConfig valuePropertiesConfig;
    
    @RequestMapping("/valuePropertiesConfig")
    public String getValuePropertiesConfig(){
        return valuePropertiesConfig.toString();
    }
}

浏览器访问@Value配置的bean对象数据

http://localhost:8086/valuePropertiesConfig
//结果为
ValuePropertiesConfig [name=wangcai2, age=30, bool=true, propertiesConfigValue="This come from Properties Config"]

如果觉得每次用浏览器访问@Value配置的bean对象数据很麻烦,我们可以改为用 控制台 输出数据:

public static void main(String[] args) {
        // Spring应用启动起来
        //SpringApplication.run(SpringBootStudy.class,args);
        
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ValuePropertiesConfig.class);

        ValuePropertiesConfig service = context.getBean(ValuePropertiesConfig.class);
        
        System.out.println(service);
        
        context.close();
    }

Scope配置

Scope属性用于定义bean在容器中初始化的次数,singleton表示定义的bean为单例模式,prototype则适合多线程模式。

1)singleton场景模拟:

我们来新建一个Dog类,内容如下:

public class Dog {
    
    private String name;

    public String getName() {
        return name;
    }

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

}

再来创建一个使用Dog类的singleton配置对象

/**
 * @Configuration标识当前类是一个配置类,相当于spring的一个xml配置文件
 * @Bean用在getDogSingleton方法上,表明当前方法返回一个Bean对象(Dog),然后将其交给 Spring 管理
 * @Scope("singleton")可以完全省略,默认为singleton模式
 *
 */
@Configuration
public class SingletonConfig {
    
    @Bean
    @Scope("singleton")
    public Dog getDogSingleton() {
        return new Dog();
    }
}

最后我们来验证下结论:

public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SingletonConfig.class);

        Dog dog1 = context.getBean(Dog.class);
        Dog dog2 = context.getBean(Dog.class);
        
        System.out.println(dog1);
        System.out.println(dog2);
        dog1.setName("I am dog1");
        System.out.println(dog2.getName());
        context.close();
    }
//输出效果:
com.study.bean.Dog@120b0058
com.study.bean.Dog@120b0058
I am dog1

可以看到,dog1和dog2的地址是一样的,并且修改了dog1的name值,dog2也跟着改变了。

2)prototype场景模拟

与上述案例相比,我们需要做的是创建一个使用Dog类的prototype配置对象

@Configuration
public class PrototypeConfig {
    
    @Bean
    @Scope("prototype")
    public Dog getDogPrototype() {
        return new Dog();
    }
}

再来给变下验证结论的主函数:

public static void main(String[] args) {        
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PrototypeConfig.class);

        Dog dog1 = context.getBean(Dog.class);
        Dog dog2 = context.getBean(Dog.class);
        
        System.out.println(dog1);
        System.out.println(dog2);
        dog1.setName("I am dog1");
        System.out.println(dog2.getName());
        context.close();
    }
//输出效果:
com.study.bean.Dog@120b0058
com.study.bean.Dog@10439aa9
null

可以看到,dog1和dog2的地址不一样,并且改变dog1的name值并不会影响dog2。

为项目创建不同的环境变量

1、在配置文件 application.yml 中区分环境变量

spring:
  profiles: 
    active: dev

---
server:
  port: 8086 
  
person:
  name: wuxiaofeng
  age: 28  
  
spring:
  profiles: dev 
---
server:
  port: 8088 
  
person:
  name: "wuxiaofeng-prod"
  age: 39 
  
spring:
  profiles: prod

application.yml文件中通过 active 表示使用哪种环境,不同环境之间通过---进行分割,并且通过 profiles 标识不同的环境名称。可以通过前面从配置文件中获取数据的方式进行验证,这里案例略。

2、在配置文件 application.properties 中区分环境变量

使用application.properties方式区分环境变量需要配置多个properties配置文件,形如 application-${profile}.properties ,然后在 application.properties 中指定调用的环境变量为 ${profile }即可,来看个案例:

//application.properties文件
spring.profiles.active=prod
//application-prod.properties文件
server.port=8088   
person.name="test-prod"
person.age=39 
  
spring.profiles=prod
//application-dev.properties文件
server.port=8086   
person.name=wuxiaofeng3333
person.age=28  
  
spring.profiles=dev

我们创建了多个配置文件。通过 spring.profiles.active=prod 标识我们调用 application-prod.properties 配置文件。

日志框架

SpringBoot1.5.x版本回忆录之配置文件

slf4j是一个日志框架的接口, log4jLogback 都实现了该接口(log4j需要借助slf4j-log4j12.jar适配)。logback在概念上与log4j非常相似,因为这两个项目都是由同一个开发人员创建的。如果您已经熟悉log4j,那么使用logback您会很快感到宾至如归。如果你喜欢log4j,你可能会喜欢logback。与log4j相比,Logback带来了许多大大小小的改进。详情改进太多,具体可以参考 官方文档

使用方式:只需要在resources下创建logback-spring.xml文件进行配置即可(springboot官方推荐优先使用带有-spring的文件名作为定义的日志配置,这样可以为它添加一些Spring Boot特有的配置项)

开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;给系统里面导入slf4j的jar和 logback的实现jar

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

日志的默认格式输出如下:

2014-03-05 10:57:51.112  INFO 45469 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.52
2014-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2014-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1358 ms
2014-03-05 10:57:51.698  INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2014-03-05 10:57:51.702  INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]

上述输出的日志信息,从左往右含义解释如下:

日期时间:精确到毫秒

日志级别: ERROR,WARN,INFO,DEBUG or TRACE

进程:id

分割符:用于区分实际日志消息的开头

线程名:括在方括号中(可以为控制台输出截断)

日志名字:通常是源类名

日志信息

看一个企业级的日志配置

首先看下 application.properties 的配置:

//指定操作系统的路径,其它配置项logging.file等可以在logback-spring.xml指定更合适
// windows操作系统
logging.path=D://mars//springBootStudy001//test//log
// linux操作系统
logging.path=/usr/local/wss_management_service/var/log

再来看下 logback-spring.xml 配置:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

     <appender name="consoleApp" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </layout>
    </appender>

    <appender name="fileInfoApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
             <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <encoder>
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </encoder>
        <!-- 滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 路径 -->
            <fileNamePattern>${LOG_PATH}/app.info.%d.log</fileNamePattern>
        </rollingPolicy>
    </appender>

    <appender name="fileErrorApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </encoder>
        
        <!-- 设置滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 路径 -->
            <fileNamePattern>${LOG_PATH}/app.err.%d.log</fileNamePattern>
            
            <!-- 控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动,且<maxHistory> 是1,则只保存最近1个月的文件,删除之前的旧文件 -->
            <!--  <MaxHistory>1</MaxHistory> -->
            
        </rollingPolicy>
    </appender>
   <root level="INFO">
        <appender-ref ref="consoleApp"/>
        <appender-ref ref="fileInfoApp"/>
        <appender-ref ref="fileErrorApp"/>
    </root>
</configuration>

上述配置中:

  1. ${LOG_PATH} 获取的就是 application.propertie s配置文件中的 logging.path 属性值
  2. 日志级别默认设置的是 INFO ,可以将 level 设置成 debug 来更改日志级别(日志太多,不建议)
  3. 其它配置项详情见 logback官方文档

结束语

这期先回顾一下springboot中常用的配置,下期我们继续回顾 springboot-web 用法。

SpringBoot1.5.x版本回忆录之配置文件

原文  https://segmentfault.com/a/1190000022154196
正文到此结束
Loading...