Enterprise Java

Building Spring Boot RESTful Service + Spring Boot Actuator

Overview

What is REST?

REST(REpresentational State Transfer) is the architectural style the web is built on and has become a standard software design pattern used for web applications. The term Representational State Transfer was first used by Roy Fielding, the originator of REST and one of the principal authors of HTTP specification, in his doctoral dissertation.

There are many good references on REST including:

This tutorial is based on Building Rest Services with Spring and the beginning of the tutorial has a good overview of REST as well.

What is Spring Boot Actuator?

Spring Boot Actuator is a sub-project of Spring Boot. It adds several production grade services to your application with minimal effort on your part.

Definition of Actuator

An actuator is a component responsible for moving or controlling a system.
The term actuator is not limited to Spring Boot; however, that is our focus here.

After Actuator is configured in your Spring Boot application, it allows you to interact and monitor your application by invoking different technology agnostic endpoints exposed by Spring Boot Actuator such as application health, beans, loggers, mappings, and trace. More are listed in this Spring doc.

0 – Spring Boot RESTful Web Service with Actuator Example Application

We will build an example RESTful web application with Spring Boot and Actuator.

The application will be a “username tracker.” In this application, a person has one account and their account may have many usernames.

View and Download the code from Github

1 – Project Structure

As usual, we have a normal Maven project structure.

2 – Project Dependencies

Besides typical Spring Boot dependencies, we are including HSQLDB for our embedded database and spring-boot-starter-actuator for all the Actuator dependencies.

<?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.michaelcgood</groupId>
	<artifactId>michaelcgood-springbootactuator</artifactId>
	<version>0.0.1</version>
	<packaging>jar</packaging>

	<name>Spring-Boot-Actuator-Example</name>
	<description>Michael C  Good - Spring Boot Actuator Example</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.6.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-actuator</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
	</dependencies>

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


</project>

3 – Run the Empty Application

Although we haven’t written any code, we will run the Spring Boot application.

Go to your terminal and follow along with the commands.

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080
{"timestamp":1505235455245,"status":404,"error":"Not Found","message":"No message available","path":"/"}

We haven’t written any code yet, instead of a default container-generated HTML error response, Actuator produces you a JSON response from its /error endpoint.

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/health
{"status":"UP"}

The Actuator /health endpoint will let you know if your application is up.

4 – Model

Now let’s define the fields of the models for our username tracker application.

  • As mentioned, a person has one account and may have many usernames. So we map Setwith a @OneToMany annotation
  • A username model will have a password and a username of course
  • Our model will need an ID and we make it autogenerated
  • We make a class construction to define an account may be made with a username and a password. Because of this custom constructor we also need to make a default one with no parameters.

Account.java

package com.michaelcgood.model;

import java.util.HashSet;
import java.util.Set;

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

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
public class Account {

	public Set<Usernames> getUsernames() {
		return usernames;
	}

	public void setUsernames(Set<Usernames> usernames) {
		this.usernames = usernames;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@OneToMany(mappedBy= "account")
	private Set<Usernames> usernames = new HashSet<>();
	
	@Id
	@GeneratedValue
	private Long id;
	
	@JsonIgnore
	public String password;
	public String username;
	
	public Account(String name, String password) {
        this.username = name;
        this.password = password;
    }
	
	Account(){
		
	}
	
}

Usernames.java

  • As there is one account to many usernames, the reverse is true as well: there many usernames to one account. Therefore, we map Account with @ManyToOne annotation
  • To track a username we need: the url and the username
  • We once again define an autogenerated ID
  • We define a custom class constructor that requires account, url, and username parameter. Once again we need to define a default constructor method to avoid an error being thrown.
package com.michaelcgood.model;

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

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
public class Usernames {
	
	@JsonIgnore
	@ManyToOne
	private Account account;
	
	public Account getAccount() {
		return account;
	}

	public void setAccount(Account account) {
		this.account = account;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@Id
	@GeneratedValue
	private Long id;
	
	public String url;
	public String username;
	
	Usernames(){
		
	}
	
	public Usernames(Account account, String url, String username){
		this.url=url;
		this.account=account;
		this.username=username;
	}
	


}

5 – Repository

We create a repository for both models and create search functions using derived queries.

AccountRepository.java

package com.michaelcgood.dao;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.michaelcgood.model.Account;

public interface AccountRepository extends JpaRepository<Account,Long> {

	Optional<Account> findByUsername(String username);
}

UsernamesRepository.java

package com.michaelcgood.dao;

import java.util.Collection;

import org.springframework.data.jpa.repository.JpaRepository;

import com.michaelcgood.model.Usernames;

public interface UsernamesRepository extends JpaRepository<Usernames,Long> {

	Collection<Usernames> findByAccountUsername(String username);
	
}

6 – Controller

In the controller, we define all the mapping that we will be using for our RESTful web service.

  • We annotate our controller with @RestController rather than @Controller. As stated in the javadoc, it is “a convenience annotation that is itself annotated with @Controller and @ResponseBody.”
  • We declare the variables for our UsernamesRepository and AccountRepository and make them final because we only want the value to be assigned once. We annotate them as @Autowired over the UsernamesRestController class constructor.
  • {userId} and {usernamesId} are path variables. That means these values are provided in a URL. This will be shown in our demo.
  • The Controller methods return POJOs (Plain Old Java Objects). Spring Boot automatically wires HttpMessageConverter to convert these generic objects to JSON.

UsernamesRestController.java

package com.michaelcgood.controller;

import java.net.URI;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.michaelcgood.dao.AccountRepository;
import com.michaelcgood.dao.UsernamesRepository;
import com.michaelcgood.model.Usernames;

@RestController
@RequestMapping("/{userId}/usernames")
public class UsernamesRestController {

	private final UsernamesRepository usernamesRepository;
	private final AccountRepository accountRepository;
	
	@Autowired
	UsernamesRestController(UsernamesRepository usernamesRepository, AccountRepository accountRepository){
		this.usernamesRepository = usernamesRepository;
		this.accountRepository = accountRepository;
	}
	
	@GetMapping
	Collection<Usernames> readUsernames (@PathVariable String userId){
		this.validateUser(userId);
		return this.usernamesRepository.findByAccountUsername(userId);
	}
	
	@PostMapping
	ResponseEntity<?> add(@PathVariable String userId,@RequestBody Usernames input){
		this.validateUser(userId);
		
		return this.accountRepository.findByUsername(userId)
				.map(account -> {
					Usernames result = usernamesRepository.save(new Usernames(account,input.url,input.username));
					
					URI url = ServletUriComponentsBuilder
							.fromCurrentRequest().path("/{id}")
							.buildAndExpand(result.getId()).toUri();
					
						return ResponseEntity.created(url).build();	
				})
				.orElse(ResponseEntity.noContent().build());
	}
	
	@GetMapping(value="{usernamesId}")
	Usernames readUsername(@PathVariable String userId, @PathVariable Long usernameId){
		this.validateUser(userId);
		return this.usernamesRepository.findOne(usernameId);
	}
	
	private void validateUser(String userId){
		this.accountRepository.findByUsername(userId).orElseThrow(
				() -> new UserNotFoundException(userId));
	}
	
	
}

UserNotFoundException.java

Here we define the custom exception we used in our Controller class to explain a user could not be found.

package com.michaelcgood.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {

/**
	 * 
	 */
	private static final long serialVersionUID = 7537022054146700535L;

public UserNotFoundException(String userId){
	super("Sorry, we could not find user '" + userId +"'.");
}
	
	
}

7 – @SpringBootApplication

We use the CommandLineRunner to create accounts and insert usernames. Every account will have two usernames.

package com.michaelcgood;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import com.michaelcgood.dao.AccountRepository;
import com.michaelcgood.dao.UsernamesRepository;
import com.michaelcgood.model.Account;
import com.michaelcgood.model.Usernames;

import java.util.Arrays;

@SpringBootApplication
public class SpringBootActuatorExampleApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootActuatorExampleApplication.class, args);
	}
	
	@Bean
	CommandLineRunner init(AccountRepository accountRepository,
			UsernamesRepository usernamesRepository) {
		return (evt) -> Arrays.asList(
				"ricksanchez,mortysmith,bethsmith,jerrysmith,summersmith,birdperson,squanchy,picklerick".split(","))
				.forEach(
						a -> {
							Account account = accountRepository.save(new Account(a,
									"password"));
							usernamesRepository.save(new Usernames(account,
									"http://example.com/login", a +"1"));
							usernamesRepository.save(new Usernames(account,
									"http://example2.com/login", "the_"+a));
						});
	}
}

