Spring Boot Guides

Repository with examples illustrated at Spring Boot QuickStart Guides (https://spring.io/quickstart and https://spring.io/guides). The examples are used in the classes about Spring Boot, taught by Rodrigo Martins Pagliares in the Computer Science undergraduate course at UNIFAL-MG, Brazil.

Outline

Part I - First steps with Spring Boot

01 - Spring Quickstart Guide

02 - command-line-spring-boot

03 - Building an Application with Spring Boot

Part II - Persistence with Spring Boot

04 - accessing-data-mysql

05 - Accessing Relational Data using JDBC with Spring

06 - managing-transactions

07 - accessing-data-jpa

Part III - RESTful with Spring Boot

08 - accessing-data-rest

09 - rest-service

10 - quoters and consuming-rest

11 - actuator-service

12 - Building a Hypermedia-Driven RESTful Web Service

13 - Enabling Cross Origin Requests for a RESTful Web Service

Part IV - Spring MVC

14 - Serving Web Content with Spring MVC

15 - Handling Form Submission

16 - Validating Form Input

17 - Uploading Files

18 - Testing the Web Layer

Part V - Spring Security

19 - Securing a Web Application

Part VI - Docker Containers

20 - Spring Boot with Docker

Part I - First steps with Spring Boot

01 - Spring Quickstart Guide

Introduction

  • This first Spring Boot was generated with aid of Spring Initializer (https://start.spring.io/) by selecting Maven as build tool, Spring Boot 3.0.0, jar packaging, Java 17, and with the Spring Web Dependency.
  • In the example we program a classic “Hello World!” endpoint (RESTful) that can be accessed from any browser.
  • The example also illustrates how to pass parameters to the server via URL (query string).
  • The parameter is used to produce the output view shown in the web browser.
  • To execute the example:
    • ./mvnw spring-boot:run (GNU/Linux)
    • mvnw spring-boot:run (Windows)
  • To test the example:
@SpringBootApplication
@RestController
public class DemoApplication {

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

   @GetMapping("/hello")
   public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
      return String.format("Hello %s!", name);
   }
}

02 - Command-line development with Spring Boot

  • Back to Outline
  • Project source: command-line-spring-boot
  • In addition to web applications, we can use spring boot to develop command-line programs.
  • This example shows how to create a simple command line application in Spring Boot by implementing the interface CommandLineRunner (in the next example we will provide an alternative implementation that uses Java SE 8 lambdas).
  • This example project was created with Spring Initializr without explicitly adding any dependencies

Spring initializr printscreen

  • CommandLineRunner is an interface used to indicate that a bean should run when it is contained within a SpringApplication.
  • The overriden run() method of the CommandLineRunner will be executed after the application starts.
  • One common use case of CommandLineRunner is to load some static data at application startup.
  • The example also demonstrates how we can inject a bean ApplicationContext with the annotation @Autowired
@SpringBootApplication
public class CommandLineSpringBootApplication implements CommandLineRunner {
	@Autowired
	private ApplicationContext ctx;

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

	@Override
	public void run(String... args) throws Exception { 
		System.out.println("Hello, cruel Spring Boot command-line world!");
		System.out.println("Let's inspect the beans provided by Spring Boot:");

		String[] beanNames = ctx.getBeanDefinitionNames();
		Arrays.sort(beanNames);
		for (String beanName : beanNames) {
			System.out.println(beanName);
		}
	}
}

03 - Building an Application with Spring Boot

Introduction

  • This example builds a simple web application with Spring Boot and add some useful monitoring services to it.
  • The project can be created by visiting Spring Initializr, filling in your project details, picking your options, and downloading a bundled up project as a zip file. I added the Spring Web Dependency.
  • If your IDE has the Spring Initializr integration, you can complete this process from your IDE.
  • Spring Web allows build web, including RESTful, applications using Spring MVC. Spring web uses Apache Tomcat as the default embedded container.
  • This example includes a web controller for a simple web application (HelloController) that outputs HelloWorld when requested from the the root of the application /
  • URLs used in this example:
  • We can use a web browser or the command-line curl utility to test our endpoints.
  • This example also evolves the simple application class created by the Spring Initializr.
@SpringBootApplication
public class Application {

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

@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
   return args -> {

	System.out.println("Let's inspect the beans provided by Spring Boot:");

	String[] beanNames = ctx.getBeanDefinitionNames();
	Arrays.sort(beanNames);
	for (String beanName : beanNames) {
	   System.out.println(beanName);
	}

   };
}
 
  • The CommandLineRunner method marked as a @Bean runs on start up. It retrieves all the beans that were created by your application or that were automatically added by Spring Boot. It sorts them and prints them out (Notice that the helloController we created is on the list)
  • The example also demonstrates how we can include unit and integration tests in a Spring Boot application.
  • If you use Maven, add the following to your pom.xml file to enable automated tests:
	org.springframework.boot
	spring-boot-starter-test
	test

  • The example includes a simple unit test that mocks the servlet request and response by calling your endpoint (HelloController). We use MockMvc that comes from Spring Test and lets us send HTTP requests into the DispatcherServlet and make assertions about the result.
  • Having used @SpringBootTest, we are asking for the whole application context to be created. An alternative would be to ask Spring Boot to create only the web layers of the context by using @WebMvcTest.
  • The example also includes an simple full-stack integration test
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerIT {

    @Autowired
    private TestRestTemplate template;

    @Test
    public void getHello() throws Exception {
        ResponseEntity response = template.getForEntity("/", String.class);
        assertThat(response.getBody()).isEqualTo("Greetings from Spring Boot!");
    }
}
  • Finnaly, the example illustrates that we can add production grade services (management services) to our Spring Boot application to, among other things, monitor the status of our web application on production environments.
  • To enable management services. Add it to Maven

org.springframework.boot spring-boot-starter-actuator

{"_links":{"self":{"href":"http://localhost:8080/actuator","templated":false},"health-path":{"href":"http://localhost:8080/actuator/health/{*path}","templated":true},"health":{"href":"http://localhost:8080/actuator/health","templated":false}}}

Part II - Persistence with Spring Boot

04 - Accessing data with MySQL

Introduction

  • This example demonstrates how to develop a Spring application that is bound to a MySQL database and is ready for production.
  • The example uses Spring Data JPA to access the database, but this is only one of many possible choices (for example, you could use plain Spring JDBC).
  • Dependencies: Spring Web, Spring Data JPA, and MySQL Driver.
  • To execute this example, you need acesss to MySQL (you can use any MySQL frontend you like: MySQL Monitor, MySQL Workbench, PHPMyAdmin, etc) in order to be able to create a database.
  • To create a new database, run the following commands at the mysql prompt:
mysql> create database db_example; -- Creates the new database
mysql> create user 'springuser'@'%' identified by 'ThePassword'; -- Creates the user
mysql> grant all on db_example.* to 'springuser'@'%'; -- Gives all privileges to the new user on the newly created database

05 - Accessing Relational Data using JDBC with Spring

Introduction

  • This example demonstrates the process of accessing relational data with Spring.
  • You will build an application that uses Spring’s JdbcTemplate to access data stored in a relational database.
  • The project is a spring-boot project with the dependencies JDBC API and H2 Database.
  • In this example, we created the project with IntelliJ assistant (that integrates with Spring Initializr).
  • The example is a command-line application with Spring Boot (the RelationalDataAccessApplication class implements Spring Boot’s CommandLineRunner, which means it will execute the run() method after the application context is loaded).
@SpringBootApplication
public class RelationalDataAccessApplication implements CommandLineRunner {
  ...
  @Override
  public void run(String... strings) throws Exception {
     ...
  }
   ...
}
  • A simple customer object is used to demonstrate persistence features with JdbcTemplate
  • The example uses a Spring template class called JdbcTemplate that makes it easy to work with SQL relational databases and JDBC.
  • The example includes a class (RelationalDataAccessApplication) that can store and retrieve data over JDBC.
  • Instead of using System.out.print statements to print messages to the console, we use sl4j (already included with Spring Boot) to manage all messages printed to the console (use a log frameworks has several advantages over System.out.print statements (for example, allows define message levels and redirectig output for a file instead of console, etc).
@SpringBootApplication
public class RelationalDataAccessApplication implements CommandLineRunner {
   ...
   private static final Logger log = LoggerFactory.getLogger(RelationalDataAccessApplication.class);
   ...
   log.info("Creating tables");
   ...
}
  • Spring Boot supports H2 (an in-memory relational database engine) and automatically creates a connection to it. Because we use spring-jdbc, Spring Boot automatically creates a JdbcTemplate that is automatically injected in the code.
   @SpringBootApplication
   public class RelationalDataAccessApplication implements CommandLineRunner {
      ...
      @Autowired
      JdbcTemplate jdbcTemplate;
       ...
  }
  • The method execute of JdbcTemplate can be used, for instance, to execute drop table e create tabel SQL DDL statements.
jdbcTemplate.execute("DROP TABLE customers IF EXISTS");
jdbcTemplate.execute("CREATE TABLE customers(id ENTITY, first_name VARCHAR(255), last_name VARCHAR(255))");
  • For single insert statements, the insert method of JdbcTemplate is good, However, for multiple inserts, it is better to use batchUpdate. Suppose splitUpNames is a list of array objects that contains on each object of the array, the first name and the last name of a customer. In this scenario, multiple insertions can be done with just one statement.
 jdbcTemplate.batchUpdate("INSERT INTO customers(first_name, last_name) VALUES (?,?)", splitUpNames);
  • The example demonstrates the use of the method query of the object JdbcTemplate in order to retrieve from the database all customers with firstname equals to 'Josh'.

  • The example takes a list of strings and, by using Java 8 streams, split them into firstname/lastname pairs in a Java array.

  • It is a good practice to use ? for arguments in a SQL Query to avoid SQL injection attacks by instructing JDBC to bind variables.

  • The example uses the query method to search your table for records that match the criteria. You again use the ? arguments to create parameters for the query, passing in the actual values when you make the call. The last argument is a Java 8 lambda that is used to convert each result row into a new Customer object.

log.info("Querying for customer records where first_name = 'Josh':");
        jdbcTemplate.query(
                "SELECT id, first_name, last_name FROM customers WHERE first_name = ?",
                new Object[] { "Josh" },
                (rs, rowNum) -> new Customer(rs.getLong("id"),
                        rs.getString("first_name"), rs.getString("last_name"))
         ).forEach(customer -> log.info(customer.toString()));
  • You can run the application by using ./mvnw spring-boot:run.
  • Alternatively, you can build the JAR file with ./mvnw clean package and then run the JAR file, as follows:
java -jar target/gs-relational-data-access-0.1.0.jar

Example output

06 - managing-transactions

  • Back to Outline
  • Refer to https://spring.io/guides/gs/managing-transactions/ if you are interested on more information about this example.
  • Before trying this example, we suggest reviewing the use of transactions with JDBC (see example 20 - TransactionManagement in the repository located at: https://github.com/pagliares/jdbc-hands-on)
  • The example (managing-transactions) in this repo walks you through the process of wrapping database operations with non-intrusive transactions.
  • You will build a simple JDBC application wherein you make database operations transactional without having to write specialized JDBC code.
  • The examples illustrates a BookingService class to create a JDBC-based service that books people into the system by name.
  • This method is tagged with @Transactional, meaning that any failure causes the entire operation to roll back to its previous state and to re-throw the original exception. This means that none of the people are added to BOOKINGS if one person fails to be added.
  • You also have a findAllBookings method to query the database. Each row fetched from the database is converted into a String, and all the rows are assembled into a List.
@Component
public class BookingService {
    …
    private final JdbcTemplate jdbcTemplate;
    …
    …
    @Transactional
    public void book(String... persons) {
        for (String person : persons) {
            logger.info("Booking " + person + " in a seat...");
            jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
        }
    }

   
    public List findAllBookings() {
        return jdbcTemplate.query("select FIRST_NAME from BOOKINGS",
                (rs, rowNum) -> rs.getString("FIRST_NAME"));
    }

}
  • Notice this example application actually has zero configuration. Spring Boot detects spring-jdbc and h2 on the classpath and automatically creates a DataSource and a JdbcTemplate for you. Because this infrastructure is now available and you have no dedicated configuration, a DataSourceTransactionManager is also created for you. This is the component that intercepts the method annotated with @Transactional (for example, the book method on BookingService). The BookingService is detected by classpath scanning.

  • Another Spring Boot feature demonstrated in this example is the ability to initialize the schema on startup. The following file (from src/main/resources/schema.sql) defines the database schema:

drop table BOOKINGS if exists;
create table BOOKINGS(ID serial, FIRST_NAME varchar(5) NOT NULL);
  • There is also a CommandLineRunner that injects the BookingService and performs some database transactions on its run method.
@Component
class AppRunner implements CommandLineRunner {
    ...
    private final BookingService bookingService;

    public AppRunner(BookingService bookingService) {
        this.bookingService = bookingService;
    }
    
    ...
    
    @Override
    public void run(String... args) throws Exception {
        bookingService.book("Alice", "Bob", "Carol");
        Assert.isTrue(bookingService.findAllBookings().size() == 3,
                "First booking should work with no problem");
        logger.info("Alice, Bob and Carol have been booked");

  • If you use Maven, you can run the application by using ./mvnw spring-boot:run. Alternatively, you can build the JAR file with ./mvnw clean package and then run the JAR file, as follows:
java -jar target/gs-managing-transactions-0.1.0.jar

07 - accessing-data-jpa

  • Back to Outline
  • Refer to https://spring.io/guides/gs/accessing-data-jpa/ if you are interested on more information about this example.
  • This example walks you through the process of building an application that uses Spring Data JPA to store and retrieve data in a relational database.
  • This example is a simple application that uses Spring Data JPA to save objects to and fetch them from a database, all without writing a concrete repository implementation.
  • You will build an application that stores Customer POJOs (Plain Old Java Objects) in a memory-based database.
  • In this example, you store Customer objects, each annotated as a JPA entity.
  • You also have two constructors in Customer entity. The default constructor exists only for the sake of JPA. You do not use it directly, so it is designated as protected.
  • Spring Data JPA focuses on using JPA to store data in a relational database. Its most compelling feature is the ability to create repository implementations automatically, at runtime, from a repository interface.
public interface CustomerRepository extends CrudRepository {
    List findByLastName(String lastName);
    Customer findById(long id);
}
  • CustomerRepository extends the CrudRepository interface. The type of entity and ID that it works with, Customer and Long, are specified in the generic parameters on CrudRepository.
  • By extending CrudRepository, CustomerRepository inherits several methods for working with Customer persistence, including methods for saving, deleting, and finding Customer entities.
  • Spring Data JPA also lets you define other query methods by declaring their method signature. For example, CustomerRepository includes the findByLastName() method.
  • In a typical Java application, you might expect to write a class that implements CustomerRepository. However, that is what makes Spring Data JPA so powerful: You need not write an implementation of the repository interface. Spring Data JPA creates an implementation when you run the application.
  • To get output (to the console, in this example), you need to set up a logger. Then you need to set up some data and use it to generate output.
  • The AccessingDataJpaApplication class includes a demo() method that puts the CustomerRepository through a few tests:
    • It fetches the CustomerRepository from the Spring application context (not shown explicitly in the code)
    • It saves a handful of Customer objects, demonstrating the save() method and setting up some data to work with.
      • It calls findAll() to fetch all Customer objects from the database.
      • It calls findById() to fetch a single Customer by its ID.
      • It calls findByLastName() to find all customers whose last name is "Bauer".
  • The demo() method returns a CommandLineRunner bean that automatically runs the code when the application launches.
@Bean
public CommandLineRunner demo(CustomerRepository repository) {
   return (args) -> {
      // save a few customers
      log.info("Saving few customers with save method of CustomerRepository:");

      repository.save(new Customer("Jack", "Bauer"));
      repository.save(new Customer("Chloe", "O'Brian"));
      repository.save(new Customer("Kim", "Bauer"));
      repository.save(new Customer("David", "Palmer"));
      repository.save(new Customer("Michelle", "Dessler"));

       // fetch all customers
       log.info("Customers found with findAll():");
       log.info("-------------------------------");
       for (Customer customer : repository.findAll()) {
          log.info(customer.toString());
       }
       log.info("");

        // fetch an individual customer by ID
        Customer customer = repository.findById(1L);

        log.info("Customer found with findById(1L):");
        log.info("--------------------------------");
        log.info(customer.toString());
        log.info("");

         // fetch customers by last name - Using lambda - Java SE 8
         log.info("Customer found with findByLastName('Bauer'):");
         log.info("--------------------------------------------");
         repository.findByLastName("Bauer").forEach(bauer -> {
           log.info(bauer.toString());
         });

         // fetch customers by last name - loop for, Pre-Java SE 8
         // for (Customer bauer : repository.findByLastName("Bauer")) {
         //    log.info(bauer.toString());
         // }
         log.info("");
        };
    }
  • By default, Spring Boot enables JPA repository support and looks in the package (and its subpackages) where @SpringBootApplication is located. If your configuration has JPA repository interface definitions located in a package that is not visible, you can point out alternate packages by using @EnableJpaRepositories and its type-safe basePackageClasses=MyRepository.class parameter.

Part III - RESTful with Spring Boot

08 - accessing-data-rest

  • Back to Outline

  • Refer to https://spring.io/guides/gs/accessing-data-rest/ if you are interested on more information about this example.

  • This example walks you through the process of creating an application that accesses relational JPA-based backend data through a hypermedia-based RESTful front end.

  • You will build a Spring application that lets you create and retrieve Person objects stored in a database by using Spring Data REST. Spring Data REST takes the features of Spring HATEOAS and Spring Data JPA and automatically combines them together.

  • Spring Data REST also supports Spring Data Neo4j, Spring Data Gemfire, and Spring Data MongoDB as backend data stores, but those are not part of this example.

  • Dependencies: Rest Repositories, Spring Data JPA, and H2 Database.

  • The example uses a domain object to present a person

  • At the end of this example, student will understand how to make a domain object accessible to the world through HTTP and how to perfom CRUD operation on it by using a command-line tool.

  • The examples will enable the student to try communicating with the RESTful endpoints by using the URLs

  • The Person object has a first name and a last name. There is also an ID object that is configured to be automatically generated, so you need not deal with that.

  • The example uses a repository (interface PersonRepository).

  • This repository is an interface that lets you perform various operations involving Person objects. It gets these operations by extending the PagingAndSortingRepository interface that is defined in Spring Data Commons.

  • At runtime, Spring Data REST automatically creates an implementation of this interface. Then it uses the @RepositoryRestResource annotation to direct Spring MVC to create RESTful endpoints at /people.

  • @RepositoryRestResource is not required for a repository to be exported. It is used only to change the export details, such as using /people instead of the default value of /persons.

  • The Repository defines a custom query to retrieve a list of Person objects based on the lastName.

  • Spring Boot automatically spins up Spring Data JPA to create a concrete implementation of the PersonRepository and configure it to talk to a back end in-memory database by using JPA.

  • Spring Data REST builds on top of Spring MVC. It creates a collection of Spring MVC controllers, JSON converters, and other beans to provide a RESTful front end. These components link up to the Spring Data JPA backend. When you use Spring Boot, this is all autoconfigured.

  • You can use any REST client you wish to test the example. The following examples use the *nix tool, curl:

  • The following example shows how to do see the top level service (Notice in the output that there is a people link located at http://localhost:8080/people. It has some options, such as ?page, ?size, and ?sort

$ curl http://localhost:8080
{
  "_links" : {
    "people" : {
      "href" : "http://localhost:8080/people{?page,size,sort}",
      "templated" : true
    }
  }
}
  • The following example shows how to see the people records (none at present):
$ curl http://localhost:8080/people
{
  "_embedded" : {
    "people" : []
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people{?page,size,sort}",
      "templated" : true
    },
    "search" : {
      "href" : "http://localhost:8080/people/search"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 0,
    "totalPages" : 0,
    "number" : 0
  }
}
  • The following listing shows how to create a new Person:
$ curl -i -H "Content-Type:application/json" -d '{"firstName": "Frodo", "lastName": "Baggins"}' http://localhost:8080/people
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:8080/people/1
Content-Length: 0
Date: Wed, 26 Feb 2014 20:26:55 GMT

-i: Ensures you can see the response message including the headers. The URI of the newly created Person is shown (http://localhost:8080/people/1). -H "Content-Type:application/json": Sets the content type so the application knows the payload contains a JSON object. -d '{"firstName": "Frodo", "lastName": "Baggins"}': Is the data being sent.

  • Note: on Windows you might need to replace the single quotes with double quotes and escape the existing double quotes,

    • i.e. -d "{"firstName": "Frodo", "lastName": "Baggins"}".
  • Notice how the response to the POST operation includes a Location header. This contains the URI of the newly created resource (http://localhost:8080/people/1).

  • The following example shows how to query for all people:

$ curl http://localhost:8080/people
{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people{?page,size,sort}",
      "templated" : true
    },
    "search" : {
      "href" : "http://localhost:8080/people/search"
    }
  },
  "_embedded" : {
    "people" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/people/1"
        }
      }
    } ]
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}
  • You can query directly for the individual record, as follows:
$ curl http://localhost:8080/people/1
{
  "firstName" : "Frodo",
  "lastName" : "Baggins",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/1"
    }
  }
}
  • This might appear to be purely web-based. However, behind the scenes, there is an H2 relational database. In production, you would probably use a real one, such as PostgreSQL.
  • In this exemple, there is only one domain object. With a more complex system, where domain objects are related to each other, Spring Data REST renders additional links to help navigate to connected records.
  • The following example shows hot to find all the custom queries
    • You can see the URL for the query, including the HTTP query parameter, name.
    • Note that this matches the @Param("name") annotation embedded in the interface:
$ curl http://localhost:8080/people/search
{
  "_links" : {
    "findByLastName" : {
      "href" : "http://localhost:8080/people/search/findByLastName{?name}",
      "templated" : true
    }
  }
}
  • The following example shows how to use the findByLastName query:
$ curl 'http://localhost:8080/people/search/findByLastName?name=Baggins'
{
  "_embedded" : {
    "persons" : [ {
      "firstName" : "Frodo",
      "lastName" : "Baggins",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/people/1"
        }
      }
    } ]
  }
}
  • You can also issue PUT, PATCH, and DELETE REST calls to replace, update, or delete existing records (respectively). The following example uses a PUT call:
