目录

环境与配置 Profile

应用配置

只针对没有接入统一配置中心

对于项目的框架或者组件,如果支持编程方式配置,则编程根据不同环境读取不同配置

Spring Boot Profile

如果使用Spring Boot,直接采用官方推荐的配置切换方式:设置spring.profiles.active,可以是jvm变量、main启动参数、环境变量等(建议使用jvm变量);

1
 -Dspring.profiles.active=dev

打包会包含所有配置,如果想过滤,可以结合 maven profile。

组件支持属性替换

如果使用 logback,在非Spring Boot环境下,可以把配置参数放在properties文件中,根据不同环境(建议根据jvm变量识别)读取对应properties;在Spring Boot环境下,可以使用logback的springProperty标签获取spring托管的参数;

Log4j2 属性替换

Log4j2 支持将配置中的令牌指定为对其他地方定义的属性的引用。当配置文件被解释时,其中一些属性将被解析,而其他属性可能被传递给组件,在运行时它们将被评估。为此,Log4j 使用了Apache Commons Lang的 StrSubstitutor和StrLookup类的变体。以类似于 Ant 或 Maven 的方式,这允许将变量声明为${name} 使用配置本身中声明的属性来解决。

https://logging.apache.org/log4j/2.x/manual/configuration.html

Log4j 还支持${prefix:name}前缀标识告诉 Log4j 应该在特定上下文中评估变量名称的语法。

Prefix Context
base64 Base64 encoded data. The format is ${base64:Base64_encoded_data}. For example: ${base64:SGVsbG8gV29ybGQhCg==} yields Hello World!.
bundle Resource bundle. The format is ${bundle:BundleName:BundleKey}. The bundle name follows package naming conventions, for example: ${bundle:com.domain.Messages:MyKey}.
ctx Thread Context Map (MDC)
date Inserts the current date and/or time using the specified format
env System environment variables. The formats are ${env:ENV_NAME} and ${env:ENV_NAME:-default_value}.
jndi A value set in the default JNDI Context. (Requires system property log4j2.enableJndiLookup to be set to true.)
jvmrunargs A JVM input argument accessed through JMX, but not a main argument; see RuntimeMXBean.getInputArguments(). Not available on Android.
log4j Log4j configuration properties. The expressions ${log4j:configLocation} and ${log4j:configParentLocation} respectively provide the absolute path to the log4j configuration file and its parent folder.
main A value set with [MapLookup.setMainArguments(String])
map A value from a MapMessage
sd A value from a StructuredDataMessage. The key “id” will return the name of the StructuredDataId without the enterprise number. The key “type” will return the message type. Other keys will retrieve individual elements from the Map.
sys System properties. The formats are ${sys:some.property} and ${sys:some.property:-default_value}.

系统环境变量。格式为${env:ENV_NAME}${env:ENV_NAME:-default_value}

sys ${sys:some.property} and ${sys:some.property:-default_value}.

-Dlog.base.dir=/tmp/log

打包工具

maven profile

gradle

通过动态加载不同环境资源文件实现

1
gradle -q -Penv=prod

gradle.properties

env=dev

直接将环境包目录下的文件打包到resources根目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apply plugin: 'java'   


sourceSets {    
    main {
        resources {
            srcDir "src/main/resources/${env}"
            
            sourceSets.main.resources.srcDirs.each   {      
                it.listFiles().each {
                     if(it.isDirectory()) {        
                        exclude "${it.name}"
                    }
                }                   
            }
        }
    }
} 

利用 ConfigSlurper 进行不同环境构建

借助Groovy的ConfigSlurper特性可以简洁而明快的达到多环境打包的目的.打包时候仅需通过-D参数传入目标环境变量即可如:gradle build -Denv=dev,这里可以通过添加gradle.properties文件设置默认的环境变量值

1
gradle build -Denv=dev

build.gradle平级建立config.groovy,这里的命名可以随意

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
environments {
    // 开发环境
    dev {
        db {
            username = "dev"
            password = 'devpwd'
        }        
    }
    // 线上环境
    production { 
        db {
            username = "prod"
            password = 'prodpwd'
        }        
    }
}

修改build.gradle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 引入ReplaceToken
import org.apache.tools.ant.filters.ReplaceTokens

processResources {
    println "==> Load configuration for $env"
    def config =  new ConfigSlurper(env).parse(file("config.groovy").toURI().toURL()).toProperties()
    
   from(sourceSets.main.resources.srcDirs) {
       // 只替换 properties
       include '**/*.properties'
        filter(ReplaceTokens, tokens: config, beginToken : '${', endToken : '}')
    }
    
}

编程方式自动读取配置

一些组件是支持以编程方式来配置的,例如 log4j。

如果使用Spring框架(但是非Spring Boot),环境相关的配置参数放在properties文件中,写个工具类,就可以根据不同环境(建议根据jvm变量识别)读取对应properties;

  • 结合maven profile
  • 通过环境变量、系统变量、VM 参数等拼接文件名
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class PropertiesUtil {
  public static final Logger log = LoggerFactory.getLogger(PropertiesUtil.class);

  public static final String PROPERTIES_FILE_NAME = "app.properties";

  private PropertiesUtil() {}

  public static Properties properties() {
    Properties properties = new Properties();
    try {
      properties.load(
          Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_FILE_NAME));
    } catch (IOException e) {
      log.error("IOException", e);
    }
    return properties;
  }
}
1
2
# 获取系统参数
(Map) System.getProperties();

通过 main 方法参数 获取

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
public class PropertiesUtil implements Serializable {
  public static final Logger log = LoggerFactory.getLogger(PropertiesUtil.class);
  private static final long serialVersionUID = 1L;

  protected static final String NO_VALUE_KEY = "__NO_VALUE_KEY";

  private final Map<String, String> data;

  private PropertiesUtil(Map<String, String> data) {
    this.data = data;
  }

  public PropertiesUtil(PropertiesUtil propertiesUtil) {
    this.data = propertiesUtil.toMap();
  }

  public static PropertiesUtil fromResourcePropertiesFile(String resourceFileName)
      throws IOException {
    return fromPropertiesFile(
        Thread.currentThread().getContextClassLoader().getResourceAsStream(resourceFileName));
  }

  public static PropertiesUtil fromSystemProperties() {
    return fromMap((Map) System.getProperties());
  }

  public static PropertiesUtil fromPropertiesFile(InputStream inputStream) throws IOException {
    Properties props = new Properties();
    props.load(inputStream);
    return fromMap((Map) props);
  }

  public PropertiesUtil mergeWith(PropertiesUtil other) {
    final Map<String, String> resultData = new HashMap<>(data.size() + other.data.size());
    resultData.putAll(data);
    resultData.putAll(other.data);

    return new PropertiesUtil(resultData);
  }

  public Properties getProperties() {
    Properties props = new Properties();
    props.putAll(this.data);
    return props;
  }

  public String get(String key) {
    return data.get(key);
  }

  public String get(String key, String defaultValue) {
    String value = get(key);
    if (value == null) {
      return defaultValue;
    } else {
      return value;
    }
  }

  public String getRequired(String key) {
    String value = get(key);
    if (value == null) {
      throw new RuntimeException("No data for required key '" + key + "'");
    }
    return value;
  }

  public int getInt(String key, int defaultValue) {
    String value = get(key);
    if (value == null) {
      return defaultValue;
    }
    return Integer.parseInt(value);
  }

  public boolean has(String value) {
    return data.containsKey(value);
  }

