📜 ⬆️ ⬇️

The book "Java in the cloud. Spring Boot, Spring Cloud, Cloud Foundry »

image Hello! Basically, this book is intended for Java and JVM machine developers who are looking for ways to create better software in a short time using Spring Boot, Spring Cloud and Cloud Foundry. It is for those who have already heard the noise that has risen around microservices. You may have already realized how stratospheric heights the Spring Boot environment has flown, and you are surprised that businesses today use the Cloud Foundry platform. If so, then this book is for you.

Excerpt 3. Configuration style of twelve factor applications


This chapter will look at how to implement an application’s configuration.

Define a number of vocabulary terms. When it comes to configuration in Spring, the most common thing is to enter into the Spring environment various implementations of the application context - ApplicationContext , which helps the container to understand how to connect beans. Such a configuration can be represented as an XML file, which must be submitted to ClassPathXmlApplicationContext , or Java classes annotated in a manner that can be provided to the AnnotationConfigApplicationContext object. And of course, when studying the latter option, we will refer to the Java configuration.

But in this chapter we are going to look at the configuration as defined in the manifest of the 12-factor application . In this case, it refers to literal values ​​that can vary from one environment to another: we are talking about passwords, ports and host names, or property flags. The configuration ignores magic constants embedded in the code. The manifest includes an excellent criterion for correct configuration: can the application code base be open source at any time without disclosing and compromising important credentials? This type of configuration refers solely to values ​​that vary from one environment to another, and does not apply, for example, to connecting Spring beans or configuring Ruby routes.
')

Spring Framework Support


In Spring, the configuration style corresponding to 12 factors has been supported since the appearance of the PropertyPlaceholderConfigurer class. Once its instance is determined, it replaces the literals in the XML configuration with the values ​​extracted from the file with the .properties extension. In Spring, PropertyPlaceholderConfigurer has been offered since 2003. Spring 2.5 introduces support for the XML namespace, as well as support for this property lookup space. This allows you to substitute the literal values ​​of the definitions of beans into the XML configurations with the values ​​assigned to the keys in the external properties file (in this case, the simple.properties file, which may appear in the class path or be external to the application).

Configuration in the style of 12 factors is aimed at eliminating the unreliability of the existing magic strings, that is, values ​​like database addresses and accounts for connecting to them, ports, etc., which are hard coded in the compiled application. If the configuration is moved outside the application, then it can be replaced without resorting to this new code assembly.

PropertyPlaceholderConfigurer class


Let's look at a sample of how to use the PropertyPlaceholderConfigurer class, the XML definitions of Spring beans, and the file with the .properties extension outside the application. We just need to print the value in this property file. This will help to make the code shown in Example 3.1.

Example 3.1. Properties File: some.properties

configuration.projectName=Spring Framework 

This is the Spring-owned ClassPathXmlApplicationContext class, so we use the XML namespace from the Spring context and point to our some.properties file. Then, in the definitions of the beans, we use literals in the form of $ {configuration.projectName}, and Spring replaces them with values ​​from our properties file (example 3.2).

Example 3.2. Spring XML configuration file

 <context:property-placeholder location="classpath:some.properties"/> (1) <bean class="classic.Application"> <property name="configurationProjectName" value="${configuration.projectName}"/> </bean> 

(1) classpath: location referencing the file in the current compiled block of code (.jar, .war, etc.). Spring supports many alternatives, including file: and url:, allowing a file to exist apart from a block of code.

Finally, let's look at how the Java class looks, thanks to which it is possible to bring all this together (Example 3.3).

Example 3.3. Java class that must be configured with a property value
package classic;

 import org.apache.commons.logging.LogFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Application { public static void main(String[] args) { new ClassPathXmlApplicationContext("classic.xml"); } public void setConfigurationProjectName(String pn) { LogFactory.getLog(getClass()).info("the configuration project name is " + pn); } } 

The first example uses the XML format for the configuration of Spring beans. In Spring 3.0 and 3.1, the situation for developers using Java configuration has improved significantly. In these issues, the Value annotation and the Environment abstraction were introduced.

Abstraction Environment and Value