$ curl -X PUT -H "Content-Type:application/json" -d '{"firstName": "Bilbo", "lastName": "Baggins"}' http://localhost:8080/people/1
$ curl http://localhost:8080/people/1
{
  "firstName" : "Bilbo",
  "lastName" : "Baggins",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/1"
    }
  }
}
  • The following example uses a PATCH call:
$ curl -X PATCH -H "Content-Type:application/json" -d '{"firstName": "Bilbo Jr."}' http://localhost:8080/people/1
$ curl http://localhost:8080/people/1
{
  "firstName" : "Bilbo Jr.",
  "lastName" : "Baggins",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/1"
    }
  }
}
  • PUT replaces an entire record. Fields not supplied are replaced with null. You can use PATCH to update a subset of items.

  • You can also delete records, as the following example shows:

$ curl -X DELETE http://localhost:8080/people/1
$ curl http://localhost:8080/people
{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people{?page,size,sort}",
      "templated" : true
    },
    "search" : {
      "href" : "http://localhost:8080/people/search"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 0,
    "totalPages" : 0,
    "number" : 0
  }
}
  • A convenient aspect of this hypermedia-driven interface is that you can discover all the RESTful endpoints by using curl (or whatever REST client you like). You need not exchange a formal contract or interface document with your customers.

