= SpringBoot =
<<TableOfContents(2)>>

== Common properties for spring boot ==
 * https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
{{{#!highlight sh
# disable web container/server
spring.main.web-environment=false
spring.jmx.enabled=false
#spring.main.banner-mode=off
spring.main.banner-mode=console
spring.banner.location=classpath:banner.txt
}}}


== Example ==

=== pom.xml ===
{{{#!highlight xml
<?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>hello</groupId>
    <artifactId>test-spring-boot</artifactId>
    <version>0.1.0</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <version>1.4.4</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>

         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>

    </dependencies>
    <properties>
        <start-class>hello.Application</start-class>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestone</id>
            <url>http://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestone</id>
            <url>http://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>
</project>
}}}

=== src/main/java/hello/GreetingController.java ===
{{{#!highlight java
package hello;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.ArrayList;

@Controller
public class GreetingController {
    private final Logger logger = LoggerFactory.getLogger(GreetingController.class);
    @Autowired
    DummyDAO dummyDAO;

    public GreetingController(){
        logger.debug("Greeting controller created.");
    }

    @RequestMapping("/greeting")
    // http://localhost:8080/greeting?name=nnnn 
    public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {
        logger.info("Greeting endpoint called.");
        model.addAttribute("name", name);
        return "greeting";
    }

    @RequestMapping(value="/dummy",produces="application/json")
    @ResponseBody
    // http://localhost:8080/dummy
    public List<Dummy> dummy(){
      List<Dummy> list= new java.util.ArrayList<Dummy>();
      Dummy dummy = new Dummy();
      dummy.setFieldA("AAA");
      dummy.setFieldB("CCC");
      list.add(dummy);

      Dummy dummy2 = new Dummy();
      dummy2.setFieldA("AAA2");
      dummy2.setFieldB("CCC2");
      list.add(dummy2);

      return list;
    }

    @RequestMapping(value="/dummyname",produces="application/json")
    @ResponseBody
    // http://localhost:8080/dummyname
    public String getDummyName(){
      return dummyDAO.getNameFromDummy();
    }


}
}}}

=== src/main/java/hello/Application.java ===
{{{#!highlight java
package hello;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ComponentScan
@EnableAutoConfiguration
public class Application {
    private static Logger logger;

    public static void main(String[] args) {
        logger = LoggerFactory.getLogger(Application.class);
        logger.info("Starting application");
        SpringApplication.run(Application.class, args);
    }

    @Bean(name="basicDataSource",destroyMethod="close")
    public BasicDataSource createBasicDataSource(){
      logger.info("Creating basicDataSource");
      BasicDataSource bds = new BasicDataSource();
      bds.setDriverClassName("org.mariadb.jdbc.Driver");
      bds.setUrl("jdbc:mariadb://localhost:3306/springmvchtml");
      bds.setUsername("usertest");
      bds.setPassword("usertest");
      bds.setInitialSize(3);
      return bds;
    }
/*
    @Bean(name="jdbcTemplate")
    public JdbcTemplate jdbcTemplate(){
      logger.info("Creating jdbcTemplate");
      return new JdbcTemplate( basicDataSource() );
    }
*/
    @Bean(name="jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(@Autowired @Qualifier("basicDataSource") BasicDataSource bds  ){
      logger.info("Creating jdbcTemplate");
      return new JdbcTemplate( bds );
    }

}
}}}

=== src/main/java/hello/ThreadTimer.java ===
{{{#!highlight java
package hello;

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;

@Component
public class ThreadTimer extends Thread {
  private int delaySeconds;
  private Logger logger;
  private boolean running;
  private Object monitor=new Object();
  private ArrayList<Object> subscribers;

  //@Autowired
  //WaitThread waitThread;

  public ThreadTimer() {
    this.logger = LoggerFactory.getLogger(ThreadTimer.class);
    logger.info("Created instance of " + this.getClass().getSimpleName());
    this.running = true;
    this.delaySeconds = 5 * 1000;
    this.setName(this.getClass().getSimpleName() + "_" + this.getName());
    this.subscribers = new ArrayList<Object>();
  }

  public void addSubscriber(Object subscriber){
    this.subscribers.add(subscriber);
  }

  @PostConstruct
  public void init() {
    logger.info("Starting the thread");
    this.start();
  }

  @Override
  public void run() {
    while (running) {
      try {
        Thread.sleep(this.delaySeconds);
        logger.info("Delay " + this.getClass().getSimpleName());

        for(Object o: this.subscribers){
          synchronized(o){
            o.notify();
          }
        }
      }
      catch (InterruptedException e) {
          logger.info("ThreadTimer interrupted exception:" + e.getMessage() );
      }
      catch (Exception e) {
          e.printStackTrace();
          logger.info("ThreadTimer exception:" + e.getMessage() );
          stopRunning();
      }
    }
    logger.info("Exited " + this.getClass().getSimpleName());
  }

  public void startRunning() {
    this.running = true;
  }

  public void stopRunning() {
    this.running = false;
  }

  public void destroy(){
    logger.info("Called destroy");
    this.stopRunning();
    this.interrupt();
  }
}

}}}

=== src/main/java/hello/WaitThread.java ===
{{{#!highlight java
package hello;

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class WaitThread extends Thread {
  private Logger logger;
  private boolean running;
  private Object monitor;

  @Autowired
  ThreadTimer timerThread;

  public WaitThread() {
    this.logger = LoggerFactory.getLogger(WaitThread.class);
    logger.info("Created instance of " + this.getClass().getSimpleName());
    this.running = true;
    this.setName(this.getClass().getSimpleName() + "_" + this.getName());
    this.monitor=new Object();
  }

  @PostConstruct
  public void init() {
    this.timerThread.addSubscriber(this);
    logger.info("Starting the thread");
    this.start();
  }

  public void run() {
    while (running) {
      try {
        synchronized(this){
          this.wait();
          logger.info("Notification received.");
        }
      }
      catch (InterruptedException e) {
          logger.info("ThreadTimer interrupted exception:" + e.getMessage() );
      }
      catch (Exception e) {
          logger.info("WaitThread exception:" + e.getMessage() );
          stopRunning();
      }
    }
    logger.info("Exited " + this.getClass().getSimpleName());
  }

  public void startRunning() {
    this.running = true;
  }

  public void stopRunning() {
    this.running = false;
  }

  public void destroy(){
    logger.info("Called destroy");
    this.stopRunning();
    this.interrupt();
  }
}
}}}


=== src/main/java/hello/Dummy.java ===
{{{#!highlight java
package hello;

public class Dummy{
  private String fieldA;
  private String fieldB;

  public Dummy(){
  }

  public String getFieldA(){
    return fieldA;
  }

  public String getFieldB(){
    return fieldB;
  }

  public void setFieldA(String arg){
    fieldA = arg;
  }

  public void setFieldB(String arg){
    fieldB = arg;
  }
}
}}}