8 – Configuration

It is stated in the Spring documentation:

By default all sensitive HTTP endpoints are secured such that only users that have an ACTUATOR role may access them. Security is enforced using the standard HttpServletRequest.isUserInRole method.

We haven’t set up any security and user roles, as this is just an example. So, for ease of demonstration, I will be disabling the security requirement. Otherwise, we will get an “unauthorized” error as of right now, like the one shown below.

{"timestamp":1505321635068,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource.","path":"/beans"}

application.properties

Add this to your application.properties to disable the need for authentication.

management.security.enabled=false

9 – Demo

To retrieve the responses from the server, you could either visit the URL in your browser or use curl. For my demo, I am using curl.

REST queries for data in repositories

Query for usernames belonging to account jerrysmith.

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/jerrysmith/usernames
[{"id":7,"url":"http://example.com/login","username":"jerrysmith1"},{"id":8,"url":"http://example2.com/login","username":"the_jerrysmith"}]

Query for usernames belonging to account picklerick

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/picklerick/usernames
[{"id":15,"url":"http://example.com/login","username":"picklerick1"},{"id":16,"url":"http://example2.com/login","username":"the_picklerick"}]

Actuator queries

The response to this query is truncated because it is really, really long.

Beans

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/beans
[{"context":"application","parent":null,"beans":[{"bean":"springBootActuatorExampleApplication","aliases":[],"scope":"singleton","type":"com.michaelcgood.SpringBootActuatorExampleApplication$$EnhancerBySpringCGLIB$$509f4984","resource":"null","dependencies":[]},{"bean":"org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory","aliases":[],"scope":"singleton","type":"org.springframework.core.type.classreading.CachingMetadataReaderFactory","resource":"null","dependencies":[]},{"bean":"usernamesRestController","aliases":[],"scope":"singleton","type":"com.michaelcgood.controller.UsernamesRestController","resource":"file [/Users/mike/javaSTS/Spring-Boot-Actuator-Example/target/classes/com/michaelcgood/controller/UsernamesRestController.class]","dependencies":["usernamesRepository","accountRepository"]},{"bean":"init","aliases":[],"scope":"singleton","type":"com.michaelcgood.SpringBootActuatorExampleApplication$$Lambda$11/889398176","resource":"com.michaelcgood.SpringBootActuatorExampleApplication",
[...]

Metrics

mikes-MacBook-Air:Spring-Boot-Actuator-Example mike$ curl localhost:8080/metrics
{"mem":350557,"mem.free":208275,"processors":4,"instance.uptime":213550,"uptime":240641,"systemload.average":1.6552734375,"heap.committed":277504,"heap.init":131072,"heap.used":69228,"heap":1864192,"nonheap.committed":74624,"nonheap.init":2496,"nonheap.used":73062,"nonheap":0,"threads.peak":27,"threads.daemon":23,"threads.totalStarted":30,"threads":25,"classes":9791,"classes.loaded":9791,"classes.unloaded":0,"gc.ps_scavenge.count":11,"gc.ps_scavenge.time":139,"gc.ps_marksweep.count":2,"gc.ps_marksweep.time":148,"httpsessions.max":-1,"httpsessions.active":0,"datasource.primary.active":0,"datasource.primary.usage":0.0,"gauge.response.beans":14.0,"gauge.response.info":13.0,"counter.status.200.beans":2,"counter.status.200.info":1}

9 – Conclusion

Congratulations, you have created a RESTful Web Service that can be monitored with Actuator. REST is really the most organic way for different clients to communicate because it works due to HTTP.

The source code is on Github

Published on Java Code Geeks with permission by Michael Good, partner at our JCG program. See the original article here: Building Spring Boot RESTful Service + Spring Boot Actuator

Opinions expressed by Java Code Geeks contributors are their own.

Michael Good

Michael is a software engineer located in the Washington DC area that is interested in Java, cyber security, and open source technologies. Follow his personal blog to read more from Michael.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button