09 - rest-service

  • Back to Outline
  • Refer to https://spring.io/guides/gs/rest-service/ if you are interested on more information about this example.
  • This example walks you through the process of creating a “Hello, World” RESTful web service with Spring.
  • Dependencies: Spring Web.
  • The example uses the Jackson JSON library to automatically marshal instances of type Greeting into JSON. Jackson is included by default by the web starter.
  • You will build a service that will accept HTTP GET requests at http://localhost:8080/greeting. It will respond with a JSON representation of a greeting, as the following listing shows:
{"id":1,"content":"Hello, World!"}
  • You can customize the greeting with an optional name parameter in the query string, as the following listing shows:
http://localhost:8080/greeting?name=User
{"id”:2,”content":"Hello, User!"}
  • To model the greeting representation, create a resource representation class (Greeting POJO).
package com.example.restservice;

public class Greeting {

   private final long id;
   private final String content;

   public Greeting(long id, String content) {
	this.id = id;
	this.content = content;
   }

   public long getId() {
	return id;
   }

   public String getContent() {
	return content;
   }
}
  • In Spring’s approach to building RESTful web services, HTTP requests are handled by a controller.
  • These components are identified by the @RestController annotation
  • the GreetingController shown in the following listing handles GET requests for /greeting by returning a new instance of the Greeting class:
@RestController
public class GreetingController {

   private static final String template = "Hello, %s!";
   private final AtomicLong counter = new AtomicLong();