=== src/main/java/hello/DummyDAO.java ===
{{{#!highlight java
package hello;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class DummyDAO {
  @Autowired
  @Qualifier("jdbcTemplate")
  private JdbcTemplate jdbcTemplate;
  private Logger logger;

  public DummyDAO() {
    logger = LoggerFactory.getLogger(DummyDAO.class);
    logger.info("Created " + this.getClass().getSimpleName());
  }

  public String getNameFromDummy() {
    return this.jdbcTemplate.queryForObject("select name from dummy limit 1", String.class);
  }
}
}}}

=== src/main/resources/templates/greeting.html ===
{{{#!highlight html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Getting Started: Serving Web Content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>
}}}

=== src/main/resources/application.properties ===
{{{#!highlight sh
logging.file=/tmp/testout.log
}}}

=== src/main/resources/logback-spring.xml ===
{{{#!highlight xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
-->
    <!-- override spring logback default behaviour -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${filelog}</file>
        <encoder>
                <pattern>%date{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="GREETFILE" class="ch.qos.logback.core.FileAppender">
        <file>/tmp/greet.log</file>
        <encoder>
                <pattern>%date{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
                <Pattern>%yellow(%date{ISO8601}) %green([%thread]) %highlight(%-5level) %cyan(%logger{35}) - %white(%msg%n) </Pattern>
        </layout>
    </appender>

    <root level="INFO">
        <appender-ref ref="FILE" />
        <appender-ref ref="CONSOLE"/>
    </root>

    <logger name="hello.GreetingController" level="debug" additivity="false">
        <appender-ref ref="GREETFILE"/>
        <appender-ref ref="CONSOLE" />
    </logger>

</configuration>
}}}

=== src/main/resources/test.sql ===
{{{#!highlight sql
--JDBC URL: jdbc:mariadb://localhost:3306/springmvchtml
--mysql -u root -p
create database springmvchtml;
create user 'usertest'@'%' identified by '????????';
create user 'usertest'@'localhost' identified by '????????';
grant all on springmvchtml.* to 'usertest'@'%';
grant all on springmvchtml.* to 'usertest'@'localhost';
show grants for 'usertest'@'%';
show grants for 'usertest'@'localhost';
create table springmvchtml.dummy (name varchar(255) ) ;
insert into springmvchtml.dummy (name) values('aaaa');
insert into springmvchtml.dummy (name) values('bbbb');
commit;
-- mysql -u usertest -p
}}}

=== JAR files list ===
{{{#!highlight sh
jar tf target/test-spring-boot-0.1.0.jar | grep "\.jar"
}}}

{{{
BOOT-INF/lib/jackson-core-2.8.1.jar
BOOT-INF/lib/spring-web-4.3.2.RELEASE.jar
BOOT-INF/lib/unbescape-1.1.0.RELEASE.jar
BOOT-INF/lib/javassist-3.20.0-GA.jar
BOOT-INF/lib/mariadb-java-client-1.4.4.jar
BOOT-INF/lib/jul-to-slf4j-1.7.21.jar
BOOT-INF/lib/groovy-2.4.7.jar
BOOT-INF/lib/hibernate-validator-5.2.4.Final.jar
BOOT-INF/lib/classmate-1.3.1.jar
BOOT-INF/lib/tomcat-embed-el-8.5.4.jar
BOOT-INF/lib/tomcat-embed-core-8.5.4.jar
BOOT-INF/lib/commons-pool-1.6.jar
BOOT-INF/lib/thymeleaf-spring4-2.1.5.RELEASE.jar
BOOT-INF/lib/spring-context-4.3.2.RELEASE.jar
BOOT-INF/lib/thymeleaf-2.1.5.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-tomcat-1.4.0.RELEASE.jar
BOOT-INF/lib/thymeleaf-layout-dialect-1.4.0.jar
BOOT-INF/lib/spring-boot-autoconfigure-1.4.0.RELEASE.jar
BOOT-INF/lib/snakeyaml-1.17.jar
BOOT-INF/lib/spring-boot-starter-web-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-logging-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-webmvc-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-beans-4.3.2.RELEASE.jar
BOOT-INF/lib/slf4j-api-1.7.21.jar
BOOT-INF/lib/spring-jdbc-4.3.2.RELEASE.jar
BOOT-INF/lib/jackson-annotations-2.8.1.jar
BOOT-INF/lib/commons-dbcp-1.4.jar
BOOT-INF/lib/ognl-3.0.8.jar
BOOT-INF/lib/spring-boot-starter-1.4.0.RELEASE.jar
BOOT-INF/lib/tomcat-embed-websocket-8.5.4.jar
BOOT-INF/lib/spring-boot-starter-thymeleaf-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-tx-4.3.2.RELEASE.jar
BOOT-INF/lib/logback-classic-1.1.7.jar
BOOT-INF/lib/spring-boot-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-expression-4.3.2.RELEASE.jar
BOOT-INF/lib/logback-core-1.1.7.jar
BOOT-INF/lib/validation-api-1.1.0.Final.jar
BOOT-INF/lib/spring-core-4.3.2.RELEASE.jar
BOOT-INF/lib/jackson-databind-2.8.1.jar
BOOT-INF/lib/jcl-over-slf4j-1.7.21.jar
BOOT-INF/lib/log4j-over-slf4j-1.7.21.jar
BOOT-INF/lib/spring-aop-4.3.2.RELEASE.jar
BOOT-INF/lib/jboss-logging-3.3.0.Final.jar
}}}

== Add HTTPS/SSL support ==
Keystore creation
{{{#!highlight sh
#create keystore with key springboot
keytool -genkey -alias springboot -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore springboot.p12 -validity 3650

#Enter keystore password: 12345678
#Re-enter new password: 12345678
#What is your first and last name?
#  [Unknown]:  Spring Boot
#What is the name of your organizational unit?
#  [Unknown]:  Boot
#What is the name of your organization?
#  [Unknown]:  Boot
#What is the name of your City or Locality?
#  [Unknown]:  Lisbon
#What is the name of your State or Province?
#  [Unknown]:  Estremadura
#What is the two-letter country code for this unit?
#  [Unknown]:  PT
#Is CN=Spring Boot, OU=Boot, O=Boot, L=Lisbon, ST=Estremadura, C=PT correct?
#  [no]:  yes

#list keys 
keytool -list -v -keystore springboot.p12 -storetype pkcs12  
}}}

Code to add in Application class/configuration
{{{#!highlight java
// import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
// import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
// import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
// import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
// import java.io.FileNotFoundException;
// import org.apache.catalina.connector.Connector;
// import org.springframework.beans.factory.annotation.Value;
// import javax.inject.Inject;
// import org.springframework.util.ResourceUtils;

public class TCC implements TomcatConnectorCustomizer{
    private String keystoreFile;
    private String keystorePassword;
    private String keystoreType;
    private String keystoreAlias;

    public TCC(String keystoreFile, String keystorePassword, String keystoreType, String keystoreAlias){
        this.keystoreFile=keystoreFile;
        this.keystorePassword=keystorePassword;
        this.keystoreType=keystoreType;
        this.keystoreAlias=keystoreAlias;
    }

    public void customize(Connector connector){
        connector.setSecure(true);
        connector.setScheme("https");
        connector.setAttribute("keystoreFile", keystoreFile);
        connector.setAttribute("keystorePass", keystorePassword);
        connector.setAttribute("keystoreType", keystoreType);
        connector.setAttribute("keyAlias", keystoreAlias);
        connector.setAttribute("clientAuth", "false");
        connector.setAttribute("sslProtocol", "TLS");
        connector.setAttribute("SSLEnabled", true);
    }
}

public class ESCC implements EmbeddedServletContainerCustomizer{
    private String keystoreFile;
    private String keystorePassword;
    private String keystoreType;
    private String keystoreAlias;

    public ESCC(String keystoreFile, String keystorePassword, String keystoreType, String keystoreAlias){
        this.keystoreFile=keystoreFile;
        this.keystorePassword=keystorePassword;
        this.keystoreType=keystoreType;
        this.keystoreAlias=keystoreAlias;
    }

    public void customize(ConfigurableEmbeddedServletContainer container){
        TomcatEmbeddedServletContainerFactory containerFactory = (TomcatEmbeddedServletContainerFactory) container;

        TomcatConnectorCustomizer tcc = new TCC(keystoreFile,keystorePassword,keystoreType,keystoreAlias);
        containerFactory.addConnectorCustomizers(tcc);
    }
}

@Bean
@Inject
public EmbeddedServletContainerCustomizer containerCustomizer(
    @Value("${keystore.file}") String keystoreFile,
    @Value("${keystore.password}") String keystorePassword,
    @Value("${keystore.type}") String keystoreType,
    @Value("${keystore.alias}") String keystoreAlias) throws FileNotFoundException
{
    final String absoluteKeystoreFile = ResourceUtils.getFile(keystoreFile).getAbsolutePath();
    EmbeddedServletContainerCustomizer escc = new ESCC(absoluteKeystoreFile,keystorePassword,keystoreType,keystoreAlias);
    return escc;
}
}}}

pom.xml 
{{{#!highlight xml
<!-- Add dependency to support @Inject annotation -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>
}}}
 * Add server.port=8443 to application.properties or use system property -Dserver.port=8443
 * Run java -Dfilelog=/tmp/out.log -Dkeystore.file=springboot.p12 -Dkeystore.password=12345678 -Dkeystore.type=PKCS12 -Dkeystore.alias=springboot -jar target/test-spring-boot-0.1.0.jar
 * Access https://localhost:8443/greeting?name=x

== Environment variables ==

src/main/resources/application.properties
{{{
logging.file=/tmp/testout.log
server.port=8443
foo.bar=bohica
}}}

{{{#!highlight java
import javax.annotation.Resource;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Value;
//....................
    @Resource
    Environment env;
    @Value("${foo.bar}")
    private String fooBar;

    @RequestMapping(value="/env",produces="application/json")
    @ResponseBody
    public List<String> getEnv(){
      List<String> ret = new ArrayList<String>();
      ret.add( env.getProperty("foo.bar") );
      ret.add(fooBar);
      return ret;
    }
//....................
}}}

Override value in application.properties
{{{#!highlight sh
java -jar target/test-spring-boot-0.1.0.jar --foo.bar=snafu
}}}

It's also possible to override values with environment variables as for example 
 * FOO_BAR=snafu java -jar target/test-spring-boot-0.1.0.jar

The *.* should be replaced by *_* and the lowercase characters must be capitalized. 

Instead of using @Autowired or @Inject use @Resource (javax.annotation.Resource).

== Example JPA RabbitMQ HttpSession Redis ==
Structure
{{{
.
./pom.xml
./src
./src/main
./src/main/java
./src/main/java/hello
./src/main/java/hello/GreetingController.java
./src/main/java/hello/Application.java
./src/main/java/hello/ThreadTimer.java
./src/main/java/hello/WaitThread.java
./src/main/java/hello/Dummy.java
./src/main/java/hello/DummyDAO.java
./src/main/java/hello/DummyJPA.java
./src/main/java/hello/MessageHandler.java
./src/main/java/hello/BroadcastMessageHandler.java
./src/main/java/hello/ConfigHttpSession.java
./src/main/resources
./src/main/resources/templates
./src/main/resources/templates/greeting.html
./src/main/resources/application.properties
./src/main/resources/logback-spring.xml
./src/main/resources/test.sql
./src/main/resources/META-INF
./src/main/resources/META-INF/persistence.xml
}}}

WaitThread.java
{{{#!highlight java
package hello;

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

@Component
public class WaitThread extends Thread {
  private Logger logger;
  private boolean running;
  private Object monitor;

  @Autowired
  ThreadTimer timerThread;

  public WaitThread() {    
    this.logger = LoggerFactory.getLogger(WaitThread.class);
    logger.info("Created instance of " + this.getClass().getSimpleName());
    this.running = true;
    this.setName(this.getClass().getSimpleName() + "_" + this.getName());
    this.monitor=new Object();
  }

  @PostConstruct
  public void init() {
    this.timerThread.addSubscriber(this);
    logger.info("Starting the thread");
    this.start();
  }

  public void run() {
    while (running) {
      try {
        synchronized(this){
          this.wait();
          logger.info("Notification received.");
        }
      }
      catch (InterruptedException e) {
          logger.info("ThreadTimer interrupted exception:" + e.getMessage() );
      }
      catch (Exception e) {
          logger.info("WaitThread exception:" + e.getMessage() );
          stopRunning();
      }      
    }
    logger.info("Exited " + this.getClass().getSimpleName());
  }

  public void startRunning() {
    this.running = true;
  }

  public void stopRunning() {
    this.running = false;
  }
  
  public void destroy(){
    logger.info("Called destroy");
    this.stopRunning();
    this.interrupt();    
  }
}
}}}

ThreadTimer.java
{{{#!highlight java
package hello;

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;

@Component
public class ThreadTimer extends Thread {
  private int delaySeconds;
  private Logger logger;
  private boolean running;
  private Object monitor=new Object();
  private ArrayList<Object> subscribers;

  public ThreadTimer() {    
    this.logger = LoggerFactory.getLogger(ThreadTimer.class);
    logger.info("Created instance of " + this.getClass().getSimpleName());
    this.running = true;
    this.delaySeconds = 5 * 1000;
    this.setName(this.getClass().getSimpleName() + "_" + this.getName());
    this.subscribers = new ArrayList<Object>();
  }

  public void addSubscriber(Object subscriber){
    this.subscribers.add(subscriber);
  }

  @PostConstruct
  public void init() {
    logger.info("Starting the thread");
    this.start();
  }

  @Override
  public void run() {
    while (running) {
      try {
        Thread.sleep(this.delaySeconds);
        logger.info("Delay " + this.getClass().getSimpleName());

        for(Object o: this.subscribers){
          synchronized(o){
            o.notify();
          }
        }
      }
      catch (InterruptedException e) {
          logger.info("ThreadTimer interrupted exception:" + e.getMessage() );
      }
      catch (Exception e) {
          e.printStackTrace();
          logger.info("ThreadTimer exception:" + e.getMessage() );
          stopRunning();
      }      
    }
    logger.info("Exited " + this.getClass().getSimpleName());
  }

  public void startRunning() {
    this.running = true;
  }

  public void stopRunning() {
    this.running = false;
  }
  
  public void destroy(){
    logger.info("Called destroy");
    this.stopRunning();
    this.interrupt();    
  }
}
}}}

MessageHandler.java
{{{#!highlight java
package hello;

import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MessageHandler{
    private final Logger logger = LoggerFactory.getLogger(MessageHandler.class);
    
    public void handleMessage(byte[] message){
        String msg = new String(message, StandardCharsets.UTF_8);
        logger.info("Received msg " + msg   );
    }

    public void handleMessage(String message){
        logger.info("Received msg " + message   );
    }

}
}}}

GreetingController.java
{{{#!highlight java
package hello;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.ArrayList;
import org.springframework.web.bind.annotation.PathVariable;
//import javax.inject.Inject;
import org.springframework.core.env.Environment;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.Resource;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import javax.servlet.http.HttpSession;

@Controller
public class GreetingController {
    private final Logger logger = LoggerFactory.getLogger(GreetingController.class);
    @Resource
    DummyDAO dummyDAO;

    @Resource
    Environment env;

    @Resource
    RabbitTemplate rabbitTemplate;
 
    @Value("${instance.name}")
    String instanceName;
    
    public GreetingController(){
        logger.debug("Greeting controller created.");
    }

    @RequestMapping("/greeting")
    public String greeting(@RequestParam(value="name", required=false, defaultValue="World") String name, Model model  ) {        
        model.addAttribute("name", name);
        rabbitTemplate.convertAndSend("topicExchange","myQueue", name  );
        rabbitTemplate.convertAndSend("fanoutExchange","", name  );
        return "greeting";
    }

    @RequestMapping("/setvalue/{value}")
    @ResponseBody
    public String setValue(@PathVariable("value") String value, Model model,HttpSession httpSession) {
        httpSession.setAttribute("name", value );
        return httpSession.getId();
    }

    @RequestMapping("/getvalue")
    @ResponseBody
    public String setValue(Model model,HttpSession httpSession) {
        return (String) httpSession.getAttribute("name");        
    }
    
    @RequestMapping("/instancename")
    @ResponseBody
    public String setValue(Model model) {
        return this.instanceName;        
    }
    
    @RequestMapping("/greeting/add/{name}")
    public String addGreeting(@PathVariable("name") String name, Model model) {
        model.addAttribute("name", name);
        dummyDAO.addName(name);
        return "greeting";
    }
    
    @RequestMapping(value="/dummy",produces="application/json")
    @ResponseBody
    public List<DummyJPA> dummy(){
      return dummyDAO.getAllNames();
    }

    @RequestMapping(value="/dummyname",produces="application/json")
    @ResponseBody
    public String getDummyName(){
      return dummyDAO.getNameFromDummy();
    }

    @Value("${foo.bar}")
    private String fooBar;

    @RequestMapping(value="/env",produces="application/json")
    @ResponseBody
    public List<String> getEnv(){
      List<String> ret = new ArrayList<String>(); 
      ret.add( env.getProperty("foo.bar") );
      ret.add(fooBar);
      return ret;
    }
    
}
}}}

DummyJPA.java
{{{#!highlight java
package hello;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.NamedQueries;

@Entity
@NamedQueries({
    @NamedQuery(name="DummyJPA.getAll",  query="Select e From DummyJPA e")
})
public class DummyJPA{
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Long id;
  private String dummy;

  public DummyJPA(){
  } 

  public String getDummy(){
    return this.dummy;
  }

  public void setDummy(String dummy){
    this.dummy=dummy;
  }

  @Override
  public String toString(){
    return String.format("Dummy %s", this.dummy);
  }
}
}}}

DummyDAO.java
{{{#!highlight java
package hello;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.persistence.PersistenceContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.transaction.Transactional;
import java.util.List;

@Component
public class DummyDAO {
  @Autowired
  @Qualifier("jdbcTemplate")
  private JdbcTemplate jdbcTemplate;
  private Logger logger;

  @PersistenceContext(unitName="default")
  EntityManager em;

  public DummyDAO() {
    logger = LoggerFactory.getLogger(DummyDAO.class);
    logger.info("Created " + this.getClass().getSimpleName());
  }

  public String getNameFromDummy() {
    return this.jdbcTemplate.queryForObject("select name from dummy limit 1", String.class);
  }

  @Transactional
  public void addName(String name){
    DummyJPA djpa = new DummyJPA();
    djpa.setDummy(name);
    em.persist(djpa);
  }

  @Transactional
  public List<DummyJPA> getAllNames(){
    return em.createNamedQuery("DummyJPA.getAll",DummyJPA.class).getResultList();
  }

}
}}}

Dummy.java
{{{#!highlight java
package hello;

public class Dummy{
  private String fieldA;
  private String fieldB;
  
  public Dummy(){
  }

  public String getFieldA(){
    return fieldA;
  }
  
  public String getFieldB(){
    return fieldB;
  }
  
  public void setFieldA(String arg){
    fieldA = arg;
  }
  
  public void setFieldB(String arg){
    fieldB = arg;
  }
}

}}}

ConfigHttpSession.java
{{{#!highlight java
/* 
http://docs.spring.io/spring-session/docs/current/reference/html5/guides/boot.html

pkg install redis #freebsd
add redis_enable="YES" to /etc/rc.conf
/usr/local/etc/rc.d/redis start

/usr/local/etc/redis.conf
requirepass 12345678

keys *
hgetall <hashkey>

Same as in application.properties spring.redis.password=12345678

https://localhost:8443/setvalue/valx
147569e4-9e6d-4363-a373-b20261513f11

https://localhost:8443/getvalue
SESSION=147569e4-9e6d-4363-a373-b20261513f11

2 instances of spring boot using the same redis server
curl -k -v --cookie "SESSION=147569e4-9e6d-4363-a373-b20261513f11" https://localhost:8443/getvalue
curl -k -v --cookie "SESSION=147569e4-9e6d-4363-a373-b20261513f11" https://localhost:8444/getvalue 
*/
package hello;

import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@EnableRedisHttpSession
public class ConfigHttpSession{
    // springSessionRepositoryFilterbean is responsible for replacing the HttpSession with a custom implementation that is backed by Spring Session.    
}

}}}

BroadcastMessageHandler.java
{{{#!highlight java
package hello;

import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BroadcastMessageHandler{
    private final Logger logger = LoggerFactory.getLogger(BroadcastMessageHandler.class);
    
    public void handleMessage(byte[] message){
        String msg = new String(message, StandardCharsets.UTF_8);
        logger.info("Received msg " + msg   );
    }

    public void handleMessage(String message){
        logger.info("Received msg " + message   );
    }
}
}}}

Application.java
{{{#!highlight java
package hello;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import java.io.FileNotFoundException;
import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import javax.inject.Inject;
import javax.annotation.Resource;
import org.springframework.util.ResourceUtils;
import org.springframework.core.env.Environment;

//-- rabbit
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.AnonymousQueue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import java.net.URI;
//-- rabbit

@ComponentScan
@EnableAutoConfiguration
@EntityScan(basePackages="hello")
public class Application {
    @Resource
    Environment env;

    private static Logger logger;

    public static void main(String[] args) {
        logger = LoggerFactory.getLogger(Application.class);
        logger.info("Starting application");
        SpringApplication.run(Application.class, args);
    }

    @Bean(name="basicDataSource",destroyMethod="close") 
    public BasicDataSource createBasicDataSource(){
      logger.info("Creating basicDataSource");
      BasicDataSource bds = new BasicDataSource();
      // get config data from application.properties
      bds.setDriverClassName(env.getProperty("db.driverclass") );
      bds.setUrl(env.getProperty("db.jdbcurl") );
      bds.setUsername( env.getProperty("db.username") );
      bds.setPassword( env.getProperty("db.password") );
      bds.setInitialSize( Integer.parseInt( env.getProperty("db.initialsizeconnpool")) );
      return bds;
    }

    @Bean(name="jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(@Autowired @Qualifier("basicDataSource") BasicDataSource bds  ){
      logger.info("Creating jdbcTemplate");
      return new JdbcTemplate( bds );
    }

//---  
    public class TCC implements TomcatConnectorCustomizer{
        Environment env;

        public TCC(Environment env){ 
            this.env=env;  
        }  

        public void customize(Connector connector){
            String absoluteKeystoreFile = "";
            logger.info(String.format("Keystore file: %s" , env.getProperty("keystore.file")   )); 

            try{ 
                absoluteKeystoreFile = ResourceUtils.getFile(env.getProperty("keystore.file")).getAbsolutePath(); 
                logger.info(String.format("Absolute path keystore: %s" , absoluteKeystoreFile )  );
            }
            catch(Exception ex){
              logger.error( ex.getMessage() );
            }

            connector.setSecure(true);
            connector.setScheme("https");
            connector.setAttribute("keystoreFile", absoluteKeystoreFile);
            connector.setAttribute("keystorePass", env.getProperty("keystore.password"));
            connector.setAttribute("keystoreType", env.getProperty("keystore.type"));
            connector.setAttribute("keyAlias", env.getProperty("keystore.alias")  );
            connector.setAttribute("clientAuth", "false");
            connector.setAttribute("sslProtocol", "TLS");
            connector.setAttribute("SSLEnabled", true);
        } 
    }

    public class ESCC implements EmbeddedServletContainerCustomizer{
        Environment env;

        public ESCC(Environment env){ 
            this.env=env;  
        }

        public void customize(ConfigurableEmbeddedServletContainer container){
            TomcatEmbeddedServletContainerFactory containerFactory = (TomcatEmbeddedServletContainerFactory) container;            
            TomcatConnectorCustomizer tcc = new TCC(env);
            containerFactory.addConnectorCustomizers(tcc);
        }
    }

    @Bean(name="containerCustomizer")
    public EmbeddedServletContainerCustomizer containerCustomizer() 
    throws FileNotFoundException {
        logger.info("Creating containerCustomizer");
        EmbeddedServletContainerCustomizer escc = new ESCC(env);
        return escc;
    }
//---

//---- rabbit

//import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
    @Bean(name="cachingConnectionFactory")
    CachingConnectionFactory createCachingConnectionFactory(){
        logger.info("Creating cachingConnectionFactory");
        String uri = env.getProperty("rabbitmq.uri");
        URI rabbitmqUri=null;
        
        try{
            rabbitmqUri = new URI(uri);
        }
        catch(Exception ex){
            ex.printStackTrace();
        }
        
        CachingConnectionFactory ret = new CachingConnectionFactory(rabbitmqUri);
        logger.info(String.format("Created cachingConnectionFactory with hashCode %d" , ret.hashCode() ) );
        return ret;
    }
    
    @Bean(name="myQueue")
    Queue createMyQueue() {
        logger.info("Creating myQueue");
        return new Queue("myQueue", false);
    }

    @Bean(name="topicExchange")
    TopicExchange createTopicExchange() {
        logger.info("Creating topicExchange");
        return new TopicExchange("topicExchange");
    }

    @Bean(name="bindingQueue")
    Binding binding(@Qualifier("myQueue")Queue queue, @Qualifier("topicExchange")TopicExchange exchange) {
        logger.info("Creating bindingQueue");
        return BindingBuilder.bind(queue).to(exchange).with(queue.getName());
    }

    @Bean(name="workQueueContainer")
    SimpleMessageListenerContainer createWorkQueueContainer(ConnectionFactory connectionFactory, @Autowired @Qualifier("listenerAdapter") MessageListenerAdapter listenerAdapter,@Qualifier("myQueue") Queue queue) {
        logger.info("Creating createWorkQueueContainer for work queue");
        logger.info(String.format("Using ConnectionFactory with hashCode %d" , connectionFactory.hashCode() ) );
        
        SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
        smlc.setConnectionFactory(connectionFactory);
        smlc.setQueueNames(queue.getName() );
        smlc.setMessageListener(listenerAdapter);
        return smlc;
    }
    
    @Bean(name="listenerAdapter")    
    MessageListenerAdapter listenerAdapter(@Autowired @Qualifier("messageHandler")MessageHandler messageHandler) {
        logger.info("Creating listenerAdapter");
        return new MessageListenerAdapter(messageHandler);
    }

    @Bean(name="messageHandler")
    MessageHandler createMessageHandler() {
        logger.info("Creating messageHandler");
        return new MessageHandler();
    }
    
    //--------    
    @Bean(name="fanoutExchange")
    FanoutExchange createFanoutExchange() {
        logger.info("Creating fanoutExchange");
        return new FanoutExchange("fanoutExchange");
    }

    @Bean(name="anonymousQueue1")
    AnonymousQueue createAnonymousQueue1() {
        logger.info("Creating anonQueue1");
        return new AnonymousQueue();
    }

    @Bean(name="bindingTopic")
    Binding bindingTopic(@Qualifier("anonymousQueue1")AnonymousQueue queue, @Qualifier("fanoutExchange")FanoutExchange exchange) {
        logger.info("Creating bindingTopic");
        return BindingBuilder.bind(queue).to(exchange);
    }

    @Bean(name="broadcastMessageHandler")
    BroadcastMessageHandler createBroadcastMessageHandler() {
        logger.info("Creating broadcastMessageHandlermessageHandler");
        return new BroadcastMessageHandler();
    }
    
    @Bean(name="broadcastListenerAdapter")    
    MessageListenerAdapter createBroadcastlistenerAdapter(@Autowired @Qualifier("broadcastMessageHandler")BroadcastMessageHandler broadcastMessageHandler) {
        logger.info("Creating broadcastListenerAdapter");
        return new MessageListenerAdapter(broadcastMessageHandler);
    }

    @Bean(name="topicContainer")
    SimpleMessageListenerContainer createTopicContainer(ConnectionFactory connectionFactory, @Autowired @Qualifier("broadcastListenerAdapter") MessageListenerAdapter listenerAdapter, @Qualifier("anonymousQueue1")  AnonymousQueue anonQueue) {
        logger.info("Creating container for topic");
        logger.info(String.format("Using ConnectionFactory with hashCode %d" , connectionFactory.hashCode() ) );
        
        SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
        smlc.setConnectionFactory(connectionFactory);
        smlc.setQueueNames( anonQueue.getName() );
        smlc.setMessageListener(listenerAdapter);
        return smlc;
    }
    
    //----
//---- rabbit
}

}}}

persistence.xml
{{{#!highlight xml
<persistence>
    <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
        <jta-data-source>basicDataSource</jta-data-source>
        <properties>
            <!-- <property name="hibernate.hbm2ddl.auto" value="create-drop" /> -->
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
            <property name="hibernate.show_sql" value="false" />
        </properties>
    </persistence-unit>
</persistence>
}}}

greeting.html
{{{#!highlight html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Getting Started: Serving Web Content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>
}}}

test.sql
{{{#!highlight sql
--JDBC URL: jdbc:mariadb://localhost:3306/springmvchtml
--mysql -u root -p
create database springmvchtml;
create user 'usertest'@'%' identified by '????';
create user 'usertest'@'localhost' identified by '????';
grant all on springmvchtml.* to 'usertest'@'%';
grant all on springmvchtml.* to 'usertest'@'localhost';
show grants for 'usertest'@'%';
show grants for 'usertest'@'localhost'; 
create table springmvchtml.dummy (name varchar(255) ) ;
insert into springmvchtml.dummy (name) values('aaaa');
insert into springmvchtml.dummy (name) values('bbbb');
commit;
-- mysql -u usertest -p
}}}

logback-spring.xml
{{{#!highlight xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}"/>
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
-->    
    <!-- override spring logback default behaviour -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
	<file>${filelog}</file>
	<encoder>
		<pattern>%date{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
	</encoder>
    </appender>

    <appender name="GREETFILE" class="ch.qos.logback.core.FileAppender">
        <file>/tmp/greet.log</file>
        <encoder>
                <pattern>%date{ISO8601} [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
	<layout class="ch.qos.logback.classic.PatternLayout">
		<Pattern>%yellow(%date{ISO8601}) %green([%thread]) %highlight(%-5level) %cyan(%logger{35}) - %white(%msg%n) </Pattern>
	</layout>
    </appender>

    <root level="INFO">
        <appender-ref ref="FILE" />
        <appender-ref ref="CONSOLE"/>
    </root>

    <logger name="hello.GreetingController" level="debug" additivity="false">
        <appender-ref ref="GREETFILE"/>
	<appender-ref ref="CONSOLE" />
    </logger>

</configuration>
}}}

application.properties
{{{
logging.file=/tmp/testout.log
server.port=8443
foo.bar=bohica
instance.name=node1
keystore.file=springboot.p12
keystore.password=????
keystore.type=PKCS12
keystore.alias=springboot

db.driverclass=org.mariadb.jdbc.Driver
db.jdbcurl=jdbc:mariadb://localhost:3306/springmvchtml
db.username=usertest
db.password=????
db.initialsizeconnpool=3
#rabbimq parameters
rabbitmq.uri=amqp://????:????@localhost:5672
#redis 
spring.redis.host=localhost
spring.redis.password=????????
spring.redis.port=6379
}}}

pom.xml
{{{#!highlight xml
<?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>hello</groupId>
    <artifactId>test-spring-boot</artifactId>
    <version>0.1.0</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <version>1.4.4</version>
        </dependency>
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency> 
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <!-- http session redis -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
            <version>1.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>        
        <!-- http session redis -->
    </dependencies>
    <properties>
        <start-class>hello.Application</start-class>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestone</id>
            <url>http://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestone</id>
            <url>http://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>
</project>
}}}

Libs
{{{#!highlight sh
-rw-r--r--  1 vitor  vitor    37M Sep 29 15:49 test-spring-boot-0.1.0.jar
jar tf test-spring-boot-0.1.0.jar | grep BOOT | grep jar
BOOT-INF/lib/spring-session-1.2.2.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-data-redis-1.4.0.RELEASE.jar
BOOT-INF/lib/jackson-core-2.8.1.jar
BOOT-INF/lib/javassist-3.20.0-GA.jar
BOOT-INF/lib/jul-to-slf4j-1.7.21.jar
BOOT-INF/lib/groovy-2.4.7.jar
BOOT-INF/lib/spring-amqp-1.6.1.RELEASE.jar
BOOT-INF/lib/hibernate-validator-5.2.4.Final.jar
BOOT-INF/lib/classmate-1.3.1.jar
BOOT-INF/lib/hibernate-commons-annotations-5.0.1.Final.jar
BOOT-INF/lib/tomcat-embed-el-8.5.4.jar
BOOT-INF/lib/jedis-2.8.2.jar
BOOT-INF/lib/httpcore-4.4.5.jar
BOOT-INF/lib/antlr-2.7.7.jar
BOOT-INF/lib/xml-apis-1.4.01.jar
BOOT-INF/lib/spring-boot-starter-tomcat-1.4.0.RELEASE.jar
BOOT-INF/lib/thymeleaf-layout-dialect-1.4.0.jar
BOOT-INF/lib/spring-boot-autoconfigure-1.4.0.RELEASE.jar
BOOT-INF/lib/snakeyaml-1.17.jar
BOOT-INF/lib/http-client-1.0.0.RELEASE.jar
BOOT-INF/lib/httpclient-4.5.2.jar
BOOT-INF/lib/spring-boot-starter-logging-1.4.0.RELEASE.jar
BOOT-INF/lib/tomcat-jdbc-8.5.4.jar
BOOT-INF/lib/spring-beans-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-jdbc-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-jdbc-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-amqp-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-aspects-4.3.2.RELEASE.jar
BOOT-INF/lib/commons-dbcp-1.4.jar
BOOT-INF/lib/spring-data-commons-1.12.2.RELEASE.jar
BOOT-INF/lib/spring-context-support-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-data-keyvalue-1.1.2.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-1.4.0.RELEASE.jar
BOOT-INF/lib/tomcat-embed-websocket-8.5.4.jar
BOOT-INF/lib/commons-logging-1.2.jar
BOOT-INF/lib/spring-tx-4.3.2.RELEASE.jar
BOOT-INF/lib/validation-api-1.1.0.Final.jar
BOOT-INF/lib/spring-core-4.3.2.RELEASE.jar
BOOT-INF/lib/spring-boot-starter-aop-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-data-jpa-1.10.2.RELEASE.jar
BOOT-INF/lib/commons-pool2-2.4.2.jar
BOOT-INF/lib/dom4j-1.6.1.jar
BOOT-INF/lib/tomcat-juli-8.5.4.jar
BOOT-INF/lib/jcl-over-slf4j-1.7.21.jar
BOOT-INF/lib/log4j-over-slf4j-1.7.21.jar
BOOT-INF/lib/aspectjweaver-1.8.9.jar
BOOT-INF/lib/spring-data-redis-1.7.2.RELEASE.jar
BOOT-INF/lib/spring-web-4.3.2.RELEASE.jar
BOOT-INF/lib/unbescape-1.1.0.RELEASE.jar
BOOT-INF/lib/mariadb-java-client-1.4.4.jar
BOOT-INF/lib/amqp-client-3.6.3.jar
BOOT-INF/lib/spring-oxm-4.3.2.RELEASE.jar
BOOT-INF/lib/tomcat-embed-core-8.5.4.jar
BOOT-INF/lib/spring-orm-4.3.2.RELEASE.jar
BOOT-INF/lib/hibernate-jpa-2.1-api-1.0.0.Final.jar
BOOT-INF/lib/commons-pool-1.6.jar
BOOT-INF/lib/thymeleaf-spring4-2.1.5.RELEASE.jar
BOOT-INF/lib/hibernate-core-5.0.9.Final.jar
BOOT-INF/lib/spring-context-4.3.2.RELEASE.jar
BOOT-INF/lib/thymeleaf-2.1.5.RELEASE.jar
BOOT-INF/lib/commons-codec-1.10.jar
BOOT-INF/lib/spring-boot-starter-web-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-webmvc-4.3.2.RELEASE.jar
BOOT-INF/lib/slf4j-api-1.7.21.jar
BOOT-INF/lib/javax.inject-1.jar
BOOT-INF/lib/jackson-annotations-2.8.1.jar
BOOT-INF/lib/jandex-2.0.0.Final.jar
BOOT-INF/lib/spring-messaging-4.3.2.RELEASE.jar
BOOT-INF/lib/ognl-3.0.8.jar
BOOT-INF/lib/hibernate-entitymanager-5.0.9.Final.jar
BOOT-INF/lib/spring-boot-starter-thymeleaf-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-retry-1.1.3.RELEASE.jar
BOOT-INF/lib/javax.transaction-api-1.2.jar
BOOT-INF/lib/logback-classic-1.1.7.jar
BOOT-INF/lib/spring-boot-starter-data-jpa-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-boot-1.4.0.RELEASE.jar
BOOT-INF/lib/spring-expression-4.3.2.RELEASE.jar
BOOT-INF/lib/logback-core-1.1.7.jar
BOOT-INF/lib/jackson-databind-2.8.1.jar
BOOT-INF/lib/spring-rabbit-1.6.1.RELEASE.jar
BOOT-INF/lib/spring-aop-4.3.2.RELEASE.jar
BOOT-INF/lib/jboss-logging-3.3.0.Final.jar
}}}

== springTestApp ==

=== pom.xml ===
{{{#!highlight xml
<?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>org.allowed.bitarus</groupId>
  <artifactId>springTestApp</artifactId>
  <version>0.1.0</version>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.2.RELEASE</version>
  </parent>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>
  <properties>
    <start-class>org.allowed.bitarus.springTestApp.Application</start-class>
  </properties>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <id>spring-releases</id>
      <url>http://repo.spring.io/libs-release</url>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>spring-releases</id>
      <url>http://repo.spring.io/libs-release</url>
    </pluginRepository>
  </pluginRepositories>
</project>
}}}

=== src/main/java/org/allowed/bitarus/springTestApp/Application.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Future;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@ComponentScan
@EnableAutoConfiguration
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    @Bean(name="tasksFutures")
    public ArrayList<Future<Integer>> createTasksFutures(){
      System.out.println("Creating tasksFutures");
      return new ArrayList<Future<Integer>>();
    }
    
    @Bean(name="executors")
    public ExecutorService createExecutors(){
      return Executors.newFixedThreadPool(5);         
    }
}

}}}

