栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【解决方案】SpringCloud框架下Logback.xml配置springProfile失效

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【解决方案】SpringCloud框架下Logback.xml配置springProfile失效

一个会者不难,难者不会的问题。

文章目录

1. 前言2. 问题复现3. 解决方案4. 原因分析

4.1 SC相关启动逻辑4.2 SpringBoot对logback的扩展4.3 logback的机制 4. 总结5. 参考

1. 前言

就一个实际工作中遇到的问题的解决方法,没啥可前言的,直接开整吧。重点是最后面的源码解析部分。

2. 问题复现

问题复现只需要两步:

    bootstrap.yml(作为SpringCloud框架下会有一个优先级高于application.yml的默认配置文件)

    spring:
      profiles:
        active: ${PROFILE:dev}
    

    logback-spring.xml配置如下:

    
    
    
      ...... 省略
    
      
        
        
          
          
          
        
    
        
        
      
    
      ...... 省略
    
    
    
    

现在的问题表现就是:

    如果你将profile的设置放在bootstrap.yml中,那么在上述logback-spring.xml配置下,你是看不到任何控制台日志输出的 —— 这逻辑不对啊,但这控制台杂一点反应都没有呢?当然你要说我去除掉logback-spring.xml中的配置,那确实是可以的。
3. 解决方案

将以上bootstrap.yml中关于profile的配置,挪到application.yml文件中(如果没有该文件就新建一个)。

4. 原因分析

这才是本文存在的意义。我们需要涉及以下三方面的知识:

    SpringCloud的基本启动逻辑。SpringBoot对于logback框架的扩展。也就是对标签的支持。logback自身提供的logback.xml配置文件解析扩展。
4.1 SC相关启动逻辑

经过如下图的堆栈追踪,我们找到SpringCloud启动阶段的一个关键类BootstrapApplicationListener。

这个BootstrapApplicationListener类有如下特点:

    该类实现了ApplicationListener接口,监听了SpringBoot启动阶段的ApplicationEnvironmentPreparedEvent事件。我们会在接下来的部分对这一实现进行更详细的解读。该类注册在spring-cloud-context-x.y.z.RELEASE.jar的配置文件meta-INF/spring.factories中。关于这一配置文件,在 SpringBoot源码解析之AutoConfiguration 中有过详细的说明。

接下来让我们看看BootstrapApplicationListener类中的主要逻辑,也就是对ApplicationListener接口的实现:

// BootstrapApplicationListener.java
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
	// 读取配置, 判断是否显式设置不需要启动
	// 注意: 这个 environment  所指向的是初始启动的SpringContext对应的Environment
	ConfigurableEnvironment environment = event.getEnvironment();
	if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
			true)) {
		return;
	}
	// don't listen to events in a bootstrap context
	if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
		return;
	}

	ConfigurableApplicationContext context = null;
	// configName默认情况下就是bootstrap, 这也就是我们熟悉的 bootstrap.yaml的由来
	String configName = environment
			.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
	for (ApplicationContextInitializer initializer : event.getSpringApplication()
			.getInitializers()) {
		if (initializer instanceof ParentContextApplicationContextInitializer) {
			context = findBootstrapContext(
					(ParentContextApplicationContextInitializer) initializer,
					configName);
		}
	}
	if (context == null) {
		// 这里注意:
		// bootstrapServiceContext()方法里做了非常重要的两件事:
		// 	1. 正如上面堆栈所显示的, 其内部会再进行一次SpringApplicationBuilder.run(), 最终创建出一个新的ApplicationContext.
		// 	2. 它会将初始启动时创建的SpringContext, 与上一步创建的SpringContext之间建立一个父子关系: 初始化创建的SpringContext为子, 而由bootstrapServiceContext()方法内部创建的SpringContext为父, 这一点一定要注意甄别, 不要搞反. 
		// 3. 两个SpringContext父子关系的建立是在BootstrapApplicationListener.AncestorInitializer中完成的。另外关于这一点的验证你可以随便在自己的SpringCloud项目下创建一个EventListener, 输出`contextRefreshedEvent.getApplicationContext().getParent().getId()` 看返回值是不是`bootstrap`. 
		// 4. 作为返回值的context, 正是bootstrapServiceContext()所创建的 parent SpringContext.
		context = bootstrapServiceContext(environment, event.getSpringApplication(),
				configName);
		event.getSpringApplication()
				.addListeners(new CloseContextOnFailureApplicationListener(context));
	}
	
	// 将bootstrapServiceContext()中启动的SpringContext父容器里的ApplicationContextInitializer实现类, 复制到SpringContext子容器(也就是咱们熟悉的@SpringBootApplication启动的容器)中.
	apply(context, event.getSpringApplication(), environment);
}