   @GetMapping("/greeting")
   public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
	return new Greeting(counter.incrementAndGet(), String.format(template, name));
   }
}
  • The @GetMapping annotation ensures that HTTP GET requests to /greeting are mapped to the greeting() method.
  • There are companion annotations for other HTTP verbs (e.g. @PostMapping for POST). There is also a @RequestMapping annotation that they all derive from, and can serve as a synonym (e.g. @RequestMapping(method=GET)).
  • @RequestParam binds the value of the query string parameter name into the name parameter of the greeting() method. If the name parameter is absent in the request, the defaultValue of World is used.
  • The implementation of the method body creates and returns a new Greeting object with id and content attributes based on the next value from the counter and formats the given name by using the greeting template.
  • Important: A key difference between a traditional MVC controller and the RESTful web service controller shown earlier is the way that the HTTP response body is created. Rather than relying on a view technology to perform server-side rendering of the greeting data to HTML, this RESTful web service controller populates and returns a Greeting object. The object data will be written directly to the HTTP response as JSON.
  • This example code uses Spring @RestController annotation, which marks the class as a controller where every method returns a domain object instead of a view. It is shorthand for including both @Controller and @ResponseBody.
  • The Greeting object must be converted to JSON. Thanks to Spring’s HTTP message converter support, you need not do this conversion manually. Because Jackson 2 is on the classpath, Spring’s MappingJackson2HttpMessageConverter is automatically chosen to convert the Greeting instance to JSON.
  • Notice also how the id attribute has changed from 1 to 2 and so on after each request. This proves that you are working against the same GreetingController instance across multiple requests and that its counter field is being incremented on each call as expected.

10 - quoters and consuming-rest

  • Back to Outline
  • Refer to https://spring.io/guides/gs/consuming-rest/ if you are interested on more information about this example.
  • This example walks you through the process of creating an application that consumes a RESTful web service.
  • You will build an application that uses Spring’s RestTemplate to retrieve a random Spring Boot quotation at http://localhost:8080/api/random
  • Dependencies in the consuming-rest project: Spring Web.
  • To run the example (consuming-rest) we need a source of REST resources. We use a second project for this purpose (quoters, from https://github.com/spring-guides/quoters)
  • You can run quoters application in a separate terminal and access the result at http://localhost:8080/api/random.
    • That address randomly fetches a quotation about Spring Boot and returns it as a JSON document.
  • Other valid addresses include
  • Dependencies in the quoters project:
    • Spring MVC
    • Spring Data JPA
    • H2 embedded database
  • If you request that URL through a web browser or curl, you receive a JSON document that looks something like this:
$ curl http://localhost:8080/api/random
{
   type: "success",
   value: {
      id: 10,
      quote: "Really loving Spring Boot, makes stand alone Spring apps easy."
   }
}
  • A more useful way to consume a REST web service is programmatically. To help you with that task, Spring provides a convenient template class called RestTemplate.
  • RestTemplate makes interacting with most RESTful services a one-line incantation. And it can even bind that data to custom domain types.
  • The example presents a domain class (Quote) that contains the data that you need.
package com.example.consumingrest;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Quote {

  private String type;
  private Value value;

  ...  // getters and setters ommited

 
}

  • It is annotated with @JsonIgnoreProperties from the Jackson JSON processing library to indicate that any properties not bound in this type should be ignored.
  • To directly bind your data to your custom types, you need to specify the variable name to be exactly the same as the key in the JSON document returned from the API.
  • In case your variable name and key in JSON doc do not match, you can use @JsonProperty annotation to specify the exact key of the JSON document.
    • This example matches each variable name to a JSON key, so you do not need that annotation here.
  • The example also shwos a ConsumingRestApplication class to get it to show quotations from our RESTful source. The class has:
    • A logger, to send output to the log (the console, in this example).
    • A RestTemplate, which uses the Jackson JSON processing library to process the incoming data.
    • A CommandLineRunner that runs the RestTemplate (and, consequently, fetches our quotation) on startup.
@SpringBootApplication
public class ConsumingRestApplication {

	private static final Logger log = LoggerFactory.getLogger(ConsumingRestApplication.class);

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

	@Bean
	public RestTemplate restTemplate(RestTemplateBuilder builder) {
		return builder.build();
	}

	@Bean
	public CommandLineRunner run(RestTemplate restTemplate) throws Exception {
		return args -> {
			Quote quote = restTemplate.getForObject("http://localhost:8080/api/random", Quote.class);
			log.info(quote.toString());
		};
	}
}

  • Since the quoters project is already running on port 8080, you MUST initialize the project consuming-rest in another port (if you don't do this, when try running consumming-rest you will get an error of port 8080 already in use (by quoters application).
  • To change de default port (8080) to another one (for example, 9090) include the line below in the file src/main/resources/application.properties of the consuming-rest project and run again the application:
server.port=9090
.
  • If you see an error that reads, Could not extract response: no suitable HttpMessageConverter found for response type [class com.example.consumingrest.Quote], it is possible that you are in an environment that cannot connect to the backend service (which sends JSON if you can reach it). Maybe you are behind a corporate proxy. Try setting the http.proxyHost and http.proxyPort system properties to values appropriate for your environment.

11 - actuator-service

  • Back to Outline
  • Refer to https://spring.io/guides/gs/actuator-service/ if you are interested on more information about this example.
  • Spring Boot Actuator is a sub-project of Spring Boot. It adds several production grade services to your application with little effort on your part.
  • In this example, you will build an application and then see how to add these services.
  • In this example, you will build a simple RESTful service with Spring and add to it some useful built-in services with Spring Boot Actuator.
  • This example takes you through creating a “Hello, world” RESTful web service with Spring Boot Actuator.
  • You will build a service that handles GET requests for /hello-world, optionally with a name query parameter.
$ curl http://localhost:9000/hello-world
  • It responds with the following JSON:
{
    "id": 1,
    "content": "Hello, World!"
}
  • The id field is a unique identifier for the greeting, and content contains the textual representation of the greeting.

  • Dependencies of the example: Spring Web and Spring Boot Actuator.

  • The @SpringBootApplication annotation provides a load of defaults (like the embedded servlet container), depending on the contents of your classpath and other things. It also turns on Spring MVC’s @EnableWebMvc annotation, which activates web endpoints.

public class Greeting {

  private final long id;
  private final String content;

  // Getters and setters ommited
  ...
}
  • The example includes a endpoint controller that will serve the representation class.
  • In Spring, REST endpoints are Spring MVC controllers.
  • The example illustrates how to handle a GET request for the /hello-world endpoint and returns the Greeting resource:
@Controller 
public class HelloWorldController {

  private static final String template = "Hello, %s!";
  private final AtomicLong counter = new AtomicLong();

  @GetMapping("/hello-world")
  @ResponseBody
  public Greeting sayHello(@RequestParam(name="name", required=false, defaultValue="Stranger") String name) {
    return new Greeting(counter.incrementAndGet(), String.format(template, name));
  }

}
  • The key difference between a human-facing controller and a REST endpoint controller is in how the response is created. Rather than rely on a view (such as JSP or Thymeleaf) to render model data in HTML, an endpoint controller returns the data to be written directly to the body of the response.
  • The @ResponseBody annotation tells Spring MVC not to render a model into a view but, rather, to write the returned object into the response body. It does so by using one of Spring’s message converters. Because Jackson 2 is in the classpath, MappingJackson2HttpMessageConverter will handle the conversion of a Greeting object to JSON if the request’s Accept header specifies that JSON should be returned.
  • Spring Boot Actuator defaults to running on port 8080. By adding an application.properties file, you can override that setting.
server.port: 9000
management.server.port: 9001
management.server.address: 127.0.0.1
  • You can test that it is working on port 9000 by running the following commands in a terminal:
$ curl localhost:9000/hello-world
{"id":1,"content":"Hello, Stranger!"}
$ curl localhost:9000/actuator/health
{"status":"UP"}
  • The status is UP, so the actuator service is running.
  • The example also presents a unit and an integration tests for your application that ensures that your controller and management endpoints are responsive.
  • Note that the tests start the application on a random port.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {"management.port=0"})
public class HelloWorldApplicationTests {

    @LocalServerPort
    private int port;

    @Value("${local.management.port}")
    private int mgt;

    @Autowired
    private TestRestTemplate testRestTemplate;

    @Test
    public void shouldReturn200WhenSendingRequestToController() throws Exception {
        …

    }

    @Test
    public void shouldReturn200WhenSendingRequestToManagementEndpoint() throws Exception {
       …
    }

}

12 - Building a Hypermedia-Driven RESTful Web Service

12.1 Introduction

  • This example walks you through the process of creating a “Hello, World” Hypermedia-driven REST web service with Spring HATEOAS.
  • Hypermedia is an important aspect of REST. It lets you build services that decouple client and server to a large extent and let them evolve independently.
  • The representations returned for REST resources contain not only data but also links to related resources. Thus, the design of the representations is crucial to the design of the overall service.
  • Spring HATEOAS: a library of APIs that you can use to create links that point to Spring MVC controllers, build up resource representations, and control how they are rendered into supported hypermedia formats (such as HAL).
  • Dependencies used in this example (spring boot Maven project): Spring HATEOAS.

12.2 Resource representation class

  • To model the greeting representation, create a resource representation class.
public class Greeting extends RepresentationModel {

	private final String content;

	@JsonCreator
	public Greeting(@JsonProperty("content") String content) {
		this.content = content;
	}

	public String getContent() {
		return content;
	}
}
- <strong>@JsonCreator</strong>: Signals how Jackson can create an instance of this POJO.
- <strong>@JsonProperty</strong>: Marks the field into which Jackson should put this constructor argument.
  • Spring will use the Jackson JSON library to automatically marshal instances of type Greeting into JSON.

12.3 REST controller

  • In Spring’s approach to building RESTful web services, HTTP requests are handled by a controller.
  • We use the @RestController annotation, which combines the @Controller and @ResponseBody annotations.
@RestController
public class GreetingController {

	private static final String TEMPLATE = "Hello, %s!";

	@RequestMapping("/greeting")
	public HttpEntity greeting(
		@RequestParam(value = "name", defaultValue = "World") String name) {

		Greeting greeting = new Greeting(String.format(TEMPLATE, name));
		greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());

		return new ResponseEntity<>(greeting, HttpStatus.OK);
	}
}

  • The @RequestMapping annotation ensures that HTTP requests to /greeting are mapped to the greeting() method.

  • The above example does not specify GET vs. PUT, POST, and so forth, because @RequestMapping maps all HTTP operations by default. Use @GetMapping("/greeting") to narrow this mapping.

  • Because the @RestController annotation is present on the class, an implicit @ResponseBody annotation is added to the greeting method. This causes Spring MVC to render the returned HttpEntity and its payload (the Greeting) directly to the response.

  • The most interesting part of the method implementation is how you create the link that points to the controller method and how you add it to the representation model. Both linkTo(…) and methodOn(…) are static methods on ControllerLinkBuilder that let you fake a method invocation on the controller. The returned LinkBuilder will have inspected the controller method’s mapping annotation to build up exactly the URI to which the method is mapped.

  • The call to withSelfRel() creates a Link instance that you add to the Greeting representation model.

