我有以下来自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

08-04 09:27