SessionLocaleResolver langage changed but not see

I use spring boot 2, spring security. I create cache for User. In my user class, I have a lang field: FR, EN... That allow user to select langage ui they want.

I use spring boot 2, spring security. I create cache for User. In my user class, I have a lang field: FR, EN... That allow user to select langage ui they want.

Actually, user have possibility to change it's lang.

@EnableCaching
public class CacheConfig {
@Bean
public UserCache userCache() throws Exception {

    return new SpringCacheBasedUserCache(new ConcurrentMapCache("userCache"));

}

}

public class I18nLocaleResolver extends SessionLocaleResolver {

@Override
public Locale resolveLocale(HttpServletRequest request) {

    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if (auth != null && !"anonymousUser".equals(auth.getPrincipal())) {
        CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();

        String lang = user.getLang();
        if (lang == null) {
            lang = LanguagesEnum.FR.getLanguage();
        }
        Locale locale = new Locale(lang);

        WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);

        return locale;
    }

    Locale locale = (Locale) WebUtils.getSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME);
    if (locale == null) {
        locale = determineDefaultLocale(request);
    }
    WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);

    return locale;
}

@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
    WebUtils.setSessionAttribute(request, LOCALE_SESSION_ATTRIBUTE_NAME, locale);

}

}

In UserDetailsServiceImpl

@Transactional
@Override
public void updateLang(Integer userId, String newLanguage) {
Optional<Users> optUserApp = repository.findById(userId);

    if (!optUserApp.isPresent()) {
        throw new UsernameNotFoundException("Username id " + userId + " not found");
    }

    Users user = optUserApp.get();

    user.setLang(newLanguage);

    repository.save(user);

    userCache.removeUserFromCache(user.getUsername());
}

Scenario

User lang is EN, so it change it for FR, we passe by updateLang method, change it's done on the db side, after debugger go to I18nLocaleResolver. At this place it's the old value who are see (EN). It's there a way to refresh that without logout user?

Spring Boot and OAuth2: Getting the Authorization Code

Spring Boot and OAuth2: Getting the Authorization Code

Getting the Authorization Code. Need help implementing Spring Boot and OAuth2? In this tutorial, we look at getting the authorization code grant for Spring Boot and OAuth2, implementing the Client Application and Resource

Getting the Authorization Code. Need help implementing Spring Boot and OAuth2? In this tutorial, we look at getting the authorization code grant for Spring Boot and OAuth2, implementing the Client Application and Resource

To do this, we will be implementing the Client Application and Resource Server. The flow we will be implemented as follows:

  • The Resource Owner will ask the Client Application to get data from the Resource Server.
  • The Resource Server asks the Resource Owner to authenticate itself and for the authorization to share data.
  • After successful authentication, the Resource Server shares an authorization code with the client application

Let’s begin, shall we?

Resource Server Application

In another previous tutorial, we implemented an application with a Simple Login Page using Spring Boot Security. We will quickly create a similar project, which will authenticate and return JSON data.

We will be configuring the authorization server. The Maven project will be as follows:

The pom.xml will add the spring-security-oauth2 dependency:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.oauth</groupId>
    <artifactId>boot-sec</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>boot-resource-server</name>
    <description>Demo project for Spring Boot OAuth</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Next, we need to define the Spring Boot bootstrap class with the SpringBootApplication annotation.

package com.javainuse;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootResourceServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootResourceServerApplication.class, args);
    }
}

Define the model class Employee. We will also be returning the model class as a JSON response.

package com.javainuse.model;

public class Employee {

    private String empId;
    private String empName;

    public String getEmpId() {
        return empId;
    }

    public void setEmpId(String empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    @Override
    public String toString() {
        return "Employee [empId=" + empId + ", empName=" + empName + "]";
    }

}

Define the controller that exposes a GET REST endpoint to return JSON as:

package com.javainuse.controllers;

import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.javainuse.model.Employee;

@Controller
public class EmployeeController {

    @RequestMapping(value = "/user/getEmployeesList", produces = "application/json")
    @ResponseBody
    public List<Employee> getEmployeesList() {
        List<Employee> employees = new ArrayList<>();
        Employee emp = new Employee();
        emp.setEmpId("emp1");
        emp.setEmpName("emp1");
        employees.add(emp);
        return employees;

    }

}

Finally, we will be configuring security. In this configuration, we specify which URLs are to be intercepted and which URLs can be accessed by which users having which roles.

package com.javainuse.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class EmployeeSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/user/getEmployeesList")
            .hasAnyRole("ADMIN").anyRequest().authenticated().and().formLogin()
            .permitAll().and().logout().permitAll();

        http.csrf().disable();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationMgr) throws Exception {
        authenticationMgr.inMemoryAuthentication().withUser("admin").password("admin")
            .authorities("ROLE_ADMIN");
    }
}

Next, we will configure an authorization server using the EnableAuthorizationServer annotation.

The server is customized by extending the class AuthorizationServerConfigurerAdapter, which provides empty method implementations for the interface AuthorizationServerConfigurer.

The authorization server does not secure the authorization endpoint, i.e. /oauth/authorize. The configure method here injects the Spring Security authentication manager.

Using the in-memory client service, we set up the clients that can access the server.

package com.javainuse.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("javainuse").secret("secret").authorizedGrantTypes("authorization_code")
            .scopes("read").authorities("CLIENT");
    }
}

Client Application

We will create the client application. This application will ask the Resource Server we created above for JSON data.

As explained previously, we have assumed that this Client Application is already registered to the Resource Server and has received the client id as ‘javainuse’ and secret key as ‘secret.’ 

According to the OAuth spec, it should ask for authorization at the default URI /authorize.

We can change this default URI, according to the requirement, but we will be using the default one in this example.

Along with the default URI, we should also send the following parameters:

  • The Resource Owner will ask the Client Application to get data from the Resource Server.
  • The Resource Server asks the Resource Owner to authenticate itself and for the authorization to share data.
  • After successful authentication, the Resource Server shares an authorization code with the client application

The above parameters should be in the “application/x-www-form-urlencoded” format. So, let’s begin the implementation.

The Maven project is as follows:

The pom.xml is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.oauth</groupId>
<artifactId>boot-client-application</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>boot-client-application</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>


Now, we need to create the Controller class with the getEmployeeInfo method, which returns a page.

package com.javainuse.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class EmployeeController {

    @RequestMapping(value = "/getEmployees", method = RequestMethod.GET)
    public ModelAndView getEmployeeInfo() {
        return new ModelAndView("getEmployees");
    }
}


Next, define the following properties:

spring.mvc.view.prefix:/WEB-INF/jsp/
spring.mvc.view.suffix:.jsp

server.port:8090


Then, create the Spring Boot bootstrap class with the SpringBootApplication annotation.

package com.javainuse;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootFormHandingApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootFormHandingApplication.class, args);
    }
}


Next, create the getEmployees.jsp, which we will POST a request to /authorize the form of the encoded URL format.

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Get Employees</title>
</head>
<body>
    <h3 >Get Employee Info</h3>

    <div id="getEmployees">
        <form:form action="http://localhost:8080/oauth/authorize"
            method="post" modelAttribute="emp">
            <p>
                <label>Enter Employee Id</label>
                 <input type="text" name="response_type" value="code" /> 
                 <input type="text" name="client_id" value="javainuse" />
                 <input type="text" name="redirect_uri" value="http://localhost:8090/showEmployees" />
                 <input type="text" name="scope" value="read" /> 
                 <input type="SUBMIT" value="Get Employee info" />
        </form:form>
    </div>
</body>
</html>


Next, start the boot-resource-server and the boot-client-application. Go to localhost:8090/getEmployees. Then, click on the Get Employee Info button:

Enter the credentials as ‘admin’ and ‘admin.’

Then, authorize the Resource Owner to share the data.

We can see that the Resource Owner shares the authorization code with the Client Application.

You can download the source code here:

  • The Resource Owner will ask the Client Application to get data from the Resource Server.
  • The Resource Server asks the Resource Owner to authenticate itself and for the authorization to share data.
  • After successful authentication, the Resource Server shares an authorization code with the client application

Build a Simple CRUD App with Spring Boot and Vue.js

Build a Simple CRUD App with Spring Boot and Vue.js

Build a Simple CRUD App with Spring Boot and Vue.js. In this tutorial, you're going to build a complete CRUD web application using Vue.js for the client and Spring Boot as the resource server.

In this tutorial, you’re going to build a complete CRUD web application using Vue.js for the client and Spring Boot as the resource server. You’ll also secure the application using OAuth 2.0 and Okta.

CRUD is Create, Read, Update, and Delete. It’s kinda the “Hello World” of the server world. Like “Hello server!” If you can add data, updated it, read it, and delete it, you’ve pretty much got all the basic tools down for a REST interface or basic resource API.

The example application you’re going to build is a simple todo app. Typically these todo apps use data stored locally, but in this example you’re going to create, read, update, and delete todos on a Spring Boot resource server.

Excited? Great! Before we dive in, a quick introduction to the technologies involved.

What is Vue.js?

Vue is a JavaScript view library, like React and Angular. It’s designed to be incrementally adoptable, and the core library focuses solely on the view layer.

In my experience, Vue.js is a great alternative to React. I learned React first, and came to use Vue later. Like React, Vue uses a virtual DOM, provides reactive and composable view components, and enforces a strict one-way parent-child relationship when defining properties and state. This means that it is performant and it avoids a lot of the confusing state relationships that can occur without one-way data binding. However, unlike React, Vue uses templates instead of JSX (a potentially welcome and more immediately accessible option) and Vue gives you component scoped css using style tags in single-file components. In practice this difference is pretty great because in React the JSX and css-like syntax is close enough to HTML and CSS to be confusing but not actually the same, which creates problems initially (ever gone from a language that doesn’t require semicolons back to one that does? It’s something like that).

I find Vue to be a simpler, cleaner implementation. React requires a deep dive. You gotta take the red pill and go all the way. It’s a super powerful system, but you have to be all in. Vue is a little friendlier and a little easier to get started.

About Spring Boot

The server technology you’re going to use is Spring Boot. Pure, unadulterated Spring (pre-Spring Boot) is a bit of a behemoth: super powerful but potentially time-sucking and frustrating. I’m pretty sure the whole computer conference phenomena came about so that people could learn and understand old-school Spring XML files. It certainly drove large sections of the computer publishing empires.

Spring Boot was Spring’s answer to this complexity (and to frameworks like Ruby on Rails and Grails). They did a great job of distilling down all of the power of Spring into a simple, quick, easy-to-use web framework. With a ridiculously small number of lines of code and a few annotations, you can have a fully functioning resource server.

Plus, when you’re ready, f have all the power of Spring under the hood, just waiting.