=== src/main/java/org/allowed/bitarus/springTestApp/Tasker.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@Component("tasker")
public class Tasker {
  private ExecutorService _es;
  
  public Tasker(@Qualifier("executors") ExecutorService es){
    System.out.println("Created Tasker !!!!");  
    _es = es;
  }
  
  public Future<Integer> addTask(TaskX taskx){
    Future<Integer> f = _es.submit( taskx );
    return f;
  }
}
}}}

=== src/main/java/org/allowed/bitarus/springTestApp/Receiver.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.annotation.PostConstruct;

@Component
public class Receiver {
  private Tasker _t;
  private ArrayList<Future<Integer>> _tasks;
  
  public Receiver(@Qualifier("tasker")Tasker t, @Qualifier("tasksFutures") ArrayList<Future<Integer>> tasks ){
    System.out.println("Created receiver !!!!"); 
    _t = t;
    _tasks = tasks;
    // wait for all tasks ...
    /*
    boolean loop=true;
    while(loop){
      boolean allDone=true;
      for(Future<Integer> f : tasks){
        if(f.isDone()==false){
          allDone=false;            
        }
      }
      
      if(allDone){
        loop=false;        
      }
      
      try{ 
        Thread.sleep(1000);           
      }
      catch(Exception ex){          
      }
    }
    
    for(Future<Integer> f : tasks){
      try{  
        Integer res = f.get();
        System.out.println(res);
      }
      catch(Exception ex){            
      }
    }    */
  }    
  