The Environment abstraction represents, during the execution of a code, its indirect relation to the environment in which it is running, and allows the application to ask a question (“What line separator line.separator on this platform?”) About the properties of the environment. Abstraction acts as a mapping from keys and values. By configuring the PropertySource PropertySource in Environment, you can configure the location from which these values ​​will be read. By default, Spring loads system keys and environment values, such as line.separator. Spring can be instructed to load configuration keys from a file in the same order that might have been used in early releases of the Spring property lookup solution using the @PropertySource annotation.

The Value annotation provides a way to embed environment values ​​in constructors, setters, fields, etc. These values ​​can be calculated using the Spring Expression Language or the property substitution syntax, provided the PropertySourcesPlaceholderConfigurer is registered, as done in Example 3.4.

Example 3.4. Register PropertySourcesPlaceholderConfigurer
package env;

 import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.Environment; import javax.annotation.PostConstruct; (1) @Configuration @PropertySource("some.properties") public class Application { private final Log log = LogFactory.getLog(getClass()); public static void main(String[] args) throws Throwable { new AnnotationConfigApplicationContext(Application.class); } (2) @Bean static PropertySourcesPlaceholderConfigurer pspc() { return new PropertySourcesPlaceholderConfigurer(); } (3) @Value("${configuration.projectName}") private String fieldValue; (4) @Autowired Application(@Value("${configuration.projectName}") String pn) { log.info("Application constructor: " + pn); } (5) @Value("${configuration.projectName}") void setProjectName(String projectName) { log.info("setProjectName: " + projectName); } (6) @Autowired void setEnvironment(Environment env) { log.info("setEnvironment: " + env.getProperty("configuration.projectName")); } (7) @Bean InitializingBean both(Environment env, @Value("${configuration.projectName}") String projectName) { return () -> { log.info("@Bean with both dependencies (projectName): " + projectName); log.info("@Bean with both dependencies (env): " + env.getProperty("configuration.projectName")); }; } @PostConstruct void afterPropertiesSet() throws Throwable { log.info("fieldValue: " + this.fieldValue); } } 

(1) The @PropertySource annotation is an abbreviation like property-placeholder that configures a PropertySource from a file with the .properties extension.

(2) PropertySourcesPlaceholderConfigurer must be registered as a static bean component, since it is an implementation of the BeanFactoryPostProcessor and must be called at an early stage of the initialization life cycle in Spring bean components. When using XML configuration of beans in Spring, this nuance is not visible.

(3) You can decorate the fields with the Value annotation (but do not do this, otherwise the code will not be tested!) ...

(4) ... or the Value annotation, you can decorate the parameters of the constructor ...

(5) ... or use the installation methods ...

(6) ... or implement the Spring Environment object and perform key resolution manually.

(7) Parameters with the Value annotation can also be used in the argument provider of Bean methods in the Spring Java configuration.

In this example, the values ​​are loaded from the file simple.properties, and then it contains the configuration.projectName value provided in various ways.

Profiles


Among other things, the Environment abstraction introduces profiles . This allows you to assign labels (profiles) in order to group the beans. Profiles should be used to describe bean-components and bean-graphs, varying from medium to medium. Multiple profiles can be activated simultaneously. Bean components that do not have profiles assigned to them are always activated. Bean components that have a default profile are activated only if they have no other active profiles. The profile attribute can be specified in a bean definition in XML or in tag classes, configuration classes, individual beans, or in the Provider Bean methods using Profile .

Profiles allow you to describe sets of beans that must be created in one environment a little differently than in another. In the local dev development profile, for example, you can use the built-in H2 data source javax.sql.DataSource, and then, when the prod profile is active, switch to the javax.sql.DataSource data source for PostgreSQL, obtained using JNDI search or by reading properties from the environment variable in Cloud Foundry . In both cases, your code will work: you get javax.sql.DataSource, but the decision on which specific instance to use is made by activating one profile or several (example 3.5).