For this project you’re going to need a server and client project. Create a root project directory called SpringBootVueApplication, and under that directory create two subdirectories: client and server.

client will be your Vue.js client application.

server will be the Spring Boot resource server.

Create Your Spring Boot App

Let’s start by creating the Spring Boot app using the Spring Initializer.

Make the following selections:

  • Project Type: Gradle Project
  • Group: com.okta
  • Artifact: spring-boot-vue
  • Dependencies: JPA, H2, Web, Rest Repositories, Lombok

Download the file and unzip the contents to your SpringBootVueApplication/server directory.

First off, let’s start with something simple. Change the default port from 8080 to 9000 (so that it doesn’t conflict with the Vue.js app client port in a bit).

Change the name of the server/src/main/resources/application.properties file to application.yml, and add the following line to it:

server:  
  port: 9000


Define the Todo Model Class

Let’s define the Todo model class file. This defines the data structure that your Spring Boot application will be using.

Create a Todo.java class in the com.okta.springbootvue package under src/main/java.

package com.okta.springbootvue;  

import lombok.*;  

import javax.persistence.Id;  
import javax.persistence.GeneratedValue;  
import javax.persistence.Entity;  

@Entity  
@Data  
@NoArgsConstructor  
public class Todo {  

  @Id @GeneratedValue  
  private Long id;  

  @NonNull
  private String title;  

  private Boolean completed = false;

}


This is pretty straight forward. You’re defining a data model with a three properties: an autogenerated id, a String title, and a true/false completed property.

Lombok is saving you a lot of wordy ceremony code defining getters and setters. Those are all the annotations tacked onto the class.

A whole lot of heavy hitting is happening here in the background that allows Spring Data and JPA to automatically map this class file to a database. This is a deep subject, and there are links at the end of the tutorial if you want to know more. For right now, it’s enough to know that the class above will be mapped to a database table in an in-memory H2 database, and each property in the class will become a table column. You get the in-memory database by default with the included dependency: H2. This is super convenient for tutorials and testing, but, obviously, for much more you’ll want to include a mapping to an actual persisted database.

Define the Database and REST Classes

Create a TodoRepository.java in the com.okta.springbootvue package.

package com.okta.springbootvue;  

import org.springframework.data.jpa.repository.JpaRepository;  
import org.springframework.data.rest.core.annotation.RepositoryRestResource;  

@RepositoryRestResource  
interface TodoRepository extends JpaRepository<Todo, Long> {}


This class is dead simple. Again, there’s a ton of stuff going on behind the scenes. All of the methods we’ll need for the REST API for our Todo app will actually be automatically generated for us here. You could, however, define some custom access methods in this class if you needed to.

Let’s also create a RestRepositoryConfigurator class in the same package.

package com.okta.springbootvue;

import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.stereotype.Component;

/**
 * IDs are not returned by RestRepository by default. I like them exposed so that the client can easily find
 * the ID of created and listed resources.
 * */
@Component
public class RestRepositoryConfigurator implements RepositoryRestConfigurer {

  @Override
  public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
    config.exposeIdsFor(Todo.class);
  }
}


This class is just a configuration class. The whole purpose of it is to tell Spring to return the data model instance IDs with the the object serializations (that way you can refer to them by ID from the client app, since this is going to be the UUID).

Test the Rest API Server

At this point, believe it or not, you have a working REST API.

Let’s test it out using HTTPie. If you don’t have HTTPie installed, install it using brew install httpie. Or head over to their website and make it happen. Or just follow along.

First off, start the server using ./gradlew bootRun.

You should see a lot of output that ends like this:

