Java 测试、spring引导、配置路径所需的运行时参数
我用SpringBoot制作了一个小的RESTAPI,我正在从配置文件中读取某些内容。因为我不能硬编码配置文件的路径,因为它在生产中的位置发生了变化,所以我决定从运行时参数中获取它。我在实例化ConfigService时使用了路径。现在的问题是,我的所有测试都失败了,因为它需要实例化ConfigService,但在运行测试时它还没有到达main(获取路径的地方)。 我的主要观点如下:Java 测试、spring引导、配置路径所需的运行时参数,java,spring,spring-boot,filepath,Java,Spring,Spring Boot,Filepath,我用SpringBoot制作了一个小的RESTAPI,我正在从配置文件中读取某些内容。因为我不能硬编码配置文件的路径,因为它在生产中的位置发生了变化,所以我决定从运行时参数中获取它。我在实例化ConfigService时使用了路径。现在的问题是,我的所有测试都失败了,因为它需要实例化ConfigService,但在运行测试时它还没有到达main(获取路径的地方)。 我的主要观点如下: @SpringBootApplication public class SecurityService {
@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替换myConfigProperties
。是波乔。Spring不需要知道所有的类。我向您展示了如果您愿意,如何让其他组件保存您的一些属性,但我仍将更新代码。。。