Spring Boot hints

Run a spring boot app :

Spring boot app via maven : the base command

It specifies the spring boot profile and it enables the remote debug :
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5000" -Dspring-boot.run.profiles=local

Pass spring properties and application args to Spring Boot maven plugin :

Both application args and spring boot properties are passed through -Dspring-boot.run.arguments. 
All of these will be stored in the String[] args of the main method.

The application args may be passed in any way :
keyOne valueOne keyTwo valueTwo
keyOne=valueOne keyTwo=valueTwo
–keyOne valueOne –keyTwo valueTwo
argOne ArgTwo
and so for…
In any case that is the application responsibility to parse the string[] args to retrieve them.

The spring properties must be passed in a specific format :
–spring-prop-foo=value
At startup, Spring will automatically add/override the spring-prop-foo prop with the « value » value as a spring boot env property.

Example passing  2 spring boot env props and 4 application args:
mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=7777,--manager.server.port=7777 --foo fooValue bar barValue"

Specify the spring boot profile with mvn :

-Dspring-boot.run.profiles=production

Run with maven in debug mode :

mvn spring-boot:run -Dspring-boot.run.profiles=local -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5000"

Run a jar by passing environment props and programs args:

Similarly to the maven spring boot plugin way, spring env props must be suffixed with — as java args or as alternative, we could pass them as java system properties with -D.
About program args these are free about their format.
Ex: here -Dspring.application.name, foo-env-prop and bar-env-prop are spring env props :
java -jar -Dspring.application.name=foo-app foo.jar --foo-env-prop=123 --bar-env-prop=456 argOne foo argTwo Bar

Run a jar in debug mode :

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=0.0.0.0:4000 -Djava.security.egd=file:/dev/urandom -jar foo.jar

Note address=0.0.0.0 that allows to listen on any ip (useful for remote machines or apps running on docker).
To restrict to localhost : address=4000

Specify the spring boot properties with java -jar :

We can do that with Java system properties (-D) or with args properties prefixed by — as seen earlier :
Ex:
-Dspring.profiles.active=production
Or
--spring.profiles.active=production

Logging

General properties

logging.file.name (called logging.file before spring boot 2.2) : the path to the log file.
Ex: logging.file.name : "foo/bar/app.log"

logging.file.path : the path to the directory where should be the log file.
Ex: logging.file.path : "foo/bar/"
And so Spring will use as log file : spring.log

Autoconfiguration with Spring boot:

Prevent Spring Boot from enabling autoconfiguration

Some dependencies present in the classpath of the spring boot app will trigger the relevant auto-configuration.
ex: activeMQ jars detection triggers the JmsAutoConfiguration auto-configuration.
In case we don’t want to benefit from auto configuration (legacy reason), we could exclude the auto-configuration :

spring:
 autoconfigure:
    exclude:
     - org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration
     - org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration

Or by annotating the spring boot main class with :
@EnableAutoConfiguration(exclude = JmsAutoConfiguration.class, ActiveMQAutoConfiguration.class)

Profile configuration (whatever the way : mvn or fat-jar)

We can set the default profile in application.yml by setting the property spring.profiles.active.
It is overriable at runtime as seen earlier.

Define a yaml file as @PropertySource

By default, yaml files can be used as properties file.
But we cannot use it in @PropertySource.
Here is how to bypass that limitation.
Note that with Spring Boot 1, the following workaround allowed to define several yaml source files (and not some application.yaml files) both as PopertySource and profile aware by specifying in the yaml a spring.profiles property and by passing the current active profile in the yaml factory that we created to map the yaml file to a Spring PropertySource.
It looked something like :

public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
 
  @Override
  public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
      ...
      String activeProfile = System.getProperty("spring.profiles.active");
      return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), activeProfile);
}

For Spring Boot 2, we cannot make our custom factory profile aware because the load() method doesn’t accept any longer the third parameter (profile parameter). But there is a workaround for. We will see below.
The servers.yml file :

master:
  host: master.com
  port: 8080