以上启动逻辑之下,与咱们本文里的问题有啥关系呢?

    正是因为SpringCloud之下的两次容器初始化启动逻辑,所以其实logback是初始化了两次的。而在BootstrapApplicationListener里启动的那次是可以读取到bootstrap.yml文件里的配置,所以logback-spring.xml里的配置是生效了的,这就解释了为什么启动阶段并不是所有的日志都没打印。但作为我们使用@SpringBootApplication启动的容器,在logback框架初始化阶段,因为无法读取到bootstrap.yml里的配置,所以springProfile失效(因为在Environment中没有读取到spring.profiles.active的值)。这里要补充两点:
    a. 所谓"在logback框架初始化阶段",更准确的说应该是SpringBoot对logback的扩展SpringProfileAction类中。更具体的我们放在下面的小节中。
    b. 至于说到的"无法读取到bootstrap.yml里的配置,所以springProfile失效"原因,这个应该是和SpringBoot中生命周期阶段处理有关,笔者并未再作进一步的研究。不过需要专门提醒的是:在@SpringBootApplication启动时,bootstrap.yml中的配置是可以读取到的。因此这里面所谓的"无法读取到bootstrap.yml里的配置"当下只适用于logback框架的初始化过程中。
4.2 SpringBoot对logback的扩展

关于SpringBoot对于logback的扩展,这里咱们就不作全面的展开,只列举与本文相关的一些逻辑。

首先是相关的类型:

    SpringBootJoranConfiguratorSpringProfileAction

相关的总结如下:

    以上两个类均位于spring-boot-2.x.x.RELEASE.jar中,且均为内部类。所以想要使用IDE智能提示找到这两个类的,建议搜LogbackLoggingSystem。SpringBootJoranConfigurator通过扩展logback解析xml的配置类JoranConfigurator,添加了对于xml节点的解析逻辑。其中核心关键是在
	@Override
	public void addInstanceRules(RuleStore rs) {
		super.addInstanceRules(rs);
		Environment environment = this.initializationContext.getEnvironment();
		rs.addRule(new ElementSelector("configuration/springProperty"), new SpringPropertyAction(environment));
		// 解析节点
		rs.addRule(new ElementSelector("*/springProfile"), new SpringProfileAction(environment));
		// 不解析下的子元素
		rs.addRule(new ElementSelector("*/springProfilespringProfile/*"), new NOPAction());  也就是不解析, 自然也就不会生效了
	// 但有了下面这两行, 解析工作将转而由 RootLoggerAction 负责.
	this.events.remove(0);
	this.events.remove(this.events.size() - 1);
	interpreter.getEventPlayer().addEventsDynamically(this.events, 1);
}
4.3 logback的机制

对于本文而言,关于logback的应该就是对于其如何实现XML解析的原理解读了。

    logback中,对于XML解析,其和 commons-digester 非常类似。这一点可以在其源码中的Interpreter类上的注释可见一斑。logback中对于logback.xml配置的解析,各个节点所对应的解析类,它们之间的关系维护是在 JoranConfigurator类中完成的。
4. 总结

必须得来一个的话,大概就是:老老实实跟着官方最佳实践来,别图省事踩一堆坑又回到官方最佳实践上来,那不叫"摸石头过河"。

放到本例中就是这个项目一直以来只有一个bootstrap.yml这一个文件,图省事压根没有创建过诸如application.yml配置文件。

5. 参考
    SpringBoot源码分析之LOG
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/781317.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号