2018-11-08 21:20:36.614  INFO 56214 --- [nio-9000-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2018-11-08 21:20:36.615  INFO 56214 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-11-08 21:20:36.646  INFO 56214 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 31 ms
<=========----> 75% EXECUTING [2m 59s]
> :bootRun


Now perform a basic GET request on the server endpoint: http GET http://localhost:9000

HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
Date: Fri, 09 Nov 2018 03:44:37 GMT
Transfer-Encoding: chunked
{
  "_links": {
    "profile": {
      "href": "http://localhost:9000/profile"
    },
    "todos": {
      "href": "http://localhost:9000/todos{?page,size,sort}",
      "templated": true
    }
  }
}


The profile link has to do with the ALPS (Application-Level Profile Semantics). Take a look at the Spring docs on it. It’s a way to describe the available resources exposed by the REST API.

The todos link is the endpoint generated from the Todo class.

Take a look at that endpoint using a GET request. You can actually omit the “GET”, and the “http://localhost” since these are defaults with HTTPie.

$ http :9000/todos
HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
Date: Fri, 09 Nov 2018 03:50:12 GMT
Transfer-Encoding: chunked
{
  "_embedded": {
    "todos": []
  },
  "_links": {
    "profile": {
      "href": "http://localhost:9000/profile/todos"
    },
    "self": {
      "href": "http://localhost:9000/todos{?page,size,sort}",
      "templated": true
    }
  },
  "page": {
    "number": 0,
    "size": 20,
    "totalElements": 0,
    "totalPages": 0
  }
}


The _embedded.todos holds the data. But since there aren’t any todos yet, it’s empty.

You can POST some data to the server using the following command:

http POST :9000/todos title="Write Vue client app"

The output will show your new Todo has been added:

HTTP/1.1 201
Content-Type: application/json;charset=UTF-8
Date: Fri, 09 Nov 2018 03:51:22 GMT
Location: http://localhost:9000/todos/1
Transfer-Encoding: chunked
{
    "_links": {
        "self": {
            "href": "http://localhost:9000/todos/1"
        },
        "todo": {
            "href": "http://localhost:9000/todos/1"
        }
    },
    "completed": false,
    "id": 1,
    "title": "Write Vue client app"
}


Todo created! Now if you GET the /todos endpoint again, you’ll see your newly created todo.

$ http :9000/todos
HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
Date: Fri, 09 Nov 2018 03:54:40 GMT
Transfer-Encoding: chunked
{
"_embedded": {
  "todos": [
    {
      "id": 1,
      "title": "Write Vue client app",
      "completed": false,
      "_links": {
        "self": {
          "href": "http://localhost:9000/todos/1"
        },
        "todo": {
          "href": "http://localhost:9000/todos/1"
        }
      }
    }
  ]
},
...
}


Pretty amazing, huh? That’s a whole lot of functionality for not a lot of code. (It didn’t used to be that way, let me tell you. We used to have to code uphill both ways in the rain and in PERL using vi to get stuff like that working. And you could have painted a house with all the getters and setters and ceremony code. Hours and hours.)

Add CORS Filter to Your Spring Boot App

Before you move on to the Vue client app, there’s one more thing to update. Currently, the server application would throw a CORS error if you tried to use it with a single-page app framework like Vue. This can be fixed by adding a CORS filter to the SpringBootVueApplication class.

What is CORS? If you’re asking this, read up on it in Spring’s Understanding CORS docs.

Update your SpringBootVueApplication class to match below. Notice the URL defined in the simpleCorsFilter() method needs to match the URL of the client app.

package com.okta.springbootvue;

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Collections;
import java.util.stream.Stream;

@SpringBootApplication  
public class SpringBootVueApplication {  

    public static void main(String[] args) {  
      SpringApplication.run(SpringBootVueApplication.class, args);  
    }  

    // Bootstrap some test data into the in-memory database
    @Bean  
    ApplicationRunner init(TodoRepository repository) {  
        return args -> {  
            Stream.of("Buy milk", "Eat pizza", "Write tutorial", "Study Vue.js", "Go kayaking").forEach(name -> {  
                    Todo todo = new Todo();  
                    todo.setTitle(name);  
                    repository.save(todo);  
            });  
            repository.findAll().forEach(System.out::println);  
        };  
    }  

    // Fix the CORS errors
    @Bean
    public FilterRegistrationBean simpleCorsFilter() {  
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();  
        CorsConfiguration config = new CorsConfiguration();  
        config.setAllowCredentials(true); 
        // *** URL below needs to match the Vue client URL and port ***
        config.setAllowedOrigins(Collections.singletonList("http://localhost:8080")); 
        config.setAllowedMethods(Collections.singletonList("*"));  
        config.setAllowedHeaders(Collections.singletonList("*"));  
        source.registerCorsConfiguration("/**", config);  
        FilterRegistrationBean bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);  
        return bean;  
    }   
}


The keen among you will also notice the ApplicationRunner init(TodoRepository repository) function. This bean is an initialization hook that adds some test todos into the repository when the application is run. This will make life easier as you’re building the client app.

On to the client app!

Install Node & Yarn

This tutorial assumes that you have Node and Yarn installed. If you don’t, install them now.

Yarn can be installed using brew install yarn, or if you’re not on a Mac, take a look at their website.

There are lots of ways to install Node.js. You can download a version from their website. I’m currently on version 8.12.0. Another option is the n package manager. Get it from their GitHub page.

Create Vue.js App

You’re going to use the Vue CLI 3 to create a project from scratch. Vue CLI is a great project that makes building a Vue app nice and easy. If you’re not familiar with it, take a look at their website.

Install the Vue CLI 3 using yarn:

yarn global add @vue/[email protected]


Once that’s finished, make sure you’re in your root project directory SpringBootVueApplication and run the following command:

vue create -d client


This creates the default Vue application named client in the client subdirectory. Without the -d option, the Vue CLI has a pretty neat interface that allows you to choose which options to include. It’s worth checking out another time. The project you’re going to build is based on the Vue TodoMVC example project by Evan You. The difference is that this project will persist the todos using a Spring Boot server instead of browser local storage.

cd into the SpringBootVueApplication/client directory.

The project can be run with yarn serve.

Right now, all you’ll see is the standard “Welcome to Your Vue.js App” screen.

Add a couple dependencies:

yarn add [email protected] vue[email protected]


axios is the package you’ll use to make HTTP requests to your server. vuejs-logger is a logging framework, because you’re not still using console.log(), right?

Add a Vue config file client/vue.config.js:

module.exports = {
  runtimeCompiler: true
};


Replace src/main.js with the following

import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

import VueLogger from 'vuejs-logger';

const options = {
  isEnabled: true,
  logLevel : 'debug',
  stringifyArguments : false,
  showLogLevel : true,
  showMethodName : false,
  separator: '|',
  showConsoleColors: true
};

Vue.use(VueLogger, options);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
});


Replace src/App.vue with the following:

<template>
  <div id="app">
    <Todos />
    <footer class="info">
      <p>Based on a project written by <a href="http://evanyou.me">Evan You</a></p>
      <p>Original Vue TodoApp project is <a href="https://vuejs.org/v2/examples/todomvc.html">here</a></p>
      <p>Modified for this tutorial by Andrew Hughes</p>
    </footer>
  </div>
