先说结论
对于不使用 Spring Cloud Context 的 Spring Cloud 程序,最好把 Spring Cloud Context 禁用掉,因为如果不禁用的话会导致某些配置加载出错。禁用方法:
在 SpringBoot 主函数加上 System.setProperty("spring.cloud.bootstrap.enabled", "false");
这句代码,示例如下:
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("cn.xgq.operator.impl.mapper")
public class OperatorImplApplication {
public static void main(String[] args) {
// 禁用 Spring Cloud Context,要不然会导致 logback-spring.xml会被加载两次
// Spring Cloud Context 详情:https://cloud.spring.io/spring-cloud-commons/multi/multi__spring_cloud_context_application_context_services.html
System.setProperty("spring.cloud.bootstrap.enabled", "false");
SpringApplication.run(OperatorImplApplication.class, args);
}
}
问题复现
logback-spring.xml 配置 contextName 使用 application.yml 里面配置的 spring.application.name,log 文件路径使用了 logging.file.path,然后启动应用程序之后报错:
Failed to rename context [projectName_IS_UNDEFINED] as [operator] java.lang.IllegalStateException: Context has been already given a name
Exception in thread "main" java.lang.IllegalStateException: Logback configuration error detected:
ERROR in ch.qos.logback.classic.joran.action.ContextNameAction - Failed to rename context [projectName_IS_UNDEFINED] as [operator] java.lang.IllegalStateException: Context has been already given a name
at org.springframework.boot.logging.logback.LogbackLoggingSystem.loadConfiguration(LogbackLoggingSystem.java:169)
at org.springframework.boot.logging.AbstractLoggingSystem.initializeWithConventions(AbstractLoggingSystem.java:80)
at org.springframework.boot.logging.AbstractLoggingSystem.initialize(AbstractLoggingSystem.java:60)
at org.springframework.boot.logging.logback.LogbackLoggingSystem.initialize(LogbackLoggingSystem.java:118)
at org.springframework.boot.context.logging.LoggingApplicationListener.initializeSystem(LoggingApplicationListener.java:313)
at org.springframework.boot.context.logging.LoggingApplicationListener.initialize(LoggingApplicationListener.java:288)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEnvironmentPreparedEvent(LoggingApplicationListener.java:246)
at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:223)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:76)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:53)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:345)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
at cn.xgq.operator.OperatorImplApplication.main(OperatorImplApplication.java:17)
logback-spring.xml 部分配置如下:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- 属性文件:在application.yml文件中找到对应的配置项 -->
<springProperty scope="context" name="filePath" source="logging.file.path"/>
<springProperty scope="context" name="projectName" source="spring.application.name"/>
<contextName>${projectName}</contextName>
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<!-- appender 配置 -->
</appender>
<!--根据日志级别分离日志,分别输出到不同的文件-->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- appender 配置 -->
</appender>
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- appender 配置 -->
</appender>
<root level="info">
<appender-ref ref="consoleLog"/>
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
</root>
</configuration>
application.yml 部分配置如下:
server:
port: 9001
spring:
application:
name: operator
datasource:
# 数据源配置
logging:
file:
path: ./logs
mybatis:
mapper-locations: classpath:mapper/*.xml
eureka:
client:
service-url: {defaultZone: http://localhost:9002/eureka/}
出现问题的原因
简单地说就是因为 Spring Cloud 程序有两个上下文,一个是“引导”上下文,负责从外部源加载配置属性和解密本地外部配置文件中的属性,从 bootstrap.yml 文件读取配置属性,另一个是主程序上下文,主要从application.yml 或者外部配置中心读取配置属性,然后这两个上下文都会读取 logback-spring.xml 的配置进行加载,但是由于本应用没有配置 bootstrap.yml,所以“引导”上下文加载 logback-spring.xml 的时候读取不到 projectName 和 filePath 变量的值,所以这两个值就会使用 projectName_IS_UNDEFINED 和 filePath_IS_UNDEFINED 作为值,然后等到主程序上下文去加载 logback-spring.xml 的时候因为这时候读取到的 contextName 和之前“引导”上下文加载出来的不一样,所以就报错了。
附:
Spring Cloud Context 官方说明
SpringCloud入门之应用程序上下文服务(Spring Cloud Context)详解