Java 测试、spring引导、配置路径所需的运行时参数

Java 测试、spring引导、配置路径所需的运行时参数,java,spring,spring-boot,filepath,Java,Spring,Spring Boot,Filepath,我用SpringBoot制作了一个小的RESTAPI,我正在从配置文件中读取某些内容。因为我不能硬编码配置文件的路径,因为它在生产中的位置发生了变化,所以我决定从运行时参数中获取它。我在实例化ConfigService时使用了路径。现在的问题是,我的所有测试都失败了,因为它需要实例化ConfigService,但在运行测试时它还没有到达main(获取路径的地方)。 我的主要观点如下: @SpringBootApplication public class SecurityService {

我用SpringBoot制作了一个小的RESTAPI,我正在从配置文件中读取某些内容。因为我不能硬编码配置文件的路径,因为它在生产中的位置发生了变化,所以我决定从运行时参数中获取它。我在实例化ConfigService时使用了路径。现在的问题是,我的所有测试都失败了,因为它需要实例化ConfigService,但在运行测试时它还没有到达main(获取路径的地方)。 我的主要观点如下:

@SpringBootApplication
public class SecurityService {

  private static final Logger LOG = LogManager.getLogger(SecurityService.class);

  private static String[] savedArgs;
  public static String[] getArgs(){
    return savedArgs;
  }

  public static void main(String[] args) throws IOException {

    savedArgs = args;
    final String configPath = savedArgs[0];
    // final String configPath = "src/config.xml";
    ConfigService configService = new ConfigService(configPath);

    if (configService.getConfigurations().getEnableHttps()) {
      LOG.info("Using HTTPS on port {}", configService.getConfigurations().getPort());
      configService.setSSL();
    }

    SpringApplication.run(SecurityService.class, args);
  }
}
@Configuration
@Service
public class ConfigService {

  private static final Logger LOG = LogManager.getLogger(ConfigService.class);
  private static String[] args = SecurityService.getArgs();
  private static final String CONFIG_PATH = args[0];
  private Configurations configurations;

  public ConfigService() {
    this(CONFIG_PATH);
  }

  public ConfigService(String configPath) {
    configurations = setConfig(configPath);
  }

  // Reads config and assigns values to an object, configurations
  private Configurations setConfig(String configPath) {
    Configurations configurations = new Configurations();
    try {
      ApplicationContext appContext =
        new ClassPathXmlApplicationContext("applicationContext.xml");
      XMLConverter converter = (XMLConverter) appContext.getBean("XMLConverter");
      configurations = (Configurations) converter.convertFromXMLToObject(configPath);

    } catch (IOException e) {
      e.printStackTrace();
    }
    LOG.info("Loaded settings from config.xml");
    return configurations;
  }

  // Checks if EnableHttps is true in config.xml and then sets profile to secure and sets SSL settings
  public void setSSL() {
    System.setProperty("spring.profiles.active", "Secure");
    System.setProperty("server.ssl.key-password", configurations.getKeyPass());
    System.setProperty("server.ssl.key-store", configurations.getKeyStorePath());
  }

  // Spring profiles
  // If EnableHttps is false it uses the Default profile, also sets the port before starting tomcat
  @Component
  @Profile({"Default"})
  public class CustomContainer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
    @Override
    public void customize(ConfigurableServletWebServerFactory container) {
      container.setPort(configurations.getPort());
    }
  }

  // If EnableHttps is True it will use the "Secure" profile, also sets the port before starting tomcat
  @Component
  @Profile({"Secure"})
  public class SecureCustomContainer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
    @Override
    public void customize(ConfigurableServletWebServerFactory container) {
      container.setPort(configurations.getPort());
    }
  }

  public Configurations getConfigurations() {
    return configurations;
  }
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class SecurityServiceTests {

  @Test
  public void contextLoads() {
  }

  @Test
  public void testGetPort() {
    String configPath = "src/config.xml";
    ConfigService configService = new ConfigService(configPath);
    int actualPort = configService.getConfigurations().getPort();
    int expectedPort = 8443;
    assertEquals(expectedPort, actualPort);
  }
  @Test
  public void testGetTTL(){
    String configPath = "src/config.xml";
    ConfigService configService = new ConfigService(configPath);
    int actualTTL = configService.getConfigurations().getTTL();
    int expectedTTL = 15000;
    assertEquals(expectedTTL, actualTTL);
  }
  @Test
  public void testSSL(){
    String configPath = "src/config.xml";
    ConfigService configService = new ConfigService(configPath);
    String expectedKeyPass = "changeit";
    String expectedKeyStore = "classpath:ssl-server.jks";
    configService.setSSL();
    assertEquals(expectedKeyPass,System.getProperty("server.ssl.key-password"));
    assertEquals(expectedKeyStore,System.getProperty("server.ssl.key-store"));
  }

}
我在启动Spring应用程序之前加载配置,因为我需要在服务器启动之前设置SSL设置等。现在,当它运行SpringApplication时,它再次实例化所有类,包括ConfigService。ConfigService如下所示:

@SpringBootApplication
public class SecurityService {

  private static final Logger LOG = LogManager.getLogger(SecurityService.class);

  private static String[] savedArgs;
  public static String[] getArgs(){
    return savedArgs;
  }

  public static void main(String[] args) throws IOException {

    savedArgs = args;
    final String configPath = savedArgs[0];
    // final String configPath = "src/config.xml";
    ConfigService configService = new ConfigService(configPath);

    if (configService.getConfigurations().getEnableHttps()) {
      LOG.info("Using HTTPS on port {}", configService.getConfigurations().getPort());
      configService.setSSL();
    }

    SpringApplication.run(SecurityService.class, args);
  }
}
@Configuration
@Service
public class ConfigService {

  private static final Logger LOG = LogManager.getLogger(ConfigService.class);
  private static String[] args = SecurityService.getArgs();
  private static final String CONFIG_PATH = args[0];
  private Configurations configurations;

  public ConfigService() {
    this(CONFIG_PATH);
  }

  public ConfigService(String configPath) {
    configurations = setConfig(configPath);
  }

  // Reads config and assigns values to an object, configurations
  private Configurations setConfig(String configPath) {
    Configurations configurations = new Configurations();
    try {
      ApplicationContext appContext =
        new ClassPathXmlApplicationContext("applicationContext.xml");
      XMLConverter converter = (XMLConverter) appContext.getBean("XMLConverter");
      configurations = (Configurations) converter.convertFromXMLToObject(configPath);

    } catch (IOException e) {
      e.printStackTrace();
    }
    LOG.info("Loaded settings from config.xml");
    return configurations;
  }

  // Checks if EnableHttps is true in config.xml and then sets profile to secure and sets SSL settings
  public void setSSL() {
    System.setProperty("spring.profiles.active", "Secure");
    System.setProperty("server.ssl.key-password", configurations.getKeyPass());
    System.setProperty("server.ssl.key-store", configurations.getKeyStorePath());
  }

  // Spring profiles
  // If EnableHttps is false it uses the Default profile, also sets the port before starting tomcat
  @Component
  @Profile({"Default"})
  public class CustomContainer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
    @Override
    public void customize(ConfigurableServletWebServerFactory container) {
      container.setPort(configurations.getPort());
    }
  }

  // If EnableHttps is True it will use the "Secure" profile, also sets the port before starting tomcat
  @Component
  @Profile({"Secure"})
  public class SecureCustomContainer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
    @Override
    public void customize(ConfigurableServletWebServerFactory container) {
      container.setPort(configurations.getPort());
    }
  }

  public Configurations getConfigurations() {
    return configurations;
  }
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class SecurityServiceTests {

  @Test
  public void contextLoads() {
  }

  @Test
  public void testGetPort() {
    String configPath = "src/config.xml";
    ConfigService configService = new ConfigService(configPath);
    int actualPort = configService.getConfigurations().getPort();
    int expectedPort = 8443;
    assertEquals(expectedPort, actualPort);
  }
  @Test
  public void testGetTTL(){
    String configPath = "src/config.xml";
    ConfigService configService = new ConfigService(configPath);
    int actualTTL = configService.getConfigurations().getTTL();
    int expectedTTL = 15000;
    assertEquals(expectedTTL, actualTTL);
  }
  @Test
  public void testSSL(){
    String configPath = "src/config.xml";
    ConfigService configService = new ConfigService(configPath);
    String expectedKeyPass = "changeit";
    String expectedKeyStore = "classpath:ssl-server.jks";
    configService.setSSL();
    assertEquals(expectedKeyPass,System.getProperty("server.ssl.key-password"));
    assertEquals(expectedKeyStore,System.getProperty("server.ssl.key-store"));
  }

}

