我用Spring-boot做了一个小的休息api,我正在从配置文件中读取某些东西。由于我无法硬编码配置文件的路径,因为它改变了生产中的位置,我决定从运行时参数中获取它。我在实例化我的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);
}
}
我在启动Spring应用程序之前加载配置,因为我需要在服务器启动之前设置SSL设置等。现在,当它运行SpringApplication时,它会再次实例化所有类,包括ConfigService。 ConfigService如下所示:
@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;
}
}
当尝试作为JAR文件运行时,它会吐出一堆nullpointerexceptions,因为args [0]为null,因为它实际上还没有获得参数。
我可以以某种方式解决这个问题吗?比如首先给它路径src / config.xml,然后在它实际启动时将它覆盖到运行时args路径?
我的一个测试类看起来像这样:
@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;
}
}
我的config.xml映射到配置类:
<?xml version="1.0" encoding="UTF-8"?>
<Configurations>
<Port>8443</Port>
<EnableHttps>true</EnableHttps>
<KeyStorePath>classpath:ssl-server.jks</KeyStorePath>
<KeyPass>changeit</KeyPass>
<TokenTtlMillis>15000</TokenTtlMillis>
</Configurations>
答案 0 :(得分:1)
你的动机和目标对我来说不是很清楚(你也没有提供MCVE),但我想告诉你,我相信春天是正确的......
我将从最后开始,首先向您展示结果然后如何实现这一目标......
你想拥有基于参数的逻辑,让Spring为你注入参数的值
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));
}
}
}
您可以删除ApplicationRunner
的实现,只是为了能够覆盖run()
并且它仅用于记录...
最后,您必须以:
运行应用程序java -jar target\args-0.0.1-SNAPSHOT.jar --configPath=somePath
如果我从评论或初步问题中错过了您的一些要求,请告诉我,我可以添加其他信息。
如评论中所述。看来,你需要在Spring开始之前以非标准的方式阅读属性。我没有测试过,但这应该是如何做到的方向
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
}
如果你想在Configuration类中重用,你应该能够这样做,但让Spring以我为configPath显示的方式注入它
Configuration {
@Value("${custom}") String custom;
// holding value for serverPort is not needed in application, so it's not here
}
你必须使用预期的属性键。
答案 1 :(得分:0)
您可以使用VM参数而不是SSL属性的命令行参数。 即-Dspring.profiles.active =安全
并且可以使用System.getProperty(“spring.profiles.active”)在程序中访问它。
答案 2 :(得分:0)
我可以理解您希望拥有不同bean(使用SSL和不使用)的任务取决于属性(例如enable.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...)
}
}