Example 3.5. Demonstrate that @Configuration classes can load various configuration files and provide various bean-based components
active profile

 package profiles; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.*; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.Environment; import org.springframework.util.StringUtils; @Configuration public class Application { private Log log = LogFactory.getLog(getClass()); @Bean static PropertySourcesPlaceholderConfigurer pspc() { return new PropertySourcesPlaceholderConfigurer(); } (1) @Configuration @Profile("prod") @PropertySource("some-prod.properties") public static class ProdConfiguration { @Bean InitializingBean init() { return () -> LogFactory.getLog(getClass()).info("prod InitializingBean"); } } @Configuration @Profile({ "default", "dev" }) (2) @PropertySource("some.properties") public static class DefaultConfiguration { @Bean InitializingBean init() { return () -> LogFactory.getLog(getClass()).info("default InitializingBean"); } } (3) @Bean InitializingBean which(Environment e, @Value("${configuration.projectName}") String projectName) { return () -> { log.info("activeProfiles: '" + StringUtils.arrayToCommaDelimitedString(e.getActiveProfiles()) + "'"); log.info("configuration.projectName: " + projectName); }; } public static void main(String[] args) { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(); ac.getEnvironment().setActiveProfiles("dev"); (4) ac.register(Application.class); ac.refresh(); } } 

(1) This configuration class and all its Bean definitions will be calculated only if the prod profile is active.

(2) This configuration class and all its Bean definitions will be calculated only if the dev profile is active or no profile is active, including dev.

(3) This InitializingBean component simply records the current active profile and enters the value that was eventually entered into the properties file.

(4) Activating a profile (or profiles) programmatically is quite simple.

Spring responds to several other profile activation methods that use the spring_profiles_active or spring.profiles.active token. A profile can be set using an environment variable (for example, SPRING_PROFILES_ACTIVE), JVM properties (‑Dspring.profiles.active = ...), the initialization parameter of a servlet application, or programmatically.

Bootiful configuration


Spring Boot significantly improves the situation. The environment will initially automatically load properties from a hierarchy of previously known locations. Command line arguments override property values ​​obtained from JNDI, which override properties obtained from System.getProperties (), etc.

- Command line arguments.
- JNDI attributes from java: comp / env.
- Properties System.getProperties ().
- Operating system environment variables.
- External files files properties: (config /)? Application. (Yml.properties).
- Internal properties files in the archive (config /)? Application. (Yml.properties).
- Annotation @PropertySource in configuration classes.
- Source properties from SpringApplication.getDefaultProperties ().

If a profile is active, data will be automatically read from configuration files based on the profile name, for example, from a file such as src / main / resources / application-foo.properties, where foo is the current profile.

If the SnakeYAML library is mentioned in the classpath, the YAML files will also be automatically downloaded, following basically the same convention.

The YAML specification page states that "YAML is a human data standard for data serialization for all programming languages." YAML is a hierarchical representation of values. In regular files with the .properties extension, the hierarchy is denoted by a dot (“.”), While in YAML files, the newline character and additional indent level are used. It would be nice to use these files to avoid having to specify common roots in the presence of highly branched configuration trees.

The contents of the file with the extension .yml shown in Example 3.6.

Example 3.6. Property file application.yml. Data is hierarchical.

 configuration: projectName : Spring Boot management: security: enabled: false 

In addition, the Spring Boot environment greatly simplifies obtaining the correct result in general cases. It turns the -D arguments into java process and environment variables available as properties. It even normalizes them, in which the $ CONFIGURATION_PROJECTNAME environment variable (CONFIGURATION_NAME PROJECT) or the -D argument in the form ‑Dconfiguration.projectName (project_name_configuration) are available using the configuration.projectName key (configuration.project_name) in the same way. The spring_profiles_active token is available.

Configuration values ​​are strings and, with a sufficient number of them, can become unreadable when trying to make sure that such keys do not themselves become magic strings in the code. Spring Boot introduces the component type @ConfigurationProperties. When you annotate a POJO — Plain Old Java Object (a plain old Java object) —– using @ConfigurationProperties and specifying the prefix, Spring will attempt to map all properties that begin with this prefix to POJO properties. In the example below, the value for configuration.projectName will be mapped to a POJO instance, which all code can then embed and dereference for type-safe reading of values. As a result, you will only have a mapping from (String) key in one place (Example 3.7).

Example 3.7. Automatic property resolution from src / main / resources / application.yml
package boot;

 import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; (1) @EnableConfigurationProperties @SpringBootApplication public class Application { private final Log log = LogFactory.getLog(getClass()); public static void main(String[] args) { SpringApplication.run(Application.class); } @Autowired public Application(ConfigurationProjectProperties cp) { log.info("configurationProjectProperties.projectName = " + cp.getProjectName()); } } (2) @Component @ConfigurationProperties("configuration") class ConfigurationProjectProperties { private String projectName; (3) public String getProjectName() { return projectName; } public void setProjectName(String projectName) { this.projectName = projectName; } } 

