【发布时间】:2019-12-30 07:09:55
【问题描述】:
我有来自 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, productionInefficientFactor=0.0, loaner=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();
}
}
【问题讨论】:
-
Spring 使用您在 @configurationProperties 中给出的前缀选择类路径上文件中的属性,例如city.populationAmountWorkshop=value, city.loaner.maxRequest=value
-
你能显示测试代码吗?还有示例 application.properties ?
-
感谢
OpenPatricianApplication。在您的测试中,如何调用CityWallSerializationTest?与生产文件相比,您的测试文件位于何处? server.properties 和 bean-test.properties 位于哪里? -
你想从你的
CityWallSerializationTest开始什么样的子上下文? -
@RUAROThibault 该项目设置为
Maven项目,这意味着测试在相应包中的src/test/java下。 bean-test.properties 位于src/test/resources下,而 application.properties 和 server.properties 位于不同模块的src/main/resources中,它们是通过依赖项定义的。我不太清楚你所说的“子上下文”是什么意思?
标签: java spring-boot properties initialization