12.4 Testing the service

http://localhost:8080/greeting
{
  "content":"Hello, World!",
  "_links":{
    "self":{
      "href":"http://localhost:8080/greeting?name=World"
    }
  }
}
http://localhost:8080/greeting?name=User

{
  "content":"Hello, User!",
  "_links":{
    "self":{
      "href":"http://localhost:8080/greeting?name=User"
    }
  }
}

13 - Enabling Cross Origin Requests for a RESTful Web Service

Introduction

  • This example presents a “Hello, World” RESTful web service with Spring that includes headers for Cross-Origin Resource Sharing (CORS) in the response.
  • This example also presents a simple jQuery client that consumes a Spring MVC-based RESTful web service.
  • You can find more information about Spring CORS support in this blog post.
  • Dependencies: Spring Web and Apache httpclient5.

The httpclient Dependency

  • To test this example, you need, in addition to Spring Boot dependencies, the Apache httpclient library. See pom.xml for details.

Resource Representation Class

public class Greeting {
    ...
    private final long id;
    private final String content;
    ... 
    ... // Getters and setters ommited
}

Resource controller

@RestController
public class GreetingController {

	private static final String template = "Hello, %s!";

	private final AtomicLong counter = new AtomicLong();
	@CrossOrigin(origins = "http://localhost:8080")
	@GetMapping("/greeting")
	public Greeting greeting(@RequestParam(required = false, defaultValue = "World") String name) {
		System.out.println("==== get greeting ====");
		return new Greeting(counter.incrementAndGet(), String.format(template, name));
	}

}
  • In the example, query string parameter is not required. If it is absent in the request, the defaultValue of World is used.
  • @RestController annotation assumes that every method inherits the @ResponseBody semantics by default. Therefore, a returned object data is inserted directly into the response body.

Enabling CORS

  • You can enable cross-origin resource sharing (CORS) from either in individual controllers or globally.
  • You can even combine global and controller-level CORS configuration.

Controller Method CORS Configuration

  • Add a @CrossOrigin annotation to the handler method