  @PostConstruct
  public void init(){   
    System.out.println("Init post construct");   
    for(int i=0;i<10;i++){ 
        _tasks.add( _t.addTask( new TaskX(5,6) ) );        
    }      
  }
}
}}}

=== TaskX.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import java.util.concurrent.Callable;

public class TaskX implements Callable<Integer>{
  private int _x;  
  private int _y;
    
  public TaskX(int x,int y){
    _x=x;
    _y=y;
  }  
    
  public Integer call() {
    try { 
       Thread.sleep(5000); 
    }
    catch(Exception ex){           
    }
    int sum = _x+_y;
    System.out.println("TID:"+Thread.currentThread().getId() + " sumVal:" + sum );
    return sum;
  }      
}

}}}

== Example with MongoDB ==

Think about using '''spring.data.mongodb.uri'''. 

=== pom.xml ===
{{{#!highlight xml
<?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>org.allowed.bitarus</groupId>
  <artifactId>springTestApp</artifactId>
  <version>0.1.0</version>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.2.RELEASE</version>
  </parent>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-mongodb</artifactId>
      <version>1.9.6.RELEASE</version>
    </dependency>        
  </dependencies>
  <properties>
    <start-class>org.allowed.bitarus.springTestApp.Application</start-class>
  </properties>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
  <repositories>
    <repository>
      <id>spring-releases</id>
      <url>http://repo.spring.io/libs-release</url>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>spring-releases</id>
      <url>http://repo.spring.io/libs-release</url>
    </pluginRepository>
  </pluginRepositories>
</project>
}}}

