Application Configuration - Spring Configuration Metadata

    Customize the application using @ConfigurationProperties, as an alternative to use @Value.

    In the article

    • Configuring and changing application functionality through application.properties using ConfigurationProperties
    • Integration of application.properties with IDE
    • Check setting values

    image

    The differences between the two approaches are described here - ConfigurationProperties vs. Value
    In the picture above, the basic composition and principle of operation. The available system components, these are Spring components, simply classes, various constants, variables, etc., can be specified in the application.properties file, while at the time the development environment specifies options, checks are made. When the application starts, the specified values ​​will be checked for compliance with the type, limitations, and if everything is satisfactory, the application will be launched. For example, it is very convenient to configure the application functionality from the list of available Spring components, below I will show how.
    Property Class

    To create an application setup using ConfigurationProperties, you can start with a property class. It actually shows the properties, system components that we want to customize.

    AppProperties.java
    @ConfigurationProperties(prefix = "demo")
    @ValidatedpublicclassAppProperties{
        private String vehicle;
        @Max(value = 999, message = "Value 'Property' should not be greater than 999")
        private Integer value;
        private Map<String,Integer> contexts;
        private StrategyEnum strategyEnum;
        private Resource resource;
        private DemoService service;
        public String getVehicle(){
            return vehicle;
        }
        publicvoidsetVehicle(String vehicle){
            this.vehicle = vehicle;
        }
        public Map<String, Integer> getContexts(){
            return contexts;
        }
        publicvoidsetContexts(Map<String, Integer> contexts){
            this.contexts = contexts;
        }
        public StrategyEnum getStrategyEnum(){
            return strategyEnum;
        }
        publicvoidsetStrategyEnum(StrategyEnum strategyEnum){
            this.strategyEnum = strategyEnum;
        }
        public Resource getResource(){
            return resource;
        }
        publicvoidsetResource(Resource resource){
            this.resource = resource;
        }
        public DemoService getService(){
            return service;
        }
        publicvoidsetService(DemoService service){
            this.service = service;
        }
        public Integer getValue(){
            return value;
        }
        publicvoidsetValue(Integer value){
            this.value = value;
        }
        @Overridepublic String toString(){
            return"MyAppProperties{" +
                    "\nvehicle=" + vehicle +
                    "\n,contexts=" + contexts +
                    "\n,service=" + service +
                    "\n,value=" + value +
                    "\n,strategyEnum=" + strategyEnum +
                    '}';
        }
    }
    


    In the class prefix = “demo” will be used in application.properties, as a prefix to the property.

    Application class SpringApplication and project pom.xml
    @SpringBootApplication@EnableConfigurationProperties({AppProperties.class})
    @ImportResource(value= "classpath:context.xml")
    publicclassDemoConfigProcessorApplication{
    	publicstaticvoidmain(String[] args){
    		ConfigurableApplicationContext context = SpringApplication.run(DemoConfigProcessorApplication.class, args);
    		AppProperties properties = context.getBean(AppProperties.class);
    		String perform = properties.getService().perform(properties.getVehicle());
    		System.out.println("perform: " + perform);
    		System.out.println(properties.toString());
    	}
    }
    

    <?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>demoConfigProcessor</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>demoConfigProcessor</name><description>Demo project for Spring Boot Configuration Processor</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.0.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.jayway.jsonpath</groupId><artifactId>json-path</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>


    Here I announced two spring bins

    Spring context (context.xml)
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="service1"class="com.example.demoConfigProcessor.MyDemoService1">
            <description>Description MyDemoService 1</description>
        </bean>
        <bean id="service2"class="com.example.demoConfigProcessor.MyDemoService2">
            <description>Description MyDemoService 2</description>
        </bean>
    </beans>
    


    In the AppProperties class, I specified a link to some available application service, I will change it in application.properties, I will have two implementations of it, and I will connect one of them in application.properties.

    image

    Here is their implementation

    Demoservice
    publicinterfaceDemoService{
      String perform(String value);
    }
    

    publicclassMyDemoService1implementsDemoService{
        @Overridepublic String perform(String value){
            return"Service №1: perform routine maintenance  work on <" + value +">";
        }
    }
    

    publicclassMyDemoService2implementsDemoService{
        @Overridepublic String perform(String value){
            return"Service №2: perform routine maintenance  work on <" + value +">";
        }
    }
    


    This is already enough to start setting up application.properties. But whenever changes are made to the class with ConfigurationProperties, you need to rebuild the project, after which a file will appear in the project
    \target\classes\META-INF\spring-configuration-metadata.json . Actually, the IDE uses it for editing in the application.properties file. I will indicate its structure in the link in the materials. This file will be created based on the AppProperties class. If you now open the application.properties file and start typing “demo”, the medium will start showing available properties.

    image

    If you try to enter the wrong type, the IDE will tell.

    image

    Even if you leave it as it is and try to start the application, there will be a clear error

    image
    Adding additional metadata

    Additional metadata is only for the convenience of working with the application.properties in the IDE, if it is not necessary, you can not do it. For this, it is possible to specify in the additional help file (hints) and other information for the environment. To do this, copy the created file spring-configuration-metadata.json to \src\main\resources\META-INF\and rename it to
    additional-spring-configuration-metadata.json. In this file, I will be interested only in the “hints” section: []

    In it, it will be possible to list, for example, valid values ​​for demo.vehicle

    "hints": [
        {
          "name": "demo.vehicle",
          "values": [
            {
              "value": "car make A",
              "description": "Car brand A is allowed."
            },
            {
              "value": "car make B",
              "description": "Car brand B is allowed."
            }
          ]
        }]
    

    The “name” field contains the “demo.vehicle” certificate, and the “values” list of valid values. Now, if you rebuild the project and go to the file application.properties, then when you enter demo.vehicle, you will receive a list of valid values.

    image

    If you enter something other than the one proposed but of the same type, the editor will highlight, but the application will start in this case, since this is not strict restriction, but just a hint.

    image

    Earlier in the project, I declared two services MyDemoService1 and MyDemoService2 both implement the DemoService interface, now you can configure that only the services implementing this interface are available to the application.properties and, accordingly, selected in the AppProperties class. To do this, there are Providers. They can be specified in the additional-spring-configuration-metadata. Providers have several types of them can be viewed in the documentation, I will show an example for one, - spring-bean-reference. This type shows the names of the available beans in the current project. The list is limited to the base class or interface.

    Sample Providers for DemoService:

      "hints": [
        {
          "name": "demo.service",
          "providers": [
            {
              "name": "spring-bean-reference",
              "parameters": {
                "target": "com.example.demoConfigProcessor.DemoService"
              }
            }
          ]
        }
      ]
    

    After that, in the application.properties for the demo.service parameter a choice of two services will be available, you can see their description (description from the definition).

    image

    Now it is convenient to choose the desired service, change the functionality of the application. There is one point for object settings, Spring needs a little help to convert the string that is specified in the configuration into an object. To do this, a small class is derived from Converter.

    Serviceconverter
    @Component@ConfigurationPropertiesBindingpublicclassServiceConverterimplementsConverter<String, DemoService> {
        @Autowiredprivate ApplicationContext applicationContext;
        @Overridepublic DemoService convert(String source){
          return (DemoService) applicationContext.getBean(source);
        }
    }
    


    The project class diagram shows how these services are separated from the main application and are accessible via AppProperties.

    image
    Validation property
    You can add checks available in the JSR 303 framework to the fields of the AppProperties class . I wrote about this here . The checked, convenient file of a configuration of the application will turn out.

    Output to console

    image

    Project structure

    image

    Full file additional-spring-configuration-metadata.json

    additional-spring-configuration-metadata
    {
      "groups": [
        {
          "name": "demo",
          "type": "com.example.demoConfigProcessor.AppProperties",
          "sourceType": "com.example.demoConfigProcessor.AppProperties"
        }
      ],
      "properties": [
        {
          "name": "demo.contexts",
          "type": "java.util.Map<java.lang.String,java.lang.Integer>",
          "sourceType": "com.example.demoConfigProcessor.AppProperties"
        },
        {
          "name": "demo.vehicle",
          "type": "java.lang.String",
          "sourceType": "com.example.demoConfigProcessor.AppProperties"
        }
      ],
      "hints": [
        {
          "name": "demo.vehicle",
          "values": [
            {
              "value": "car make A",
              "description": "Car brand A is allowed."
            },
            {
              "value": "car make B",
              "description": "Car brand B is allowed."
            }
          ]
        },
        {
          "name": "demo.service",
          "providers": [
            {
              "name": "spring-bean-reference",
              "parameters": {
                "target": "com.example.demoConfigProcessor.DemoService"
              }
            }
          ]
        }
      ]
    }
    


    Configuration Metadata Materials

    Also popular now: