📜 ⬆️ ⬇️

Application Configuration - Spring Configuration Metadata

Setting up an application using @ConfigurationProperties , as an alternative to using @Value .

In the article


image

About the differences between the two approaches it says 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") @Validated public class AppProperties { 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; } public void setVehicle(String vehicle) { this.vehicle = vehicle; } public Map<String, Integer> getContexts() { return contexts; } public void setContexts(Map<String, Integer> contexts) { this.contexts = contexts; } public StrategyEnum getStrategyEnum() { return strategyEnum; } public void setStrategyEnum(StrategyEnum strategyEnum) { this.strategyEnum = strategyEnum; } public Resource getResource() { return resource; } public void setResource(Resource resource) { this.resource = resource; } public DemoService getService() { return service; } public void setService(DemoService service) { this.service = service; } public Integer getValue() { return value; } public void setValue(Integer value) { this.value = value; } @Override public 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") public class DemoConfigProcessorApplication { public static void main(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"?> <project xmlns="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 of its implementations, and I will connect one of them in application.properties.

image

Here is their implementation

Demoservice
 public interface DemoService { String perform(String value); } 

 public class MyDemoService1 implements DemoService { @Override public String perform(String value) { return "Service â„–1: perform routine maintenance work on <" + value +">"; } } 

 public class MyDemoService2 implements DemoService { @Override public String perform(String value) { return "Service â„–2: perform routine maintenance work on <" + value +">"; } } 


This is now 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,” then the medium will start showing available properties.

image

If you try to enter the wrong type, IDE will report

image

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

image
Add 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: []

It will be possible to list in it, 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” text, and the “values” list of allowed 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 a strict limitation, 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 the selected one is initialized in the AppProperties class. To do this, there are Providers. You can specify them 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. For this, a small class is derived from Converter.

Serviceconverter
 @Component @ConfigurationPropertiesBinding public class ServiceConverter implements Converter<String, DemoService> { @Autowired private ApplicationContext applicationContext; @Override public 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 it here . You get a testable, convenient application configuration file.

Console output

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

Source: https://habr.com/ru/post/430256/


All Articles