=== src/main/resources/application.properties ===
{{{#!highlight bash
# disable web container/server
spring.main.web-environment=false
threadpool.nrExecutors=5
mongox.host=127.0.0.1
mongox.port=27017
mongox.database=mydatabase
}}}

=== src/main/java/org/allowed/bitarus/springTestApp/Application.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.Future;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.data.mongodb.core.MongoTemplate;
import com.mongodb.Mongo;
import org.springframework.core.env.Environment;
import javax.annotation.Resource;

@ComponentScan
@EnableAutoConfiguration
@SpringBootApplication
@EnableMongoRepositories // search for MongoDB reps
public class Application {
    @Resource
    Environment _env;
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    @Bean(name="tasksFutures")
    public ArrayList<Future<Integer>> createTasksFutures(){
      System.out.println("Creating tasksFutures");
      return new ArrayList<Future<Integer>>();
    }
    
    @Bean(name="executors")
    public ExecutorService createExecutors(){
      System.out.println("Creating executors");
      System.out.println(_env.getProperty("threadpool.nrExecutors"));
      return Executors.newFixedThreadPool(5);         
    }
    
    @Bean(name="mongoTemplate")     
    public MongoTemplate mongoTemplate() throws Exception {
        System.out.println("Creating mongoTemplate");
        System.out.println(_env.getProperty("mongox.host"));
        System.out.println(_env.getProperty("mongox.port"));
        System.out.println(_env.getProperty("mongox.database"));
        Mongo mongo = new Mongo( _env.getProperty("mongox.host"), Integer.parseInt(_env.getProperty("mongox.port") ) );        
        return new MongoTemplate(mongo, _env.getProperty("mongox.database") );
    }     
}


}}}