</template>

<script>
  import Todos from './components/Todos'
  // app Vue instance
  const app = {
    name: 'app',
    components: {
      Todos
    },
    // app initial state
    data: () => {
      return {
      }
    }
  }

  export default app
</script>

<style>
  [v-cloak] { display: none; }
</style>


Delete the src/components/HelloWorld.vue module. You can also delete the src/assets folder, if you want, as you won’t need it.

Create a new Vue component called src/components/Todos.vue:

<template>
  <div>
    <h1 class="title">Todos</h1>
    <h1 class="email">{{userEmail}}</h1>
    <section class="todoapp">
      <div v-if="loading">
        <h1 class="loading">Loading...</h1>
      </div>
      <div v-else>
        <header class="header">
          <input class="new-todo"
                 autofocus autocomplete="off"
                 :placeholder="this.inputPlaceholder"
                 v-model="newTodo"
                 @keyup.enter="addTodo">
        </header>
        <section class="main" v-show="todos.length" v-cloak>
          <input class="toggle-all" type="checkbox" v-model="allDone">
          <ul class="todo-list">
            <li v-for="todo in filteredTodos"
                class="todo"
                :key="todo.id"
                :class="{ completed: todo.completed, editing: todo == editedTodo }">
              <div class="view">
                <input class="toggle" type="checkbox" v-model="todo.completed" @change="completeTodo(todo)">
                <label @dblclick="editTodo(todo)">{{ todo.title }}</label>
                <button class="destroy" @click="removeTodo(todo)"></button>
              </div>
              <input class="edit" type="text"
                     v-model="todo.title"
                     v-todo-focus="todo == editedTodo"
                     @blur="doneEdit(todo)"
                     @keyup.enter="doneEdit(todo)"
                     @keyup.esc="cancelEdit(todo)">
            </li>
          </ul>
        </section>
        <footer class="footer" v-show="todos.length" v-cloak>
          <span class="todo-count">
            <strong>{{ remaining }}</strong> {{ remaining | pluralize }} left
          </span>
          <ul class="filters">
            <li><a href="#/all" @click="setVisibility('all')" :class="{ selected: visibility == 'all' }">All</a></li>
            <li><a href="#/active" @click="setVisibility('active')" :class="{ selected: visibility == 'active' }">Active</a></li>
            <li><a href="#/completed" @click="setVisibility('completed')" :class="{ selected: visibility == 'completed' }">Completed</a></li>
          </ul>
          <button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
            Clear completed
          </button>
        </footer>
      </div>
    </section>
    <div v-if="error" class="error" @click="handleErrorClick">
      ERROR: {{this.error}}
    </div>
  </div>
</template>

<script>

  // visibility filters
  let filters = {
    all: function (todos) {
      return todos
    },
    active: function (todos) {
      return todos.filter(function (todo) {
        return !todo.completed
      })
    },
    completed: function (todos) {
      return todos.filter(function (todo) {
        return todo.completed
      })
    }
  }

  // app Vue instance
  const Todos = {
    name: 'Todos',
    props: {
      activeUser: Object
    },

    // app initial state
    data: function() {
      return {
        todos: [],
        newTodo: '',
        editedTodo: null,
        visibility: 'all',
        loading: true,
        error: null,
      }
    },

    mounted() {
      // inject some startup data
      this.todos = [{title: 'Drink coffee', completed:false},{title: 'Write REST API', completed:false}];
      // hide the loading message
      this.loading = false;
    },

    // computed properties
    // http://vuejs.org/guide/computed.html
    computed: {
      filteredTodos: function () {
        return filters[this.visibility](this.todos)
      },
      remaining: function () {
        return filters.active(this.todos).length
      },
      allDone: {
        get: function () {
          return this.remaining === 0
        },
        set: function (value) {
          this.todos.forEach(function (todo) {
            todo.completed = value
          })
        }
      },
      userEmail: function () {
        return this.activeUser ? this.activeUser.email : ''
      },
      inputPlaceholder: function () {
        return this.activeUser ? this.activeUser.given_name + ', what needs to be done?' : 'What needs to be done?'
      }
    },

    filters: {
      pluralize: function (n) {
        return n === 1 ? 'item' : 'items'
      }
    },

    // methods that implement data logic.
    // note there's no DOM manipulation here at all.
    methods: {

      addTodo: function () {
        var value = this.newTodo && this.newTodo.trim()
        if (!value) {
          return
        }

        this.todos.push({
          title: value,
          completed: false
        });

        this.newTodo = ''
      },

      setVisibility: function(vis) {
        this.visibility = vis
      },

      completeTodo (todo) {
      },

      removeTodo: function (todo) { // notice NOT using "=>" syntax
        this.todos.splice(this.todos.indexOf(todo), 1)
      },

      editTodo: function (todo) {
        this.beforeEditCache = todo.title
        this.editedTodo = todo
      },

      doneEdit: function (todo) {
        if (!this.editedTodo) {
          return
        }

        this.editedTodo = null
        todo.title = todo.title.trim()

        if (!todo.title) {
          this.removeTodo(todo)
        }
      },

      cancelEdit: function (todo) {
        this.editedTodo = null
        todo.title = this.beforeEditCache
      },

      removeCompleted: function () {
        this.todos = filters.active(this.todos)
      },

      handleErrorClick: function () {
        this.error = null;
      },
    },

    // a custom directive to wait for the DOM to be updated
    // before focusing on the input field.
    // http://vuejs.org/guide/custom-directive.html
    directives: {
      'todo-focus': function (el, binding) {
        if (binding.value) {
          el.focus()
        }
      }
    }
  }

  export default Todos