  public static PropertiesUtil fromArgs(String[] args) {
    final Map<String, String> map = new HashMap<>(args.length / 2);

    int i = 0;
    while (i < args.length) {
      final String key = getKeyFromArgs(args, i);

      if (key.isEmpty()) {
        throw new IllegalArgumentException(
            "The input " + Arrays.toString(args) + " contains an empty argument");
      }

      i += 1; // try to find the value

      if (i >= args.length) {
        map.put(key, NO_VALUE_KEY);
      } else if (NumberUtils.isCreatable(args[i])) {
        map.put(key, args[i]);
        i += 1;
      } else if (args[i].startsWith("--") || args[i].startsWith("-")) {
        // the argument cannot be a negative number because we checked earlier
        // -> the next argument is a parameter name
        map.put(key, NO_VALUE_KEY);
      } else {
        map.put(key, args[i]);
        i += 1;
      }
    }

    return fromMap(map);
  }

  public static String getKeyFromArgs(String[] args, int index) {
    String key;
    if (args[index].startsWith("--")) {
      key = args[index].substring(2);
    } else if (args[index].startsWith("-")) {
      key = args[index].substring(1);
    } else {
      throw new IllegalArgumentException(
          String.format(
              "Error parsing arguments '%s' on '%s'. Please prefix keys with -- or -.",
              Arrays.toString(args), args[index]));
    }

    if (key.isEmpty()) {
      throw new IllegalArgumentException(
          "The input " + Arrays.toString(args) + " contains an empty argument");
    }

    return key;
  }

  public static PropertiesUtil fromMap(Map<String, String> map) {
    assert map != null : "Unable to initialize from empty map";
    return new PropertiesUtil(map);
  }

  public Map<String, String> toMap() {
    return data;
  }
}

CLI argunments

Environment variables

Linux的变量种类

  • 永久的:需要修改配置文件,变量永久生效。
  • 临时的:使用export命令声明即可,变量在关闭shell时失效。

设置变量的三种方法

在/etc/profile文件中添加变量【对所有用户生效(永久的)】

用户目录下的.bash_profile文件中增加变量,改变量仅会对当前用户有效,并且是“永久的”。

直接运行export命令定义变量【只对当前shell(BASH)有效(临时的)】

环境变量的查看 使用echo命令查看单个环境变量。例如: echo $PATH 使用env查看所有环境变量。例如: env 使用set查看所有本地定义的环境变量。 unset可以删除指定的环境变量。

常用的环境变量 PATH 决定了shell将到哪些目录中寻找命令或程序 HOME 当前用户主目录 HISTSIZE 历史记录数 LOGNAME 当前用户的登录名 HOSTNAME 指主机的名称 SHELL 当前用户Shell类型 LANGUGE  语言相关的环境变量,多语言可以修改此环境变量 MAIL 当前用户的邮件存放目录 PS1 基本提示符,对于root用户是#,对于普通用户是$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 显示环境变量
echo $HOME
# 显示所有环境变量
env
# 使用set命令显示所有本地定义的Shell变量
set
# 清除环境变量
unset TEST
# 设置一个新的环境变量
export HELLO="Hello!"
1
2
3
4
export TEST="Hi..." #增加一个环境变量TEST
env | grep TEST #此命令有输入,证明环境变量TEST已经存在了
unset TEST #删除环境变量TEST
env|grep TEST #此命令没有输出,证明环境变量TEST已经存在了
1
2
3
4
5
# 该变量将会对Linux下所有用户有效,并且是“永久的”
/etc/profile
# 修改文件后要想马上生效还要运行# 不然只能在下次重进此用户时生效。
source /etc/profile
# export path=$path:/path1:/path2:/pahtN

VM options (VM arguments)

IDEA配置JVM启动参数

TOMCAT JVM启动参数配置 :在catalina.bat或catalina.sh里面设置

非spring环境中获取bean对象

实现BeanFactoryPostProcessor接口

实现BeanFactoryPostProcessor接口,重写postProcessBeanFactory方法,然后获取到beanFactory工厂,可以通过它来获取bean对象。

实现ApplicationContextAware接口

实现ApplicationContextAware 接口,重写setApplicationContext方法,然后获取到applicationContext ,可以通过它来获取bean对象。

封装好的SpringContextUtils 就是 实现ApplicationContextAware接口