=== src/main/java/org/allowed/bitarus/springTestApp/Tasker.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import org.springframework.stereotype.Component;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

@Component("tasker")
public class Tasker {
  private ExecutorService _es;
  
  public Tasker(@Qualifier("executors") ExecutorService es){
    System.out.println("Created Tasker !!!!");  
    _es = es;
  }
  
  public Future<Integer> addTask(TaskX taskx){
    Future<Integer> f = _es.submit( taskx );
    return f;
  }
}
}}}

=== src/main/java/org/allowed/bitarus/springTestApp/Result.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import org.springframework.data.annotation.Id;

public class Result {
    @Id
    private String id;
    private int sumResult;

    public Result(){}    
    public int getSumResult(){return this.sumResult;}
    public void setSumResult(int sumResult){this.sumResult=sumResult;}
    public String getId(){return this.id;}
    public void setId(String id){this.id=id;}   
}
}}}

=== src/main/java/org/allowed/bitarus/springTestApp/Receiver.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.MongoTemplate;

@Component
public class Receiver {
  private Tasker _t;
  private ArrayList<Future<Integer>> _tasks;
  private ResultRepository _repository;
  
  public Receiver(@Qualifier("tasker")Tasker t, @Qualifier("tasksFutures") ArrayList<Future<Integer>> tasks, ResultRepository repository ){
    System.out.println("Created receiver !!!!"); 
    _t = t;
    _tasks = tasks;
    _repository = repository;
  }    
  @Autowired
  private MongoTemplate mongoTemplate;
  
