我有以下来自application.properties的属性的类映射部分:
@Component
@ConfigurationProperties(prefix = "city")
@Getter
@Setter
public class CityProperties {
private int populationAmountWorkshop;
private double productionInefficientFactor;
private Loaner loaner = new Loaner();
private Tax tax = new Tax();
private Guard pikeman = new Guard();
private Guard bowman = new Guard();
private Guard crossbowman = new Guard();
private Guard musketeer = new Guard();
@Getter
@Setter
public static class Loaner {
private int maxRequest;
private int maxAgeRequest;
private int maxNbLoans;
}
@Getter
@Setter
public static class Tax {
private double poor;
private double middle;
private double rich;
private int baseHeadTax;
private int basePropertyTax;
}
@Getter
@Setter
public static class Guard {
private int weeklySalary;
}
}
application.properties
的一部分:#City
# Amount of inhabitants to warrant the city to have one workshop
city.populationAmountWorkshop=2500
# Factor that is applied on the efficient production to get the inefficient production
city.productionInefficientFactor=0.6
# Maximum requests per loaner
city.loaner.maxRequest=6
# Maximum age of loan request in weeks
city.loaner.maxAgeRequest=4
# Maximum loan offers per loaner
city.loaner.maxNbLoans=3
# Weekly tax value factor for the various population per 100 citizens
city.tax.poor=0
city.tax.middle=0.6
city.tax.rich=2.0
city.tax.baseHeadTax=4
city.tax.basePropertyTax=280
city.pikeman.weeklySalary=3
city.bowman.weeklySalary=3
city.crossbowman.weeklySalary=4
city.musketeer.weeklySalary=6
这是测试设置的应用程序:
@SpringBootApplication
@Import({ServerTestConfiguration.class})
@ActiveProfiles("server")
@EnableConfigurationProperties
@PropertySource(value = {"application.properties", "server.properties", "bean-test.properties"})
public class SavegameTestApplication {
}
这些是
ServerTestConfiguration
类上的注释,所有其他导入的配置也与我在生产案例中使用的相同:@Configuration
@EnableAutoConfiguration
@Import(value = {ClientServerInterfaceServerConfiguration.class, ServerConfiguration.class, ImageConfiguration.class})
public class ServerTestConfiguration {
...
}
最后是我的测试类的构造函数,用于初始化
Spring-Boot
应用程序:public CityWallSerializationTest() {
SpringApplicationBuilder builder = new SpringApplicationBuilder(SavegameTestApplication.class);
DependentAnnotationConfigApplicationContext context = (DependentAnnotationConfigApplicationContext)
builder.contextClass(DependentAnnotationConfigApplicationContext.class).profiles("server").run();
setContext(context);
setClientServerEventBus((AsyncEventBus) context.getBean("clientServerEventBus"));
IConverterProvider converterProvider = context.getBean(IConverterProvider.class);
BuildProperties buildProperties = context.getBean(BuildProperties.class);
Archiver archiver = context.getBean(Archiver.class);
IDatabaseDumpAndRestore databaseService = context.getBean(IDatabaseDumpAndRestore.class);
TestableLoadAndSaveService loadAndSaveService = new TestableLoadAndSaveService(context, converterProvider,
buildProperties, archiver, databaseService);
setLoadAndSaveService(loadAndSaveService);
}
这在我的生产代码中可以正常工作,但是当我想使用Spring Boot应用程序编写一些测试时,这些值未初始化。
在构造函数的末尾打印出
CityProperties
会得到以下输出:CityProperties(populationAmountWorkshop = 0,productionInficientFactor = 0.0,借贷者= CityProperties.Loaner(maxRequest = 0,maxAgeRequest = 0,maxNbLoans = 0),tax = CityProperties.Tax(poor = 0.0,middle = 0.0,rich = 0.0,baseHeadTax = 0 ,basePropertyTax = 0),pikeman = CityProperties.Guard(weeklySalary = 0),bowman = CityProperties.Guard(weeklySalary = 0),crossbowman = CityProperties.Guard(weeklySalary = 0),musketeer = CityProperties.Guard(weeklySalary = 0))
我想了解
Spring
如何处理这些ConfigurationProperties
带注释的类的初始化,可以这么说魔术是如何发生的。我想知道这一点,以便正确调试应用程序以找出问题所在。高效的代码是JavaFX应用程序,它使整个初始化更加复杂:
@Slf4j
@SpringBootApplication
@Import(StandaloneConfiguration.class)
@PropertySource(value = {"application.properties", "server.properties"})
public class OpenPatricianApplication extends Application implements IOpenPatricianApplicationWindow {
private StartupService startupService;
private GamePropertyUtility gamePropertyUtility;
private int width;
private int height;
private boolean fullscreen;
private Stage primaryStage;
private final AggregateEventHandler<KeyEvent> keyEventHandlerAggregate;
private final MouseClickLocationEventHandler mouseClickEventHandler;
private ApplicationContext context;
public OpenPatricianApplication() {
width = MIN_WIDTH;
height = MIN_HEIGHT;
this.fullscreen = false;
keyEventHandlerAggregate = new AggregateEventHandler<>();
CloseApplicationEventHandler closeEventHandler = new CloseApplicationEventHandler();
mouseClickEventHandler = new MouseClickLocationEventHandler();
EventHandler<KeyEvent> fullScreenEventHandler = event -> {
try {
if (event.getCode().equals(KeyCode.F) && event.isControlDown()) {
updateFullscreenMode();
}
} catch (RuntimeException e) {
log.error("Failed to switch to/from fullscreen mode", e);
}
};
EventHandler<KeyEvent> closeEventWindowKeyHandler = event -> {
if (event.getCode().equals(KeyCode.ESCAPE)) {
log.info("Pressed ESC");
context.getBean(MainGameView.class).closeEventView();
}
};
addKeyEventHandler(closeEventHandler);
addKeyEventHandler(fullScreenEventHandler);
addKeyEventHandler(closeEventWindowKeyHandler);
}
/**
* Add a key event handler to the application.
* @param eventHandler to be added.
*/
private void addKeyEventHandler(EventHandler<KeyEvent> eventHandler) {
keyEventHandlerAggregate.addEventHandler(eventHandler);
}
public static void main(String[] args) {
launch(args);
}
@Override
public void init() {
SpringApplicationBuilder builder = new SpringApplicationBuilder(OpenPatricianApplication.class);
context = builder.contextClass(DependentAnnotationConfigApplicationContext.class).profiles("standalone")
.run(getParameters().getRaw().toArray(new String[0]));
this.startupService = context.getBean(StartupService.class);
this.gamePropertyUtility = context.getBean(GamePropertyUtility.class);
if (startupService.checkVersion()) {
startupService.logEnvironment();
CommandLineArguments cmdHelper = new CommandLineArguments();
Options opts = cmdHelper.createCommandLineOptions();
CommandLine cmdLine = cmdHelper.parseCommandLine(opts, getParameters().getRaw().toArray(new String[getParameters().getRaw().size()]));
if (cmdLine.hasOption(CommandLineArguments.HELP_OPTION)){
cmdHelper.printHelp(opts);
System.exit(0);
}
if (cmdLine.hasOption(CommandLineArguments.VERSION_OPTION)) {
System.out.println("OpenPatrician version: "+OpenPatricianApplication.class.getPackage().getImplementationVersion());
System.exit(0);
}
cmdHelper.persistAsPropertyFile(cmdLine);
}
}
@Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setMinWidth(MIN_WIDTH);
this.primaryStage.setMinHeight(MIN_HEIGHT);
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/icons/trade-icon.png")));
UIFactory uiFactory = context.getBean(UIFactory.class);
uiFactory.setApplicationWindow(this);
BaseStartupScene startupS = uiFactory.getStartupScene();
Scene defaultScene = new Scene(startupS.getRoot(), width, height);
defaultScene.getStylesheets().add("/styles/font.css");
this.fullscreen = Boolean.valueOf((String) gamePropertyUtility.getProperties().get("window.fullscreen"));
startupS.setSceneChangeable(this);
defaultScene.setOnMousePressed(mouseClickEventHandler);
defaultScene.setOnKeyPressed(keyEventHandlerAggregate);
try {
CheatKeyEventListener cheatListener = context.getBean(CheatKeyEventListener.class);
if (cheatListener != null) {
addKeyEventHandler(cheatListener);
}
} catch (Exception e) {
// the cheat listener is no defined for the context.
e.printStackTrace();
}
setCursor(defaultScene);
primaryStage.setFullScreen(fullscreen);
primaryStage.setFullScreenExitHint("");
primaryStage.setTitle("OpenPatrician");
primaryStage.setScene(defaultScene);
primaryStage.show();
}
private void setCursor(Scene scene) {
URL url = getClass().getResource("/icons/64/cursor.png");
try {
Image img = new Image(url.openStream());
scene.setCursor(new ImageCursor(img));
} catch (IOException e) {
log.warn("Failed to load cursor icon from {}", url);
}
}
/**
* @see SceneChangeable#changeScene(OpenPatricianScene)
*/
@Override
public void changeScene(final OpenPatricianScene scene) {
primaryStage.getScene().setOnMousePressed(mouseClickEventHandler);
primaryStage.getScene().setOnKeyPressed(keyEventHandlerAggregate);
primaryStage.getScene().setRoot(scene.getRoot());
}
/**
* Toggle between full screen and non full screen mode.
*/
public void updateFullscreenMode() {
fullscreen = !fullscreen;
primaryStage.setFullScreen(fullscreen);
}
@Override
public double getSceneWidth() {
return primaryStage.getScene().getWidth();
}
@Override
public double getSceneHeight() {
return primaryStage.getScene().getHeight();
}
@Override
public void stop() throws Exception {
System.out.println("Stopping the UI Application");
stopUIApplicationContext();
super.stop();
}
/**
* Closing the application context for the user interface.
*/
private void stopUIApplicationContext() {
AsyncEventBus eventBus = (AsyncEventBus) context.getBean("clientServerEventBus");
eventBus.post(new GameStateChange(EGameStatusChange.SHUTDOWN));
((AbstractApplicationContext)context).close();
}
}
最佳答案
看来您在将测试代码和生产代码不匹配。让我解释:@SpringBootApplication
旨在通过main来运行您的课程。通常,这看起来像:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args] {
SpringApplication.run(MyApplication .class, args);
}
}
如果要测试应用程序,则必须使用
@SpringBootTest
注释测试类。此注释将自动检测由@SpringBootConfiguration
注释的类(该类包含在@SpringBootApplication
中)。在@SpringBootTest类中,您还可以使用
@Import
加载其他配置类。@ActiveProfiles
只能在测试上下文中使用...运行“ test”类时,您不使用任何配置文件,因为此注释仅用于测试目的。如果切换到@SpringBootTest
,将是这样。仍然在测试上下文中,如果要加载属性文件(
application.properties
除外),则必须使用@TestPropertySource
而不是@PropertySource
。您的“主”测试类应该看起来像这样:
@SpringBootTest
@ActiveProfiles("server")
@Import({ServerTestConfiguration.class})
@TestPropertySource(locations = {"classpath:/server.properties, "classpath:/bean-test.properties"}})
public class SavegameTestApplication {
}
有关Spring Boot应用程序测试的更多信息,请检查以下内容:https://www.baeldung.com/spring-boot-testing#integration-testing-with-springboottest
一旦完成了测试类,就应该删除
CityWallSerializationTest
,这基本上是@SpringBootTest
的重新实现。请注意,您也可以在测试类中使用@Autowired
。