配置类别:

// Model class that we map config.xml to
@Component
public class Configurations {
  private int port;
  private boolean enableHttps;
  private String keyStorePath;
  private String keyPass;
  private int TokenTtlMillis;

  public int getPort() {
    return port;
  }

  public void setPort(int port) {
    this.port = port;
  }

  public boolean getEnableHttps() {
    return enableHttps;
  }

  public void setEnableHttps(boolean enableHttps) {
    this.enableHttps = enableHttps;
  }

  public String getKeyStorePath() {
    return keyStorePath;
  }

  public void setKeyStorePath(String keyStorePath) {
    this.keyStorePath = keyStorePath;
  }

  public String getKeyPass() {
    return keyPass;
  }

  public void setKeyPass(String keyPass) {
    this.keyPass = keyPass;
  }

  public int getTTL() {
    return TokenTtlMillis;
  }

  public void setTTL(int TTL) {
    this.TokenTtlMillis = TTL;
  }
}
以及映射到配置类的my config.xml:

// Model class that we map config.xml to
@Component
public class Configurations {
  private int port;
  private boolean enableHttps;
  private String keyStorePath;
  private String keyPass;
  private int TokenTtlMillis;

  public int getPort() {
    return port;
  }

  public void setPort(int port) {
    this.port = port;
  }

  public boolean getEnableHttps() {
    return enableHttps;
  }

  public void setEnableHttps(boolean enableHttps) {
    this.enableHttps = enableHttps;
  }

  public String getKeyStorePath() {
    return keyStorePath;
  }

  public void setKeyStorePath(String keyStorePath) {
    this.keyStorePath = keyStorePath;
  }

  public String getKeyPass() {
    return keyPass;
  }

  public void setKeyPass(String keyPass) {
    this.keyPass = keyPass;
  }

  public int getTTL() {
    return TokenTtlMillis;
  }

  public void setTTL(int TTL) {
    this.TokenTtlMillis = TTL;
  }
}

8443
真的
类路径:ssl-server.jks
换
15000

对于SSL属性,可以使用VM参数而不是命令行参数。 i、 e.-Dspring.profiles.active=安全


并且可以使用System.getProperty(“spring.profiles.active”)在程序中访问相同的bean。

因为我可以理解,您希望有不同bean(有SSL和没有SSL)的任务取决于属性(
例如启用.https

对于这种情况,您可以使用条件配线。例如:

@Component
@ConditionalOnProperty(value = "enable.https", havingValue = "false")
public class CustomContainer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

  @Value("${port}")
  private int port;

  @Override
  public void customize(ConfigurableServletWebServerFactory container) {
    container.setPort(port);
  }
}


@Component
@ConditionalOnProperty(value = "enable.https", havingValue = "true")
public class SecureCustomContainer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

   @Value("${port}")
   private int port;    

   @Value("${ssl.key.store}")
   private String sslKeyStore;
   .....

   @Override
   public void customize(ConfigurableServletWebServerFactory container) {
      container.setPort(port);
       container.setSsl(... configure ssl here...) 
  }
}
@组件
@ConditionalOnProperty(value=“enable.https”,havingValue=“false”)
公共类CustomContainer实现WebServerFactoryCustomizer{
@值(“${port}”)
专用int端口;
@凌驾
public void自定义(ConfigurableServletWebServerFactory容器){
集装箱。设置港口(港口);
}
}
@组成部分
@ConditionalOnProperty(value=“enable.https”,havingValue=“true”)
公共类SecureCustomContainer实现WebServerFactoryCustomizer{
@值(“${port}”)
专用int端口;
@值(${ssl.key.store}”)
私有字符串sslKeyStore;
.....
@凌驾
public void自定义(ConfigurableServletWebServerFactory容器){
集装箱。设置港口(港口);
container.setSsl(…在此处配置ssl…)
}
}

我对你的动机和目标不是很清楚(你也没有提供),但我想向你展示,我认为是一种合适的春天方式

我将从最后开始,让你们先展示成果,然后展示如何实现这一目标

如果您希望有基于参数的so逻辑,让弹簧为您注入参数的值

package com.example.args;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;