  @PostConstruct
  public void init(){   
    System.out.println("Init post construct");   
    for(int i=0;i<10;i++){ 
        _tasks.add( _t.addTask( new TaskX(5,6) ) );        
    }      
    
    // wait for all tasks ...
    boolean loop=true;
    while(loop){
      boolean allDone=true;
      for(Future<Integer> f : _tasks){
        if(f.isDone()==false){
          allDone=false;            
        }
      }
      
      if(allDone){
        loop=false;        
      }
      
      try{ 
        Thread.sleep(1000);           
      }
      catch(Exception ex){          
      }
    }
    
    for(Future<Integer> f : _tasks){
      try{  
        Integer res = f.get();
        System.out.println(res);
        Result r = new Result();
        r.setSumResult(res);
        _repository.save(r);
      }
      catch(Exception ex){            
      }
    }    
    
    System.out.println( _repository.count() );
    List<Result> res = _repository.findBySumResult(11);
    for(Result r : res){
        r.setSumResult((int)System.currentTimeMillis());
        _repository.save(r);
    }   
    
    System.out.println("mongoTemplateCount " + mongoTemplate.count(new Query() ,Result.class) );
  }
}

}}}

=== src/main/java/org/allowed/bitarus/springTestApp/ResultRepository.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;
/*
 Result -> class to store
 String -> id in collection type
 Collection result in database mydatabase
 { "_id" : ObjectId("58828e2822e1a609d96a5e52"), "_class" : "org.allowed.bitarus.springTestApp.Result", "sumResult" : 11 }
 */