@CrossOrigin(origins = "http://localhost:8080")
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(required = false, defaultValue = "World") String name) {
	System.out.println("==== get greeting ====");
	return new Greeting(counter.incrementAndGet(), String.format(template, name));
  • This @CrossOrigin annotation enables cross-origin resource sharing only for this specific method.

  • By default, it allows all origins, all headers, and the HTTP methods specified in the @RequestMapping annotation.

  • Also, a maxAge of 30 minutes is used. You can customize this behavior by specifying the value of one of the following annotation attributes:

    • origins
    • methods
    • allowedHeaders
    • exposedHeaders
    • allowCredentials
    • maxAge.
  • In this example, we allow only http://localhost:8080 to send cross-origin requests.

  • You can also add the @CrossOrigin annotation at the controller class level as well, to enable CORS on all handler methods of this class.

Global CORS Configuration

  • In addition (or as an alternative) to fine-grained annotation-based configuration, you can define some global CORS configuration as well.
  • By default, all origins and GET, HEAD, and POST methods are allowed.
@GetMapping("/greeting-javaconfig")
public Greeting greetingWithJavaconfig(@RequestParam(required = false, defaultValue = "World") String name) {
   System.out.println("==== in greeting ====");
   return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
  • The difference between the greetingWithJavaconfig method and the greeting method (used in the controller-level CORS configuration) is the route (/greeting-javaconfig rather than /greeting) and the presence of the @CrossOrigin origin.
  • The following example, shows how to add CORS mapping in the application class:
  • You need to add a method in the Application class generated when creating the project to configure how to handle cross-origin resource sharing.
@SpringBootApplication
public class RestServiceCorsApplication {

   ...
   ...
   
   @Bean
   public WebMvcConfigurer corsConfigurer() {
	return new WebMvcConfigurer() {
		@Override
		public void addCorsMappings(CorsRegistry registry) {
			registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:8080");
		}
	};
  }
}

Run the applicationr

./mvnw spring-boot:run

Build an executable jar file

./mvnw clean package java -jar target/gs-rest-service-cors-0.1.0.jar

Test the service

{"id":1,"content":"Hello, World!"}
 {"id":2,"content":"Hello, Florentino!"} 
  • Now you can test that the CORS headers are in place and allow a Javascript client from another origin to access the service.
  • To do so, you need to create a Javascript client to consume the service. The following listing shows such a client (hello.js)
$(document).ready(function() {
    $.ajax({
        url: "http://localhost:8080/greeting"
    }).then(function(data, status, jqxhr) {
       $('.greeting-id').append(data.id);
       $('.greeting-content').append(data.content);
       console.log(jqxhr);
    });
});
  • This javascript client is represented as a simple JavaScript function. It uses jQuery’s $.ajax() methodto consume the REST service at http://localhost:8080/greeting.
  • If successful, it will assign the JSON received to data, effectively making it a Greeting model object. The id and content are then appended to the greeting-id and greeting-content DOM elements respectively.
  • Note the use of the jQuery promise .then(). This directs jQuery to execute the anonymous function when the $.ajax() method completes, passing the data result from the completed AJAX request.
  • This javascript client uses jQuery to consume the REST service at http://localhost:8080/greeting. It is loaded by index.html nested inside the head element:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="hello.js"></script>
  • The first script tag loads the minified jQuery library (jquery.min.js) from a content delivery network (CDN) so that you don’t have to download jQuery and place it in your project.
  • It also loads the controller code (hello.js) from the application’s path.
  • Also note that the p tags (see index.html) include class attributes.
  • These class attributes help jQuery to reference the HTML elements and update the text with the values from the id and content properties of the JSON received from the REST service.
  • Once the app starts, open http://localhost:8080 in your browser, where you should see the following:
The ID is 1
The content is, Hello World!
  • To test the CORS behaviour, you need to start the client from another server or port. Doing so not only avoids a collision between the two applications but also ensures that the client code is served from a different origin than the service.
  • To start the app running on localhost at port 9000 (as well as the one that is already running on port 8080), run the following Maven command:
./mvnw spring-boot:run -Dspring-boot.run.arguments=--server.port=9000

  • Note: the spring guide uses the following command (instead of the previous one). This command did not work on my machine:
 ./mvnw spring-boot:run -Dserver.port=9000 
The ID is 
The content is
  • If the service response includes the CORS headers, then the ID and content are rendered into the page.
  • But if the CORS headers are missing (or insufficient for the client), the browser fails the request and the values are not rendered into the DOM.

Part IV - Spring MVC

14 - Serving Web Content with Spring MVC

Introduction

  • This example walks you through the process of creating a “Hello, World” web site with Spring.
  • You will build an application that has a static home page and that will also accept HTTP GET requests at: http://localhost:8080/greeting.
  • It will respond with a web page that displays HTML. The body of the HTML will contain a greeting: “Hello, World!”
  • You can customize the greeting with an optional name parameter in the query string. The URL might then be http://localhost:8080/greeting?name=User.
  • The name parameter value overrides the default value of World and is reflected in the response by the content changing to “Hello, User!”
  • Dependencies: Spring Web, Thymeleaf, and Spring Boot DevTools.

Web Controller

  • In Spring’s approach to building web sites, HTTP requests are handled by a controller. You can easily identify the controller by the @Controller annotation.
  • In the following example, GreetingController handles GET requests for /greeting by returning the name of a View (in this case, greeting). A View is responsible for rendering the HTML content.
@Controller
public class GreetingController {

	@GetMapping("/greeting")
	public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) {
		model.addAttribute("name", name);
		return "greeting";
	}
}
  • The value of the name parameter is added to a Model object, ultimately making it accessible to the view template.
  • The implementation of the method body relies on a view technology (in this case, Thymeleaf) to perform server-side rendering of the HTML. Thymeleaf parses the greeting.html template and evaluates the th:text expression to render the value of the ${name} parameter that was set in the controller.

Spring Boot Devtools

  • A common feature of developing web applications is coding a change, restarting your application, and refreshing the browser to view the change. This entire process can eat up a lot of time. To speed up this refresh cycle, Spring Boot offers with a handy module known as spring-boot-devtools. Spring Boot Devtools:
    • Enables hot swapping.
    • Switches template engines to disable caching.
    • Enables LiveReload to automatically refresh the browser.
    • Other reasonable defaults based on development instead of production.

Test the Application

Home Page

  • Static resources, including HTML and JavaScript and CSS, can be served from your Spring Boot application by dropping them into the right place in the source code.
  • By default, Spring Boot serves static content from resources in the classpath at /static (or /public).
  • The index.html resource is special because, if it exists, it is used as a welcome page for the serving-web-content application, which means it is served up as the root resource

15 - Handling Form Submission

Introduction

  • This example walks you through the process of using Spring to create and submit a web form, which will be accessible at the following URL:
  • Dependencies: Spring Web and Thymeleaf.

Web Controller

@Controller
public class GreetingController {

  @GetMapping("/greeting")
  public String greetingForm(Model model) {
    model.addAttribute("greeting", new Greeting());
    return "greeting";
  }

  @PostMapping("/greeting")
  public String greetingSubmit(@ModelAttribute Greeting greeting, Model model) {
    model.addAttribute("greeting", greeting);
    return "result";
  }
}
  • The two methods in the controller are both mapped to /greeting.
  • You can use @RequestMapping (which, by default, maps all HTTP operations, such as GET, POST, and so forth). However, in this case, the greetingForm() method is specifically mapped to GET by using @GetMapping, while greetingSubmit() is mapped to POST with @PostMapping.
  • This mapping lets the controller differentiate the requests to the /greeting endpoint.
  • The greetingForm() method uses a Model object to expose a new Greeting to the view template.

Model

public class Greeting {

  private long id;
  private String content;
  
  … // Getters and Setters omitted
}
  • The fields id and content of the Greeting class correspond to the form fields in the greeting view and are used to capture the information from the form.
  • The implementation of the method greetingSubmit relies on a view technology to perform server-side rendering of the HTML by converting the view name (greeting) into a template to render.
  • In this case, we use Thymeleaf, which parses the greeting.html template and evaluates the various template expressions to render the form.

  • The th:action="@{/greeting}" expression directs the form to POST to the /greeting endpoint, while the th:object="${greeting}" expression declares the model object to use for collecting the form data.
  • The two form fields, expressed with th:field="{id}" and th:field="{content}", correspond to the fields in the Greeting object.
  • the form submits to the /greeting endpoint by using a POST call. The greetingSubmit() method receives the Greeting object that was populated by the form. The Greeting is a @ModelAttribute, so it is bound to the incoming form content.
  • The submitted data can be rendered in the result view by referring to it by name (by default, the name of the method parameter, so greeting in this case).
  • The id is rendered in the

    expression.

  • Likewise, the content is rendered in the

    expression.

  • This example uses two separate view templates for rendering the form and displaying the submitted data. However, you can use a single view for both purposes.

Make the Application Executable

  • Although you can package this service as a traditional WAR file for deployment to an external application server, the simpler approach is to create a standalone application (JAR).
    • This selection is done when creating the project with Spring Initializr.
  • You package everything in a single, executable JAR file, driven by a main() method.
  • You use Spring’s support for embedding the Tomcat servlet container as the HTTP runtime, instead of deploying to an external instance.

Test the service

  • Submit an ID and message to see the results:

16 - Validating Form Input

Introduction

  • This example walks you through the process of configuring a web application form to support validation.
  • You will build a simple Spring MVC application that takes user input and checks the input by using standard validation annotations.
  • You will also see how to display the error message on the screen so that the user can re-enter input to make it be valid.
  • Dependencies: Spring Web, Thymeleaf, and Validation.

PersonForm Object

  • The application involves validating a user’s name and age, so you first need a class that backs the form used to create a person.
public class PersonForm {

   @NotNull
   @Size(min=2, max=30)
   private String name;

   @NotNull
   @Min(18)
   private Integer age;

   … // Getters and setters omitted

   public String toString() {
	return "Person(Name: " + this.name + ", Age: " + this.age + ")";
   }
}
  • @Size(min=2, max=30): Allows names between 2 and 30 characters long.
  • @NotNull: Does not allow a null value, which is what Spring MVC generates if the entry is empty.
  • @Min(18): Does not allow the age to be less than 18.

Web Controller

@Controller
public class WebController implements WebMvcConfigurer {

   @Override
   public void addViewControllers(ViewControllerRegistry registry) {
	registry.addViewController("/results").setViewName("results");
   }

   @GetMapping("/")
   public String showForm(PersonForm personForm) {
	return "form";
   }

   @PostMapping("/")
   public String checkPersonInfo(@Valid PersonForm personForm, BindingResult bindingResult) {

	if (bindingResult.hasErrors()) {
		return "form";
	}

	return "redirect:/results";
   }
}
  • This controller has a GET method and a POST method. Both methods are mapped to /.

  • The showForm method returns the form template. It includes a PersonForm in its method signature so that the template can associate form attributes with a PersonForm.

  • The checkPersonInfo method accepts two arguments:

    • A personForm object marked with @Valid to gather the attributes filled out in the form.
    • A bindingResult object so that you can test for and retrieve validation errors.
  • You can retrieve all the attributes from the form, which is bound to the PersonForm object.

  • In the code, you test for errors. If you encounter an error, you can send the user back to the original form template. In that situation, all the error attributes are displayed.

  • If all of the person’s attribute are valid, it redirects the browser to the final results template.

HTML Front End

  • The form is geared to post to /
  • It is marked as being backed up by the personForm object that you saw in the GET method in the web controller. This is known as a “bean-backed form”.
  • There are two fields in the PersonForm bean, and you can see that they are tagged with th:field="{name}" and th:field="{age}".
  • Next to each field is an element that is used to show any validation errors.
  • In general, if the user enters a name or age that violates the @Valid constraints, it bounces back to this page with the error message displayed. If a valid name and age is entered, the user is routed to the next web page.

Run the application

@SpringBootApplication public class ValidatingFormInputApplication {

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

}

  • To activate Spring MVC, you would normally add @EnableWebMvc to the Application class.
  • But Spring Boot’s @SpringBootApplication already adds this annotation when it detects spring-webmvc on your classpath. This same annotation lets it find the annotated @Controller class and its methods.
  • The Thymeleaf configuration is also taken care of by @SpringBootApplication. By default, templates are located in the classpath under templates/ and are resolved as views by stripping the '.html' suffix off the file name.

Testing the application

  • The following pair of images show what happens if you enter N for the name and 15 for your age and click on Submit:

  • The preceding images show that, because the values violated the constraints in the PersonForm class, you get bounced back to the “main” page.
  • Note that, if you click on Submit with nothing in the entry box, you get a different error, as the following image shows:

  • If you enter a valid name and age, you end up on the results page, as the following image shows:

17 - Uploading Files

Introduction

  • This example walks you through the process of creating a server application that handle file uploads by receiving HTTP multi-part file uploads.
  • The example also build a simple HTML interface to upload a test file.
  • Dependencies: Spring Web and Thymeleaf.

Application class

  • To upload files with Servlet containers, you need to register a MultipartConfigElement class (which would be in web.xml).
  • As part of auto-configuring Spring MVC, Spring Boot will create a MultipartConfigElement bean and make itself ready for file uploads.

File upload controller

@Controller
public class FileUploadController {

	private final StorageService storageService;

	@Autowired
	public FileUploadController(StorageService storageService) {
		this.storageService = storageService;
	}

	@GetMapping("/")
	public String listUploadedFiles(Model model) throws IOException {

		model.addAttribute("files", storageService.loadAll().map(
				path -> MvcUriComponentsBuilder.fromMethodName(FileUploadController.class,
						"serveFile", path.getFileName().toString()).build().toUri().toString())
				.collect(Collectors.toList()));

		return "uploadForm";
	}

	@GetMapping("/files/{filename:.+}")
	@ResponseBody
	public ResponseEntity serveFile(@PathVariable String filename) {

		Resource file = storageService.loadAsResource(filename);
		return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
				"attachment; filename=\"" + file.getFilename() + "\"").body(file);
	}

	@PostMapping("/")
	public String handleFileUpload(@RequestParam("file") MultipartFile file,
			RedirectAttributes redirectAttributes) {

		storageService.store(file);
		redirectAttributes.addFlashAttribute("message",
				"You successfully uploaded " + file.getOriginalFilename() + "!");

		return "redirect:/";
	}

	@ExceptionHandler(StorageFileNotFoundException.class)
	public ResponseEntity handleStorageFileNotFound(StorageFileNotFoundException exc) {
		return ResponseEntity.notFound().build();
	}
}
  • The FileUploadController class is annotated with @Controller so that Spring MVC can pick it up and look for routes.
  • Each method is tagged with @GetMapping or @PostMapping to tie the path and the HTTP action to a particular controller action.
  • GET /: Looks up the current list of uploaded files from the StorageService and loads it into a Thymeleaf template. It calculates a link to the actual resource by using MvcUriComponentsBuilder.
  • GET /files/{filename}: Loads the resource (if it exists) and sends it to the browser to download by using a Content-Disposition response header.
  • POST /: Handles a multi-part message file and gives it to the StorageService for saving.
  • In a production scenario, you more likely would store the files in a temporary location, a database, or perhaps a NoSQL store (such as Mongo’s GridFS). It is best to NOT load up the file system of your application with content.