workers:
  - host: foo.bar
    port: 8080
    capacity: 100
  - host: bar.foo
    port: 8080
    capacity: 10

import java.io.IOException;
 
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
 
public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        if (resource == null) {
            return super.createPropertySource(name, resource);
        }
 
        CompositePropertySource propertySource = new CompositePropertySource(name);
        new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource())
                                      .stream()
                                      .forEach(propertySource::addPropertySource);
        return propertySource;
    }
}

And here how define the Yaml bean :

package davidxxx.yaml;
 
import java.util.List;
 
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
 
@Configuration
@ConfigurationProperties()
@PropertySource(name = "servers", value = "classpath:servers.yml", factory = YamlPropertyLoaderFactory.class)
public class Servers {
 
    private Master master;
    private List<Worker> workers;
 
    public Master getMaster() {
        return master;
    }
 
    public void setMaster(Master master) {
        this.master = master;
    }
 
    public List<Worker> getWorkers() {
        return workers;
    }
 
    public void setWorkers(List<Worker> workers) {
        this.workers = workers;
    }
}

The worker class (the master class is the same without the capacity property) :

package davidxxx.yaml;
 
public class Worker {
 
    private String host;
    private int port;
    private int capacity;
 
    public String getHost() {
        return host;
    }
 
    public void setHost(String host) {
        this.host = host;
    }
 
    public int getPort() {
        return port;
    }
 
    public void setPort(int port) {
        this.port = port;
    }
 
    public int getCapacity() {
        return capacity;
    }
 
    public void setCapacity(int capacity) {
        this.capacity = capacity;
    }
}

And at last how to write a test slicing for that yaml :

package davidxxx.yaml;
 
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
 
import static org.assertj.core.groups.Tuple.tuple;
 
 
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {},
        initializers = ConfigFileApplicationContextInitializer.class)
@EnableConfigurationProperties(value = Servers.class)
public class ServersYamlTest {
 
    @Autowired
    Servers servers;
 
    @Test
    void validateConfiguration() {
        Assertions.assertThat(servers.getMaster())
                  .extracting(Master::getHost, Master::getPort)
                  .containsExactly("master.com", 8080);
 
        Assertions.assertThat(servers.getWorkers())
                  .extracting(Worker::getHost, Worker::getPort, Worker::getCapacity)
                  .containsExactly(tuple("foo.bar", 8080, 100),
                                   tuple("bar.foo", 8080, 10)
                  );
 
    }
}

Define a yaml file both as @PropertySource and Profile aware

Since Spring Boot 2, it requires a trick.  It exists probably other ways. That is the way that I use :

– name each distinct yaml property source files with a common prefix that you concatenate to a specific suffix (ex: servers-dev.xml, servers-prod.xml) .

– move the profile information located in these yaml property source files to the application-xxx.yml files but now convey the profile information by introducing a custom property.
servers-filename-suffix=dev for servers-dev.xml  
servers-filename-suffix=prod for servers-prod.xml.

– make the @PropertySource value attribute dynamic by valuing it with an EL that concatenate the static common prefix to the dynamic specific suffix :

@PropertySource(name = "servers", value = "classpath:servers-${servers-filename-suffix}.yml", factory = YamlPropertyLoaderFactory.class)
public class Servers{...}

Yaml conf : nested lists

Java model :

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
 
@Configuration
@ConfigurationProperties()
@PropertySource(name = "servers", value = "classpath:servers.yml", factory = YamlPropertyLoaderFactory.class)
public class Servers {
 
    private Cluster cluster;
    private List<Host> hosts;
   // getters/setters
}
 
 
public class Host {
 
    private String host;
    private List<Node> nodes;
   // getters/setters
}
 
 
public class Node {
 
    private int foo;
    private int bar;
   // getters/setters
}

yaml mapping

cluster:
  host: master.com:8080
hosts:
  - host: foo.com:8080
    nodes:
      - foo: 1
        bar: 10
      - foo: 2
        bar: 20
  - host: bar.com:8080
    nodes:
      - foo: 10
        bar: 100
      - foo: 20
        bar: 200

Yaml hints

See yaml page.

Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *