Spring boot 一个jar进程运行多个容器或者运行多个Application
Posted xlxxcc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring boot 一个jar进程运行多个容器或者运行多个Application相关的知识,希望对你有一定的参考价值。
背景:
有些时候, 由于机器原因或者是环境原因, 我们希望将微服务架构离散的可运行组件打包到一起运行. 也就是说 ,一个进程运中行多个容器或者运行多个Application.
由于组件间的调用都是rpc调用. 那么怎样在不动组件代码的情况下, 且保证进程中的多容器中的组件功能完全隔离呢?
apollo assembly:
我们看一下初始示例, 来源于携程配置中心 apollo assembly, 先看一下apollo的工程依赖:
assembly(装配)
只有一个类,就是一个main函数,同时启动了common、configservice、adminservice、portal组件。
buildtools(构建工具)
只有些脚本工具,和一些规范
demo
apollo的一些使用方法和示例
assembly 源码(改造后):
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
public class ApolloApplication {
private static final Logger logger = LoggerFactory.getLogger(ApolloApplication.class);
public static void main(String[] args) throws Exception {
/**
* Common
*/
ConfigurableApplicationContext commonContext =
new SpringApplicationBuilder(ApolloApplication.class).web(WebApplicationType.NONE).run(args);
logger.info(commonContext.getId() + " isActive: " + commonContext.isActive());
/**
* ConfigService
*/
if (commonContext.getEnvironment().containsProperty("configservice")) {
ConfigurableApplicationContext configContext =
new SpringApplicationBuilder(ConfigServiceApplication.class).parent(commonContext)
.sources(RefreshScope.class).run(args);
logger.info(configContext.getId() + " isActive: " + configContext.isActive());
}
/**
* AdminService
*/
if (commonContext.getEnvironment().containsProperty("adminservice")) {
ConfigurableApplicationContext adminContext =
new SpringApplicationBuilder(AdminServiceApplication.class).parent(commonContext)
.sources(RefreshScope.class).run(args);
logger.info(adminContext.getId() + " isActive: " + adminContext.isActive());
}
/**
* Portal
*/
ConfigurableEnvironment environment = commonContext.getEnvironment();
String name = "Config resource 'class path resource [application-github.properties]' via location 'optional:classpath:/'";
OriginTrackedMapPropertySource propertySource = (OriginTrackedMapPropertySource) environment.getPropertySources().get(name);
Map<String, Object> source = propertySource.getSource();
Map map = new HashMap();
map.putAll(source);
map.put("spring.datasource.url", "jdbc:mysql://127.0.0.1:3306/ApolloPortalDB?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true");
environment.getPropertySources().replace(name, new OriginTrackedMapPropertySource(name, map));
// 一些配置bean在父上下文 commonContext 就加载了, 如 DataSourceProperties 的配置, 死活在 portalContext 中更新profiles配置无效, 要启动 PortalApplication, 只能改bean的属性值,若希望通过设置配置改变值必须 publishEvent 刷新bean
commonContext.publishEvent(new EnvironmentChangeEvent(new HashSet<String>(){{add("spring.datasource.url");}}));
logger.info(commonContext.getBean(DataSourceProperties.class).getUrl());
// 一些配置bean在父上下文 commonContext 就加载了, 如 DataSourceProperties 的配置, 死活在 portalContext 中更新profiles配置无效, 要启动 PortalApplication, 只能改bean的属性值,
// commonContext.getBean(DataSourceProperties.class).setUrl("jdbc:mysql://127.0.0.1:3306/ApolloPortalDB?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true");
if (commonContext.getEnvironment().containsProperty("portal")) {
ConfigurableApplicationContext portalContext =
new SpringApplicationBuilder(PortalApplication.class).parent(commonContext).profiles("portal")
//.profiles("portal")
.sources(RefreshScope.class).run(args);
logger.info(portalContext.getId() + " isActive: " + portalContext.isActive());
}
}
}
注意: 代码改造前, portal 不能同时 和 configservice、adminservice运行. 原因是 portal 的数据源配置不能被正确加载.
运行:
springboot assembly:
基于 apollo assembly 示例, 验证配置的加载和优先级关系.
A1源码:
A1Application.java
package com.test.a1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
@EnableDiscoveryClient
@EnableAspectJAutoProxy
@Configuration
@PropertySource(value = {"classpath:a1.properties"})
@EnableAutoConfiguration
@ComponentScan(basePackageClasses = {A1Application.class})
public class A1Application {
public static void main(String[] args) {
SpringApplication.run(A1Application.class, args);
}
}
TestController.java
package com.test.a1;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
@Configuration
@RestController
@Slf4j
public class TestController {
@Value("${public.p1}")
private String publicA1;
@Value("${public.p1}")
private String publicA2;
@Value("${private.a1}")
private String privateA1;
@Value("${private.a2}")
private String privateA2;
@Value("${spring.application.name}")
private String appName;
@GetMapping("/a1")
public String a1() {
return "a1";
}
@Autowired
private ConfigurableApplicationContext configurableApplicationContext;
@PostConstruct
public void log() {
log.info("application-a1, publicA1: {}, publicA2: {}, privateA1: {}, privateA2: {}, appName: {}, configurableApplicationContext: {}",
publicA1, publicA2, privateA1, privateA2, appName, configurableApplicationContext);
}
}
A2源码:
A2Application.java
package com.test.a2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
@EnableDiscoveryClient
@EnableAspectJAutoProxy
@Configuration
@PropertySource(value = {"classpath:a2.properties"})
@EnableAutoConfiguration
@ComponentScan(basePackageClasses = {A2Application.class})
public class A2Application {
public static void main(String[] args) {
SpringApplication.run(A2Application.class, args);
}
}
TestController.java
package com.test.a2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@RestController
@Slf4j
public class TestController {
@Value("${public.p1}")
private String publicA1;
@Value("${public.p1}")
private String publicA2;
@Value("${private.a3:null}")
private String privateA3;
@Value("${private.a4}")
private String privateA4;
@Value("${spring.application.name}")
private String appName;
@GetMapping("/a2")
public String a1() {
return "a2";
}
@Autowired
private ConfigurableApplicationContext configurableApplicationContext;
@PostConstruct
public void log() {
log.info("application-a1, publicA1: {}, publicA2: {}, privateA3: {}, privateA4: {}, appName: {}, configurableApplicationContext: {}",
publicA1, publicA2, privateA3, privateA4, appName, configurableApplicationContext);
}
}
Assembly源码:
AssemblyApplication.java
package com.test.assembly;
import com.test.a1.A1Application;
import com.test.a2.A2Application;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
@Slf4j
@SpringBootApplication(scanBasePackages = "com.test.assembly")
public class AssemblyApplication {
public static void main(String[] args) {
System.setProperty("spring.cloud.service-registry.auto-registration.enabled", "false");
final ConfigurableApplicationContext commonContext =
new SpringApplicationBuilder(AssemblyApplication.class).web(WebApplicationType.NONE).run(args);
log.info(commonContext.getId() + " isActive: " + commonContext.isActive());
log.info(commonContext.getId() + " env: " + commonContext.getEnvironment().toString());
System.setProperty("spring.cloud.service-registry.auto-registration.enabled", "true");
// System.setProperty("spring.cloud.consul.discovery.instance-id", "${spring.application.name}-${spring.cloud.client.ip-address}-${server.port}");
// a1
if (commonContext.getEnvironment().containsProperty("a1")) {
final ConfigurableApplicationContext context =
new SpringApplicationBuilder(A1Application.class)
.parent(commonContext)
.properties("server.port=9060")
.properties("spring.application.name=a1-ms")
.properties("spring.cloud.consul.discovery.instance-id=a1")
// .properties("spring.cloud.consul.service-registry.auto-registration.enabled=true")
.sources(RefreshScope.class)
.run(args);
log.info(context.getId() + " isActive: " + context.isActive());
log.info(commonContext.getId() + " env: " + context.getEnvironment().toString());
}
// a2
if (commonContext.getEnvironment().containsProperty("a2")) {
final ConfigurableApplicationContext context =
new SpringApplicationBuilder(A2Application.class)
.parent(commonContext)
.properties("server.port=9070")
.properties("spring.application.name=a2-ms")
.properties("spring.cloud.consul.discovery.instance-id=a2")
.sources(RefreshScope.class)
.run(args);
log.info(context.getId() + " isActive: " + context.isActive());
log.info(commonContext.getId() + " env: " + context.getEnvironment().toString());
}
}
}
运行
配置和依赖图:
其中 application-additional.properties 是通过 SpringApplicationBuilder.profiles(“additional”) 动态加入的配置
总结:
- 1、同名配置文件先加载上层配置, 即 application.yaml 和 application-sit.properties 只会加载assembly的. 尽管每个子context都会重新初始化 Environment, 依然只能加载到上层同名配置文件.
- 2、同名配置项, profiles 定义的配置优先于默认的配置[即application.yaml] , 优先于 @PropertySource中配置
- 3、通过 SpringApplicationBuilder.properties() 设置的配置, 属于默认配置. 优先级低.
- 4、通过 SpringApplicationBuilder.profiles(“portal”)加载到配置, 优先级最高. 见apollo assembly源码改造.
- 5、此外 子context前, 还可以通过 System.setProperty() 设置配置.
- 6、一些配置bean在父 Context 就加载了, 如 DataSourceProperties 配置, 若希望通过设置配置改变值必须 publishEvent 刷新bean, 否则只能通过getBean()去改bean的属性值. 换句话说, 子context 装配bean时, 先到父 context 上找, 没有再注册bean. 因此 A1 和 A2模块注意定义不同的包路径, 在装配bean时, 通过扫描不同包路径隔离.
- @PropertySource的优先级最低
以上总结并非100%正确. 有兴趣的同学可以先阅读spring boot 启动源码, 并且自行测试. 本例子代码见: GITHUB
附: 配置优秀级顺序
附: 启动日志
以上是关于Spring boot 一个jar进程运行多个容器或者运行多个Application的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot 一个jar进程运行多个容器或者运行多个Application
Spring boot 一个jar进程运行多个容器或者运行多个Application
spring boot jar 进程自动停止,自动终止,不能后台持续运行