</script>

<style>
  [v-cloak] { display: none; }
</style>


Finally, add a stylesheet called public/style.css and copy and paste the styles from the stylesheet in our GitHub repository..

In the public/index.html, add the following line at the bottom of the block.

<link rel="stylesheet" type="text/css" href="<%= BASE_URL %>style.css">


If you do this now, you’ll see a functioning todo application, but the data doesn’t persist. The todos are simply saved as an array in the Vue module. You’re going to modify this to send and receive data from your Spring Boot resource server.

Add Client Logic to Handle API Requests

Under the client/src directory, add a file named Api.js with the following contents:

import axios from 'axios'  

const SERVER_URL = 'http://localhost:9000';  

const instance = axios.create({  
  baseURL: SERVER_URL,  
  timeout: 1000  
});  

export default {  
  // (C)reate  
  createNew: (text, completed) => instance.post('todos', {title: text, completed: completed}),  
  // (R)ead  
  getAll: () => instance.get('todos', {  
    transformResponse: [function (data) {  
      return data? JSON.parse(data)._embedded.todos : data;  
    }]  
  }),  
  // (U)pdate  
  updateForId: (id, text, completed) => instance.put('todos/'+id, {title: text, completed: completed}),  
  // (D)elete  
  removeForId: (id) => instance.delete('todos/'+id)  
}


This file encapsulates some of the logic around the REST API requests. The SERVER_URL should be the URL and port of the Spring Boot server.

You’ll notice the CRUD (Create, Read, Update, and Delete) functions are defined. This module would be totally trivial, really, except for the transformResponse options you’re setting. This is just used to normalize the data in the _embedded response property.

You might be wondering why to even bother with an API class this simple, thinking that this code could easily have just been put into the Todos component. That’s true, for the purposes of this tutorial. However, as projects grow, this kind of encapsulation is what keeps a project maintainable over time.

For example, suppose at some point in the future you decided that you didn’t want to use the axios module, or your boss told you to switch it out for fetch; you’d feel pretty smart when you realized that all of the code is handily gathered in one place and you’re only going to have to edit one file (as opposed to searching and replacing all over the project).

Load Data From the Server

Now you need to change the Todos component (src/components/Todos.vue) so that it loads data from the Spring Boot REST server.

First thing is to import the Api module you just created. Beneath the section, just below the

Kafka with Spring streams step by step guide for Beginners

Kafka with Spring streams step by step guide for Beginners

This post gives a step-by-step tutorial to enable messaging in a microservice using Kafka with Spring Cloud Stream.

This post gives a step-by-step tutorial to enable messaging in a microservice using Kafka with Spring Cloud Stream.

Spring Cloud Stream is a framework under the umbrella project Spring Cloud, which enables developers to build event-driven microservices with messaging systems like Kafka and RabbitMQ.

Asynchronous messaging systems are always an important part of any modern enterprise software solution. The evolution of microservices has shortened the time-to-market for any software product, but this is not possible without the necessary tools and frameworks.

Spring Cloud Stream is a framework built on top of Spring Integration. It integrates with Spring Boot seamlessly to build efficient microservices in less time to connect with shared messaging systems. Spring Cloud Stream provides multiple binder implementations such as Kafka, RabbitMQ and various others. The details are provided here.

Here is a step-by-step tutorial on building a simple microservice application based on Spring Boot and uses Spring Cloud Stream to connect with a Kafka instance.

Getting Started

Install Kafka and create a topic. I am using a Kafka broker running on my local windows machine for this demonstration, but it can be an installation on a Unix machine as well. Steps for Kafka installation on windows machine are provided here.

Create a Spring Boot starter project either using STS IDE or Spring Initializr. I am providing the pom.xml for reference.


<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.techwording</groupId>
<artifactId>spring-cloud-stream-kafka-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cloud-stream-kafka-example</name>
<description>Demo project for Spring Cloud Stream and Kafka</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-kafka-streams</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

The Spring Cloud Stream project needs to be configured with the Kafka broker URL, topic, and other binder configurations. Below is an example of configuration for the application.



spring:
  cloud:
    stream:
      default-binder: kafka
      kafka:
        binder:
          brokers:
          - localhost:9092
      bindings:
        input:
         binder: kafka
         destination: test
         content-type: text/plain
         group: input-group-1
        output:
          binder: kafka
          destination: test
          group: output-group-1
          content-type: text/plain
					

We will need at least one producer and a consumer to test the message and send and receive operations. Below is the sample code for a producer and consumer in its simplest form, developed using Spring Cloud Stream.


package com.techwording.scs;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
@EnableBinding(Source.class)
public class Producer {
private Source mySource;
public Producer(Source mySource) {
super();
this.mySource = mySource;
}
public Source getMysource() {
return mySource;
}
public void setMysource(Source mysource) {
mySource = mySource;
}
}


package com.techwording.scs;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.handler.annotation.Payload;
@EnableBinding(Sink.class)
public class Consumer {
private static final Logger logger = LoggerFactory.getLogger(Consumer.class);
@StreamListener(target = Sink.INPUT)
public void consume(String message) {
logger.info("recieved a string message : " + message);
}
@StreamListener(target = Sink.INPUT, condition = "headers['type']=='chat'")
public void handle(@Payload ChatMessage message) {
final DateTimeFormatter df = DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM)
.withZone(ZoneId.systemDefault());
final String time = df.format(Instant.ofEpochMilli(message.getTime()));
logger.info("recieved a complex message : [{}]: {}", time, message.getContents());
}
}