public interface ResultRepository extends MongoRepository<Result, String> {
    public List<Result> findBySumResult(int sumResult);
}
}}}

=== src/main/java/org/allowed/bitarus/springTestApp/TaskX.java ===
{{{#!highlight java
package org.allowed.bitarus.springTestApp;

import java.util.concurrent.Callable;

public class TaskX implements Callable<Integer>{
  private int _x;  
  private int _y;
    
  public TaskX(int x,int y){
    _x=x;
    _y=y;
  }  
    
  public Integer call() {
    try { 
       Thread.sleep(5000); 
    }
    catch(Exception ex){           
    }
    int sum = _x+_y;
    System.out.println("TID:"+Thread.currentThread().getId() + " sumVal:" + sum );
    
    return sum;
  }      
}

}}}

== Integration tests ==
Integration tests setup and start Spring context before running the tests.

{{{#!highlight java
// <groupId>org.springframework.boot</groupId>
// <artifactId>spring-boot-starter-test</artifactId>
@Test // test case
@RunWith(SpringRunner.class)
@MockBean // inject mocked beans
// Mockito, given , willReturn Mocks simulates real objects 
@SpyBean
// create proxy for a real object
@SpringBootTest // test application context
@ActiveProfiles("test") // use application properties for test profile 
@WebFluxTest  // web flux controllers tests
@WebMvcTest   // web tests

//Mockito mocks
doNothing().when(objx).objMethod()
doReturn(response).when(objx).objMethod()
doThrow(Exception.class).when(objx).objMethod()
doCallRealMethod().when(objx).objMethod()

// application-<profile>.properties
// application-<profile>.yml 
application-test.properties
application-test.yml 

// org.springframework.test.util.ReflectionTestUtils
// ReflectionTestUtils is a collection of reflection-based utility methods for use in unit and integration testing scenarios. 

ReflectionTestUtils.setField(obj,"fieldname",value)
ReflectionTestUtils.invokeMethod(obj,"methodName",args)
}}}

== JPARepository ==
 * https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html
Captures the domain type to manage as well as the domain type's id type. 

 * https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Repository.html
Mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects. Specialization of @Component.

{{{#!highlight java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public Repo implements  JpaRepository<EntityX,Long>{
}
}}}

== ConfigurationProperties ==
 * https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/ConfigurationProperties.html
 * https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/EnableConfigurationProperties.html
 * Annotation for externalized configuration.
 * @EnableConfigurationProperties
 * @ConfigurationProperties
{{{#!highlight sh
@ConfigurationProperties(prefix = "server")
public class ServerProperties {
    private Map<String, String> app;
    private Map<String, List<String>> config;
}}}