(1) The @EnableConfigurationProperties annotation instructs Spring to map properties to POJO objects annotated with @ConfigurationProperties.

(2) The @ConfigurationProperties annotation indicates to Spring that this bean should be used as the root component for all properties starting with configuration., With subsequent tokens mapped to the properties of the object.

(3) The projectName field will eventually have the value assigned to the configuration.projectName property key.

Spring Boot actively applies the @ConfigurationProperties mechanism to enable users to redefine the elementary components of the system. You may notice that property keys allow you to make changes, for example, by adding the org.springframework.boot: spring-boot-starter-actuator dependency to a Spring Boot based web application and then visiting page 127.0.0.1 : 8080 / configprops.

The end points of the actuator will be discussed in more detail in Chapter 13. They are locked and require a username and password by default. Security measures can be disabled (but only to look at these points) by specifying management.security.enabled = false in the application.properties file (or application.yml).

You will get a list of supported configuration properties based on the types represented in the classpath at run time. As the number of Spring Boot types grows, additional properties will be shown. At this endpoint, the properties exported by your POJO objects annotated with @ConfigurationProperties will also be displayed.

Centralized logged configuration using Spring Cloud configuration server


So far so good, but it is necessary that things go even more successfully. We still did not answer the questions about common applications:


Spring Cloud Configuration Server


The problem of configuration centralization can be solved by saving the configuration in one directory and pointing all applications to it. You can also install version control of this directory using Git or Subversion. Then the support needed for verification and registration will be obtained. But the last two requirements will still not be fulfilled, so something more sophisticated is needed. Refer to the Spring Cloud configuration server. The Spring Cloud platform offers a configuration server and client for this server.

The Spring Cloud Config server is a REST API to which our customers will connect to pick up their configuration. The server also manages a version control configuration repository. He is an intermediary between our clients and the configuration repository and thus is in a favorable position, allowing to implement security tools for connections from clients to the service and connections from the service to the repository of configurations with version control. The Spring Cloud Config client provides client applications with a new scope, refresh, which makes it possible to configure Spring components again without restarting the application.

Technologies similar to the Spring Cloud Config server play an important role, but entail additional work costs. Ideally, this duty should be shifted to the platform and automated. When using Cloud Foundry, you can find the Config Server service in the services catalog, whose actions are based on the use of the Spring Cloud Config server.

Consider a simple example. First we set up the Spring Cloud Config server. Several Spring Boot applications can access one such service at once. You need somewhere and somehow make it function. Then it will only remain to inform all our services about where to find the configuration service. It works as a kind of intermediary for configuration keys and values ​​that it reads from Git storage over the network or from disk. Add org.springframework.cloud: spring-cloud-config-server to your Spring Boot application build to enter the Spring Cloud Config server (Example 3.8).

Example 3.8. To embed a configuration server into an assembly, use the @EnableConfigServer annotation

 package demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; (1) @SpringBootApplication @EnableConfigServer public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 

(1) Using the @EnableConfigServer annotation installs Spring Cloud Config server.

Example 3.9 shows the configuration for the configuration service.

Example 3.9. Configuration of the src / main / resources / application.yml configuration server

server.port = 8888
spring.cloud.config.server.git.uri = \
github.com/cloud-native-java/config-server-configuration-repository (1)

(1) An indication of a running Git repository that has a local character or is accessible over the network (for example, on GitHub (https://github.com/)) and used by the Spring Cloud Config server.

Here, the Spring Cloud configuration service is instructed to search for configuration files for individual clients in Git storage on GitHub. We pointed to this repository, but a link to any valid Git URI would fit. Of course, he doesn’t even have to do with the Git system, you can use Subversion or even unmanaged directories (although we strongly advise against this). In this case, the repository URI is hard-coded, but there is nothing to prevent it from getting from the -D argument, the argument — or from the environment variable.

»More information about the book can be found on the publisher's website.
» Table of Contents
» Excerpt

For Habrozhiteley a 20% discount on coupon - Java

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


All Articles