We will also create a Rest Controller class, which will accept the message over HTTP and pass it to the producer. This is just to make the testing convenient.


package com.techwording.scs;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
private Producer producer;
public Controller(Producer producer) {
super();
this.producer = producer;
}
// get the message as a complex type via HTTP, publish it to broker using spring cloud stream
@RequestMapping(value = "/sendMessage/complexType", method = RequestMethod.POST)
public String publishMessageComplextType(@RequestBody ChatMessage payload) {
payload.setTime(System.currentTimeMillis());
producer.getMysource()
.output()
.send(MessageBuilder.withPayload(payload)
.setHeader("type", "chat")
.build());
return "success";
}
// get the String message via HTTP, publish it to broker using spring cloud stream
@RequestMapping(value = "/sendMessage/string", method = RequestMethod.POST)
public String publishMessageString(@RequestBody String payload) {
// send message to channel
producer.getMysource()
.output()
.send(MessageBuilder.withPayload(payload)
.setHeader("type", "string")
.build());
return "success";
}
}

Run the below maven commands to build and run this project.

mvn clean install
mvn spring-boot:run


Hit the POST endpoint /sendMessage/string and check the application console logs. Here is an example output the application produced when I hit this endpoint with message "hello" in the rest body.

2019-10-01 14:37:22.764  INFO 377456 --- [container-0-C-1] com.techwording.scs.Consumer             : received a string message : {"contents":"hello","time":1569920841187}


Hit the POST endpoint /sendMessage/complexType and check the application console logs.

2019-10-01 14:37:22.773  INFO 377456 --- [container-0-C-1] com.techwording.scs.Consumer             : received a complex message : [2:37:21 PM]: hello


The annotation @EnableBinding takes one or more interfaces as parameters. In this example, we have used Sink and Source interfaces, which declare input and output channels, respectively. You can also define your own interfaces for this purpose.

@StreamListener annotation is a convenient way provided by Spring Cloud Stream for content-based routing. It works based on a pub-sub model, and every @StreamListener receives its own copy of the message.

I have used two stream listeners in this project — one for consuming plain string messages and another one for messages with a complex type, ChatMessage. The producer sends messages attached with a header "type" with a logical value and consumer can apply conditions to filter messages using @StreamListener.

You can find the complete project here.

Swagger TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body

I have added Swagger to my Spring Boot 2 application:

I have added Swagger to my Spring Boot 2 application:

This is my Swagger config:

@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
    // @formatter:off
    return new Docket(DocumentationType.SWAGGER_2)  
            .select()                                  
            .apis(RequestHandlerSelectors.any())              
            .paths(PathSelectors.any())                          
            .build();
    // @formatter:on
}

}

This is Maven dependency:

    <!-- Swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>

When I try to invoke for example http://localhost:8080/api/actuator/auditevents it fails with the following error:

TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body.

What am I doing wrong and how to fix it ?

Spring Boot: Run and Build in Docker

Spring Boot: Run and Build in Docker

Spring Boot: Run and Build in Docker - We take a look at how to create Java and Spring Boot-based applications in a Docker container. It's gonna be a whale of a time!

Spring Boot: Run and Build in Docker - We take a look at how to create Java and Spring Boot-based applications in a Docker container. It's gonna be a whale of a time!

There are a lot of guides on “Docker for Java developers,” but most of them do not take care of small and efficient Docker images.

I have combined many resources on how to make a simple and fast Docker image containing any Spring Boot-like application.

My goals:

  • Create a single and portable Dockerfile (as general as possible).
  • Make Maven build inside Docker (no need to have Maven locally).
  • Don’t download any Maven dependencies repeatedly, if no changes in pom.xml (rebuilding image as fast as possible).
  • The final Docker image should contain only application itself (no source codes, no Maven dependencies required by Maven build, etc.)
  • The final image should be as small as possible (no full JDK required).
  • The application inside Docker should remain configurable as much as possible (with all Spring Boot configuration options).
  • Possibility to enable debugging (on demand).
  • Possibility to see log files.

The final image is designed for development purposes, but it does not contain any no-go production parts and it is fully configurable.

To see a working example, see  my GitHub project.
To fulfill a single portable Dockerfile requirement, I need to use Docker multi-stage builds.

It will have two main parts (stages):

  • Create a single and portable Dockerfile (as general as possible).
  • Make Maven build inside Docker (no need to have Maven locally).
  • Don’t download any Maven dependencies repeatedly, if no changes in pom.xml (rebuilding image as fast as possible).
  • The final Docker image should contain only application itself (no source codes, no Maven dependencies required by Maven build, etc.)
  • The final image should be as small as possible (no full JDK required).
  • The application inside Docker should remain configurable as much as possible (with all Spring Boot configuration options).
  • Possibility to enable debugging (on demand).
  • Possibility to see log files.
The Building Part of the Dockerfile
### BUILD image
FROM maven:3-jdk-11 as builder
# create app folder for sources
RUN mkdir -p /build
WORKDIR /build
COPY pom.xml /build
#Download all required dependencies into one layer
RUN mvn -B dependency:resolve dependency:resolve-plugins
#Copy source code
COPY src /build/src
# Build application
RUN mvn package