@Configuration
@Service
public class ConfigService implements InitializingBean {

    private Configurations configurations;

    @Value("${configPath:defaultPath}") // "defaultPath" is used if not specified as arg from command line
    private String configPath;

    // you can use also @PostConstruct and not interface, up to you
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("configPath: " + configPath);
        configurations = setConfig(configPath); // you original setConfig
    }

}
现在,正如您可能看到的,您将args传递给
SpringApplication.run(SecurityService.class,args),所以Spring可以使用它,在SecurityService中,您不需要代码,只需这么简单

package com.example.args;

import java.util.Set;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SecurityService 
        implements ApplicationRunner { // not really needed, just for logging in run

    public static void main(String[] args) {
        SpringApplication.run(SecurityService.class, args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // for logging only
        System.out.println("NonOptionArgs: " + args.getNonOptionArgs());
        Set<String> optionNames = args.getOptionNames();
        System.out.println("OptionArgs: " + optionNames);
        for (String optionName : optionNames) {
            System.out.println(optionName + ": " + args.getOptionValues(optionName));
        }
    }
}
如果我在评论或初始问题中遗漏了您的一些要求,请告诉我,我可以添加其他信息


如评论中所述。看起来,在春天开始之前,你需要以非标准的方式阅读属性。我没有测试,但这应该是如何做到这一点的方向

public static void main(String[] args) {
    HashMap<String, Object> props = new HashMap<>();

    ConfigProperties cp = ... // some magic to load, cp is not Spring bean
    // server.port and server.ssl.enabled are properties Spring is aware of - will use it
    props.put("server.port", cp.getPort());
    props.put("server.ssl.enabled", cp.isSslEnabled()); // where you read properties from config.xml
    props.put("custom", cp.getCustom());

    ConfigurableApplicationContext context = new SpringApplicationBuilder()
        .sources(SecurityService.class)                
        .properties(props)
        .run(args);

    SecurityService ss = context.getBean(SecurityService.class);
    // configuration is not Spring managed, so there is not @Autowired in SecurityService 
    ss.setConfiguration(configuration); // problem is, that while it is not Spring bean, you cannot reference it from multiple services
}

您必须使用预期的属性键。

您应该在测试之前运行的一种特殊方法中实例化configService。如果您在bean中使用/resources/application.properties和
Value
注释,一切都会简单得多。您不应该使用
new
…是的,在春天。不要使用
new
,而是使用
context.getBean
或类似工具,它必须由Spring管理…我添加了我的答案,请检查;-)这也可以,但不能解决我的问题。此外,config.xml必须保持原样,不使用属性,因此我使用解组。它不仅仅包括ssl设置。这在我的代码中起作用,它使用它应该使用的配置文件,这取决于是否启用HTTPS。因此,我确信您的代码可以工作,但这与我的问题并不相关,因为我没有在启动服务器之前实例化configService两次,一次是在主实例中,然后Spring在启动服务器时再次实例化,所以我可以将其自动连接到其他类中。正确,但是,如果您想基于值执行一些逻辑,则不能在构造函数中执行(尚未设置)。没什么,只是记录,你可以删除它。这可能会对你有所帮助,一旦你熟悉了一个概念,你就可以删除它。您需要所谓的选项参数,以便能够将其映射到
@Value
。当然,您有。在Java中,构造函数总是存在的(至少是一个没有参数的构造函数),但是Spring应该创建您的服务,而不是您<代码>新建
可以按您尝试的方式在技术上使用,但它破坏了框架,只会导致问题。你可以使用
new
来保存你的数据实例和其他非Spring的东西。最大的问题是,你以后是否需要所有这些属性,可能只需要这个标记时间,所以把这个传递给Spring,如我上面所示,其余的重新映射到正确的键(Spring期望的键),像
server.port
server.ssl.enabled
等等。。。(在我看来,此后您不再需要这些值)。这不是setSSL()在ConfigService中所做的吗?只有当enableHttps设置为true时,它才应该这样做,因为如果它设置了这3个属性,它将在https上启动。在customcontainers上设置了端口。为什么不在
SecurityService
中执行此操作?如果您想在tomcat启动之前使用ConfigService替换my
ConfigProperties
。是波乔。Spring不需要知道所有的类。我向您展示了如果您愿意,如何让其他组件保存您的一些属性,但我仍将更新代码。。。