【问题标题】:Spring Boot: Multiple WAR deployment in same tomcat different propertiesSpring Boot:同一个tomcat不同属性中的多个WAR部署
【发布时间】:2020-09-28 08:34:49
【问题描述】:

所以我必须在同一个tomcat服务器中部署同一个springboot应用作为多个应用。 例如/app1/app2/app3

除了数据源配置之外,它们共享大部分相同的配置。

我一直在寻找一种基于servlet-context 或类似的东西来外部化数据源配置的方法。

使用 springs 外部化配置,我可以让它为 all 应用程序加载 相同 外部数据源文件,但它们需要不同。例如。

@PropertySource(value = "file:${HOME}/datasource-override.properties", ignoreResourceNotFound=false)

使用嵌入式tomcat模式,即通过.\gradlew bootRun我想我可以实现它。 我只需要使用以下内容作为该配置文件的 application.properties 设置 server.context-path 属性。 (因为这是一个单一的应用程序)例如。

@PropertySource(value = "file:${HOME}/${server.context-path}/datasource-override.properties", ignoreResourceNotFound=false),

四处搜索,我认为它可能类似于(和组合)以下内容,但它不起作用。例如

@PropertySource(value = "file:${HOME}/${server.servlet.context-path}/datasource-override.properties", ignoreResourceNotFound=false)

到目前为止,我发现的所有示例都处理嵌入式 tomcat 或单个应用程序的单个外部化属性文件。

理想情况下,我希望它在自己的目录中找到文件

file:${HOME}/${server.servlet.context-path}/datasource.properties

因此,对于这三个应用程序,它将类似于以下内容,它从部署的上下文中检测其属性文件的位置。例如:

file:${HOME}/app1/datasource.properties
file:${HOME}/app2/datasource.properties
file:${HOME}/app3/datasource.properties

显然,如果应用程序部署为/funky_chicken,那么它将有一个匹配的funky_chicken/datasource.properties

有什么想法吗?我知道我可能已经很接近了,而且我已经尝试过倾倒所有的环境属性。 (你可能会告诉我从JNDI 得到它,因为它是我唯一没有放弃寻找上下文的)

现在我知道${HOME} 不是存储配置项的最佳位置,它实际上只是为了更容易描述问题。

更新:

感谢使用配置文件的建议,有没有办法在一个 tomcat 服务器中拥有三个活动配置文件,/app1/app2/app3 来自同一个 WAR 文件?

【问题讨论】:

标签: spring spring-boot tomcat spring-properties


【解决方案1】:

为什么要部署在 tomcat 中? Springboot 应用程序可以单独工作。希望以下步骤对您有所帮助。

  1. 在 /resources 中添加 application.yml(application.properties 也可以)。在此文件中,您可以在此处配置常用设置。
  2. 然后您将名为 application-app1.yml 的文件也添加到 /resources 中的 application-app3.yml 中。在这些文件中,您可以配置不同的数据库设置。
  3. 启动您的应用程序:例如,我假设 app1 使用端口 10000,app2 使用端口 10001...

在maven之后,

app1: java -jar target/[***SNAPSHOT].jar --server.port=10000 --spring.profiles.active=app1

app2: java -jar target/[***SNAPSHOT].jar --server.port=10001 --spring.profiles.active=app2

app3: java -jar target/[***SNAPSHOT].jar --server.port=10002 --spring.profiles.active=app3

【讨论】:

    【解决方案2】:

    弹簧型材可以解决问题,不用@PropertySource

    对于应用程序 1,只需激活配置文件:spring.profiles.active=app1 - 这假设在类路径中您有 application-app1.properties 文件。同样适用于 app2、app3..appN。并且文件application.properies 将包含所有服务的通用属性

    【讨论】:

    • 我最初想到的是配置文件,它作为一个独立的部署是有意义的。但是在像 tomcat 这样的 servlet 容器中,如果我部署了 3 次相同的 WAR 文件,我找不到如何告诉每个应用程序使用哪个配置文件。即告诉app1使用profile.app1。
    【解决方案3】:

    我也有类似的要求。我的方法是将配置目录作为环境变量传递给 tomcat,假设你通过了

    spring.config.location=/home/user/config

    然后在 /home/user/config 文件夹下,您应该有与每个应用程序实例的 contextPath 匹配的文件。在你的情况下,你应该有

    • app1.properties
    • app2.properties
    • app3.properties

    如果您不想复制常用参数,可以将所有常用属性放在一个单独的文件中,并使用“spring.config.import”从每个应用程序特定的配置文件中导入它。请注意,自 Spring Boot 2.4 起支持导入另一个文件 See section "Importing Additional Configuration"

    为了让Spring Boot应用程序根据上下文路径加载属性文件,你应该重写“createRootApplicationContext”来获取上下文路径,并重写“configure”将其设置为属性文件名,如下所示。

    @SpringBootApplication
    public class TestApp extends SpringBootServletInitializer {
    
        private static Class<TestApp> applicationClass = TestApp.class;
        private String contextPath;
    
        public static void main(String[] args) {
            try {
                SpringApplication.run(applicationClass, args);
            }
            catch (Exception e) {
               // Handle error
            }
        }
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(applicationClass).properties("spring.config.name:" + contextPath);
        }
    
        @Override
        protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
            contextPath = servletContext.getContextPath();
            return super.createRootApplicationContext(servletContext);
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可以尝试 RoutingDataSource 方法,这可以让您在运行时/实时切换数据源。

      要实现这一点,您必须首先传递一些数据源标识符(您可以在您的身份验证令牌中设置它以用于基于休息的请求或在会话中)

      例如-

      • localhost:80/yourcontext/{app1}
      • localhost:80/yourcontext/{app2}
      • localhost:80/yourcontext/app3

      这里 app1、app2、app3 将是您的数据源标识符

      应用控制器

      @Controller
      public class AppController extends SuperController{
      @GetMapping("/{client}")
          public String login(ModelMap map, @PathVariable("client") String client, HttpServletRequest request){
      //Logic to set the path variable 
      return "loginPage";
      }
      
      }
      

      路由数据源配置

      @Configuration
          @EnableTransactionManagement
          @ComponentScan({ "com.components_package" })
          @PropertySource(value = { "classpath:database.properties" })
              public class RoutingDataSource extends AbstractRoutingDataSource {
                  @Override
                  protected Object determineCurrentLookupKey() {
                      String tenant = DbContextHolder.getDbType();
                      LoginDetails LoginDetails = currentUser();
                      if(LoginDetails != null){
                          tenant = LoginDetails.getTenantId();
                      }
                      logger.debug("tenant >>>> "+tenant);
                      return tenant;
                  }
                  
                  private LoginDetails currentUser() {
                      final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                      if (authentication instanceof UserAuthentication) {
                          return ((UserAuthentication) authentication).getDetails();
                      }
                      //If not authenticated return anonymous user
                      return null; 
                  }
              }
      

      休眠配置

          @PropertySource(value = { "classpath:database.properties" })
          public class HibernateConfiguration {
              @Autowired
              private Environment environment;
              
              @Bean
              @Primary
              public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
                    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
                    em.setDataSource(dynamicDataSource());
                    em.setPackagesToScan("com.entities_package");
                    JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
                    em.setJpaVendorAdapter(vendorAdapter);
                    em.setJpaProperties(hibernateProperties());
                    return em;
              }
              
              /** RoutingDataSource datasource initialized enabling the application
               *  to switch multiple databases at runtime
               * @return
               */
              private RoutingDataSource dynamicDataSource(){
                  RoutingDataSource routingDataSource = new RoutingDataSource();
                  /*Set default datasource*/
                  routingDataSource.setDefaultTargetDataSource(defaultDataSource());
                  /*Set all datasource map*/
                  routingDataSource.setTargetDataSources(fetchDatasourceMap());
                  routingDataSource.afterPropertiesSet();
                  return routingDataSource;
              }
              
              /** This is a default datasource
               * @return
               */
              private DataSource defaultDataSource() {
                  final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
                  DataSource dataSource = dsLookup.getDataSource("jdbc/defaultDs");
                  return dataSource;
              }
              
              /** This method sets all predefined client specific datasources in a map
               * @return Map<Object, Object>
               */
              private Map<Object, Object> fetchDatasourceMap(){
                  Map<Object, Object> dataSourcesMap = new HashMap<>();
      //database.clients=app1,app2,app3
                  String client = environment.getRequiredProperty("database.clients");
                  String[] allClients = client.split(",");
                  if(allClients != null && allClients.length > 0){
                      for (Integer i = 0; i < allClients.length; i++) {
                          String clientKey = allClients[i].trim();
                          dataSourcesMap.put(clientKey, dataSource(clientKey));
                      }
                  }
                  return dataSourcesMap;
              }
              
              private DataSource dataSource(String clientKey) {
                  final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
                  String lookupKey = "jdbc/"+clientKey;
                  DataSource dataSource = dsLookup.getDataSource(lookupKey);
                  return dataSource;
              }
              
              private Properties hibernateProperties() {
                  Properties properties = new Properties();
                  properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
                  properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
                  // properties.put("hibernate.format_sql",
                  // environment.getRequiredProperty("hibernate.format_sql"));
                  return properties;
              }
          
              @Bean
              JpaTransactionManager transactionManager() {
                  JpaTransactionManager transactionManager = new JpaTransactionManager();
                  transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
                  return transactionManager;
              }
          }
      

      您必须在 context.xml 中定义您的数据源属性以进行 JNDI 查找。

      希望对你有帮助

      【讨论】:

        猜你喜欢
        • 2015-03-10
        • 2018-12-05
        • 2018-05-18
        • 2016-08-11
        • 2020-06-17
        • 1970-01-01
        • 2016-02-15
        • 2023-01-03
        • 2020-10-23
        相关资源
        最近更新 更多