Storage service

public interface StorageService {

   void init();

   void store(MultipartFile file);

   Stream loadAll();

   Path load(String filename);

   Resource loadAsResource(String filename);

   void deleteAll();

}

HTML template

  • The following Thymeleaf template shows an example of how to upload files and show what has been uploaded:

  • This template has three parts:
    • An optional message at the top where Spring MVC writes a flash-scoped message.
    • A form that lets the user upload files.
    • A list of files supplied from the backend.

Tuning File Upload Limits

  • When configuring file uploads, it is often useful to set limits on the size of files. Imagine trying to handle a 5GB file upload! With Spring Boot, we can tune its auto-configured MultipartConfigElement with some property settings.
  • Add the following properties to your existing properties settings (in src/main/resources/application.properties):
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
  • The multipart settings are constrained as follows:
    • spring.servlet.multipart.max-file-size is set to 128KB, meaning total file size cannot exceed 128KB.
    • spring.servlet.multipart.max-request-size is set to 128KB, meaning total request size for a multipart/form-data cannot exceed 128KB.

Run the application

  • You want a target folder to which to upload files, so you need to enhance the basic UploadingFilesApplication class that Spring Initializr created and add a Boot CommandLineRunner to delete and re-create that folder at startup.
@SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
public class UploadingFilesApplication {
   ...
   ...
	
   @Bean
   CommandLineRunner init(StorageService storageService) {
	return (args) -> {
	   storageService.deleteAll();
	   storageService.init();
	};
   }
}

Testing the application

  • With the server running, you need to open a browser and visit http://localhost:8080/ to see the upload form.
  • Pick a (small) file and press Upload. You should see the success page from the controller. If you choose a file that is too large, you will get an ugly error page.
  • You should then see a line resembling the following in your browser window: “You successfully uploaded !”

Automated testing

@AutoConfigureMockMvc
@SpringBootTest
public class FileUploadTests {

   @Autowired
   private MockMvc mvc;

   @MockBean
   private StorageService storageService;

   @Test
   public void shouldListAllFiles() throws Exception {
	given(this.storageService.loadAll())
			.willReturn(Stream.of(Paths.get("first.txt"), Paths.get("second.txt")));

	this.mvc.perform(get("/")).andExpect(status().isOk())
				.andExpect(model().attribute("files",
						Matchers.contains("http://localhost/files/first.txt",
								"http://localhost/files/second.txt")));
   }

   @Test
   public void shouldSaveUploadedFile() throws Exception {
	MockMultipartFile multipartFile = new MockMultipartFile("file", "test.txt",
				"text/plain", "Spring Framework".getBytes());
	this.mvc.perform(multipart("/").file(multipartFile))
				.andExpect(status().isFound())
				.andExpect(header().string("Location", "/"));

	then(this.storageService).should().store(multipartFile);
   }

   @SuppressWarnings("unchecked")
   @Test
   public void should404WhenMissingFile() throws Exception {
	given(this.storageService.loadAsResource("test.txt"))
			.willThrow(StorageFileNotFoundException.class);

	this.mvc.perform(get("/files/test.txt")).andExpect(status().isNotFound());
	}
}
  • In those tests, you use various mocks to set up the interactions with your controller and the StorageService but also with the Servlet container itself by using MockMultipartFile.
  • For an example of an integration test, see the FileUploadIntegrationTests class (which is in src/test/java/com/example/uploadingfiles).

18 - Testing the Web Layer

Introduction

  • This example walks you through the process of creating a Spring application and then testing it with JUnit and Spring MockMvc.
  • The example also demonstrates how to isolate the web layer and load a special application context.
  • Dependencies: Spring Web.

Home Controller

@Controller
public class HomeController {

	@RequestMapping("/")
	public @ResponseBody String greeting() {
		return "Hello, World";
	}
}

Test the application

  • Loading the home page at http://localhost:8080 will work, however, to give yourself more confidence that the application works when you make changes, you want to automate the testing.
  • For example, below is a simple sanity check test that will fail if the application context cannot start.
@SpringBootTest
public class TestingWebApplicationTests {

	@Test
	public void contextLoads() {
	}
}
  • The @SpringBootTest annotation tells Spring Boot to look for a main configuration class (one with @SpringBootApplication, for instance) and use that to start a Spring application context.
  • You can run this test in your IDE or on the command line (by running ./mvnw test or ./gradlew test), and it should pass.
  • To convince yourself that the context is creating your controller, you could add an assertion, as the following example
@SpringBootTest
public class SmokeTest {

	@Autowired
	private HomeController controller;

	@Test
	public void contextLoads() throws Exception {
		assertThat(controller).isNotNull();
	}
}
  • Spring interprets the @Autowired annotation, and the controller is injected before the test methods are run.
  • We use AssertJ (which provides assertThat() and other methods) to express the test assertions.
  • A nice feature of the Spring Test support is that the application context is cached between tests. That way, if you have multiple methods in a test case or multiple test cases with the same configuration, they incur the cost of starting the application only once.
  • It is nice to have a sanity check, but you should also write some tests that assert the behavior of your application. To do that, you could start the application and listen for a connection (as it would do in production) and then send an HTTP request and assert the response as the following example:
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {

	@Value(value="${local.server.port}")
	private int port;

	@Autowired
	private TestRestTemplate restTemplate;

	@Test
	public void greetingShouldReturnDefaultMessage() throws Exception {
		assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/",
				String.class)).contains("Hello, World");
	}
}
  • Note the use of webEnvironment=RANDOM_PORT to start the server with a random port (useful to avoid conflicts in test environments) and the injection of the port with @LocalServerPort.
  • Also, note that Spring Boot has automatically provided a TestRestTemplate for you. All you have to do is add @Autowired to it.
  • Another useful approach is to not start the server at all but to test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller.
    • That way, almost of the full stack is used, and your code will be called in exactly the same way as if it were processing a real HTTP request but without the cost of starting the server.
    • To do that, use Spring’s MockMvc and ask for that to be injected for you by using the @AutoConfigureMockMvc annotation on the test case as in the following example:
@SpringBootTest
@AutoConfigureMockMvc
public class TestingWebApplicationTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void shouldReturnDefaultMessage() throws Exception {
		this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
				.andExpect(content().string(containsString("Hello, World")));
	}
}
  • In this test, the full Spring application context is started but without the server.
  • We can narrow the tests to only the web layer by using @WebMvcTest, as the following
@WebMvcTest
public class WebLayerTest {

	@Autowired
	private MockMvc mockMvc;

	@Test
	public void shouldReturnDefaultMessage() throws Exception {
		this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
				.andExpect(content().string(containsString("Hello, World")));
	}
}
  • The test assertion is the same as in the previous case. However, in this test, Spring Boot instantiates only the web layer rather than the whole context.
  • In an application with multiple controllers, you can even ask for only one to be instantiated by using, for example, @WebMvcTest(HomeController.class).
  • So far, our HomeController is simple and has no dependencies. We could make it more realistic by introducing an extra component to store the greeting (perhaps in a new controller), as in the following example:
@Controller
public class GreetingController {

	private final GreetingService service;

	public GreetingController(GreetingService service) {
		this.service = service;
	}

	@RequestMapping("/greeting")
	public @ResponseBody  String greeting() {
		return service.greet();
	}
}
  • Then create a greeting service, as the following listing:
@Service
public class GreetingService {
	public String greet() {
		return "Hello, World";
	}
}
  • Spring automatically injects the service dependency into the controller (because of the constructor signature).

  • to test this controller with @WebMvcTest:

@WebMvcTest(GreetingController.class)
public class WebMockTest {

	@Autowired
	private MockMvc mockMvc;

	@MockBean
	private GreetingService service;

	@Test
	public void greetingShouldReturnMessageFromService() throws Exception {
		when(service.greet()).thenReturn("Hello, Mock");
		this.mockMvc.perform(get("/greeting")).andDo(print()).andExpect(status().isOk())
				.andExpect(content().string(containsString("Hello, Mock")));
	}
}
  • We use @MockBean to create and inject a mock for the GreetingService (if you do not do so, the application context cannot start), and we set its expectations using Mockito.

Part V - Spring Security

19 - Securing a web application

Introduction

  • This example walks you through the process of creating a simple web application with resources that are protected by Spring Security.
  • The example shows a Spring MVC application that secures the page with a login form.
  • Dependencies: Spring Web and Thymeleaf.

An Unsecured Web Application

  • Initially, the example demonstrates a web application includes two simple views: a home page and a “Hello, World” page.
  • The home page is defined in the following Thymeleaf template:

  • This simple view includes a link to the /hello page, which is defined in the following Thymeleaf template:

  • The web application is based on Spring MVC. As a result, you need to configure Spring MVC and set up view controllers to expose these templates as the following example:
@Configuration
public class MvcConfig implements WebMvcConfigurer {

   public void addViewControllers(ViewControllerRegistry registry) {
	registry.addViewController("/home").setViewName("home");
	registry.addViewController("/").setViewName("home");
	registry.addViewController("/hello").setViewName("hello");
	registry.addViewController("/login").setViewName("login");
   }
}
  • The addViewControllers() method (which overrides the method of the same name in WebMvcConfigurer) adds four view controllers.

Set up Spring Security

  • Suppose that you want to prevent unauthorized users from viewing the greeting page at /hello.
  • You need to add a barrier that forces the visitor to sign in before they can see that page.
    • You do that by configuring Spring Security in the application.
  • If Spring Security is on the classpath, Spring Boot automatically secures all HTTP endpoints with “basic” authentication.
    • However, you can further customize the security settings.
  • With Apache Maven, you need to add two extra entries (one for the application and one for testing):

  • The following security configuration ensures that only authenticated users can see the secret greeting:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

   @Bean
   public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	http
		.authorizeHttpRequests((requests) -> requests
			.requestMatchers("/", "/home").permitAll()
			.anyRequest().authenticated()
		)
		.formLogin((form) -> form
			.loginPage("/login")
			.permitAll()
		)
		.logout((logout) -> logout.permitAll());

	return http.build();
   }

   @Bean
   public UserDetailsService userDetailsService() {
	UserDetails user =
		User.withDefaultPasswordEncoder()
			.username("user")
			.password("password")
			.roles("USER")
			.build();

	return new InMemoryUserDetailsManager(user);
   }
}
  • The WebSecurityConfig class is annotated with @EnableWebSecurity to enable Spring Security’s web security support and provide the Spring MVC integration.
  • It also exposes two beans to set some specifics for the web security configuration:
    • The SecurityFilterChain bean defines which URL paths should be secured and which should not. Specifically, the / and /home paths are configured to not require any authentication. All other paths must be authenticated.
    • When a user successfully logs in, they are redirected to the previously requested page that required authentication. There is a custom /login page (which is specified by loginPage()), and everyone is allowed to view it.
    • The UserDetailsService bean sets up an in-memory user store with a single user. That user is given a user name of user, a password of password, and a role of USER.

Login page

  • The following Thymeleaf template presents a form that captures a username and password and posts them to /login:

  • If the user fails to authenticate, the page is redirected to /login?error, and your page displays the appropriate error message.

  • Upon successfully signing out, your application is sent to /login?logout, and your page displays the appropriate success message.
  • To display the current user name and sign out, update the hello.html to say hello to the current user and contain a Sign Out form:

  • We display the username by using Thymeleaf’s integration with Spring Security.
  • The “Sign Out” form submits a POST to /logout.
  • Upon successfully logging out, it redirects the user to /login?logout.

Run the application

  • Point your browser to http://localhost:8080. You should see the home page, as the following image shows:

  • When you click on the link, it attempts to take you to the greeting page at /hello. However, because that page is secured and you have not yet logged in, it takes you to the login page, as the following image shows:

  • At the login page, sign in as the test user by entering user and password for the username and password fields, respectively.

  • Once you submit the login form, you are authenticated and then taken to the greeting page, as the following image shows:

  • If you click on the Sign Out button, your authentication is revoked, and you are returned to the login page with a message indicating that you are logged out.

Part VI - Docker Containers

20 - Spring Boot with Docker

  • Back to Outline
  • Project source: spring-boot-docker
  • Refer to https://spring.io/guides/gs/spring-boot-docker/ if you are interested on more information about this example.
    • Important: The guide on this link mentions the installation of boot2docker if you are on a MacOS platform. This is not needed anymore (January, 2023).
    • Important: The guide uses on Dockerfile the entry FROM openjdk:8-jdk-alpine. We decided to use a newer version instead (FROM eclipse-temurin:17-jdk-focal).

Introduction

  • This example walks you through the process of building a Docker image and create a Docker container for running a Spring Boot application.
  • By default, Spring Boot applications run on port 8080 inside the container, and we mapped that to the same port on the host by using -p on the command line.
  • We start with a basic Dockerfile and make a few tweaks. Then we show a couple of options that use build plugins (for Maven and Gradle) instead of docker.
  • A Docker image is a recipe for running a containerized process.
  • Pre-requisite for this example:
  • Dependencies: Spring Web

Spring Boot Application

@SpringBootApplication
@RestController
public class Application {

  @RequestMapping("/")
  public String home() {
    return "Hello Docker World";
  }

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}
  • The Application class is flagged as a @SpringBootApplication and as a @RestController, meaning that it is ready for use by Spring MVC to handle web requests.
  • @RequestMapping maps / to the home() method, which sends a Hello World response.

Running the application without the Docker container (that is, in the host OS)

  • Go to localhost:8080 to see your “Hello Docker World” message.

Containerize the application

  • Docker has a simple "Dockerfile" file format that it uses to specify the “layers” of an image.
  • Example 1:
FROM eclipse-temurin:17-jdk-focal
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
  • If you use Apache Maven, you can run it with the following command:
docker build -t springio/gs-spring-boot-docker . 
  • This command builds an image and tags it as springio/gs-spring-boot-docker.

  • The build creates a spring user and a spring group to run the application.

  • It is then copied (by the COPY command) the project JAR file into the container as app.jar, which is run in the ENTRYPOINT.

  • The array form of the Dockerfile ENTRYPOINT is used so that there is no shell wrapping the Java process.

  • Running applications with user privileges helps to mitigate some risks.

    • So, an important improvement to the Dockerfile is to run the application as a non-root user.