I have started from the official Maven image, so you may change this as you wish. The most interesting part is this:

RUN mvn -B dependency:resolve dependency:resolve-plugins

It downloads all dependencies required either by your application or by plugins called during a build process. Then all dependencies are a part of one layer. That layer does not change until any changes are found in the pom.xml file.

So the rebuilding is very fast and does not include downloading all the dependencies again and again.

The second option, how to download required dependencies, comes from the official Docker Maven site (when you have some problems with the previous variant):

RUN mvn -B -e -C -T 1C org.apache.maven.plugins:maven-dependency-plugin:3.0.2:go-offline

How to Customize Maven Settings

There are many situations where you need to change a default Maven setting for your customized build. To do that, you need to copy your settings.xml file into the image before you provide the builder image definition, For example:

FROM maven:3-jdk-11 as builder
#Copy Custom Maven settings
COPY settings.xml /root/.m2/

The Runtime Part of the Dockerfile
FROM openjdk:11-slim as runtime
EXPOSE 8080
#Set app home folder
ENV APP_HOME /app
#Possibility to set JVM options (https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html)
ENV JAVA_OPTS=""

#Create base app folder
RUN mkdir $APP_HOME
#Create folder to save configuration files
RUN mkdir $APP_HOME/config
#Create folder with application logs
RUN mkdir $APP_HOME/log

VOLUME $APP_HOME/log
VOLUME $APP_HOME/config

WORKDIR $APP_HOME
#Copy executable jar file from the builder image
COPY --from=builder /build/target/*.jar app.jar

ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar app.jar" ]
#Second option using shell form:
#ENTRYPOINT exec java $JAVA_OPTS -jar app.jar $0 [email protected]

The runtime part starts with some necessary steps, i.e. exposing ports, setting up environments, and creating some useful folders. The most interesting part is related to copying a previously created jar file into our new image:

#Copy executable jar file from the builder image
COPY --from=builder /build/target/*.jar app.jar

I am copying from the builder image, see the param –from. For more information about copying files from other images, see the Docker documentation page.

As for the Spring Boot application, the created jar file is executable, so it is possible to run our application with the single command:

ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar app.jar" ]

To reduce Tomcat startup time there is a system property pointing to /dev/urandom.

There are other options for runing a Spring Boot application inside Docker. For more info, visit the official Spring guide.

How to Build and Run Spring Boot Application in Docker in One Step
docker build -t <image_tag> . &amp;&amp; docker run -p 8080:8080 <image_tag>

The above command will build your application with Maven and start it without any delay. This is the simplest way without any customizations. The file may come with some specific requirements, so here’s a couple of them.

Now you can visit the URL to get response from my GitHub example:

http://localhost:8081/customer/10

How to Debug?

My example uses Java 11, so there are some JVM options to enable debug mode:

docker build -t <image_tag> . && docker run -p 8080:8080 -p 5005:5005 --env JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 <image_tag>

You need to add the Docker environment variable, JAVA_OPTS, with JVM options and map the internal debugging port to the outside of the container: -p 5005:5005.

For Java 5-8 containers, use this JAVA_OPTS parameter:

JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

How to Setup Logging

The runtime container contains a folder called app/app/log with all the log files. This path could be easily mounted into your host:

docker build -t <image_tag> . && docker run -p 8080:8080 -v /opt/spring-boot/test/log:/app/log <image_tag>

How to Change the Application Configuration

The jar file contains the default configuration. To selectively override those values, you have many options. I will show you some of them.

Please note that all of the configurations are possible when using the exec form of the ENTRYPOINT. When using the shell form of the ENTRYPOINT, you need to pass all command line arguments manually:

ENTRYPOINT exec java $JAVA_OPTS -jar app.jar $0 [email protected]

Command Line Arguments

Spring Boot automatically accepts all command line arguments and these arguments are passed into the run command inside Docker:

docker build -t <image_tag> . && docker run -p 8080:8080 <image_tag> --logging.level.org.springframework=debug

System Properties

A similar way is using regular system properties:

docker build -t <image_tag> . && docker run -p 8080:8080 --env JAVA_OPTS=-Dlogging.level.org.springframework=DEBUG <image_tag>

Environment Variables

You may use environment variables instead of system properties. Most operating systems do not allow for period-separated key names, but you can use underscores instead (for example,  SPRING_CONFIG_NAME  instead of  spring.config.name ). Check the documentation page for more information.

docker build -t <image_tag> . && docker run -p 8080:8080 --env LOGGING_LEVEL_ORG_SPRINGFRAMEWORK=DEBUG <image_tag>

Mount Your Own Configuration File

You may have noticed that there is a VOLUME command for mounting a configuration folder:

docker build -t <image_tag> . && docker run -p 8080:8080 -v /opt/spring-boot/test/config:/app/config:ro <image_tag>

So your local folder /opt/spring-boot/test/config should contain the file application.properties. This is the default configuration file name and can be easily changed by setting the property spring.config.name.

That’s all for this post, but your requirements may vary in many ways. I tried to solve some of the most important conditions Java developers using Docker.

To see a working example, see  my GitHub project.
Originally published by Pavel Sklenar at https://dzone.com

Learn More

☞ Docker Mastery: The Complete Toolset From a Docker Captain

☞ Learn DevOps: The Complete Kubernetes Course

☞ Docker for the Absolute Beginner - Hands On

☞ Docker Technologies for DevOps and Developers

☞ Docker Swarm Mastery: DevOps Style Cluster Orchestration