Book Image

Spring 5.0 Projects

By : Nilang Patel
Book Image

Spring 5.0 Projects

By: Nilang Patel

Overview of this book

Spring makes it easy to create RESTful applications, merge with social services, communicate with modern databases, secure your system, and make your code modular and easy to test. With the arrival of Spring Boot, developers can really focus on the code and deliver great value, with minimal contour. This book will show you how to build various projects in Spring 5.0, using its features and third party tools. We'll start by creating a web application using Spring MVC, Spring Data, the World Bank API for some statistics on different countries, and MySQL database. Moving ahead, you'll build a RESTful web services application using Spring WebFlux framework. You'll be then taken through creating a Spring Boot-based simple blog management system, which uses Elasticsearch as the data store. Then, you'll use Spring Security with the LDAP libraries for authenticating users and create a central authentication and authorization server using OAuth 2 protocol. Further, you'll understand how to create Spring Boot-based monolithic application using JHipster. Toward the end, we'll create an online book store with microservice architecture using Spring Cloud and Net?ix OSS components, and a task management system using Spring and Kotlin. By the end of the book, you'll be able to create coherent and ?exible real-time web applications using Spring Framework.
Table of Contents (13 chapters)
Title Page
About Packt
Contributors
Preface
Index

Defining the API controllers


So far, we have written code to interact with the DB. Next up is to work on the code for the controller. We will have both types of controller—one that returns the view name (Thymeleaf template in our case) with the data for the view populated in the model object, and the other that exposes the RESTful APIs. We will need to add the following dependency to pom.xml:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>

Note

Adding spring-webmvc to the dependency will automatically include spring-core, spring-beans, and spring-context dependencies. So we can remove them from the pom.xml.

Enabling Web MVC using @EnableWebMvc

To be able to make use of the Spring MVC features, we need to have one class that has been annotated with @Configuration, to be annotated with @EnableWebMvc. The @EnableWebMvc annotation, imports the Spring MVC configuration from the WebMvcConfigurationSupport class present in the Spring MVC framework. If we need to override any of the default imported configuration, we would have to implement the WebMvcConfigurer interface present in the Spring MVC framework and override the required methods.

We will create an AppConfiguration class with the following definition:

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "com.nilangpatel.worldgdp")
public class AppConfiguration implements WebMvcConfigurer{

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**").addResourceLocations("/static/");
  }
}

In the previous configuration, a few important things to note are as follows:

  • @EnableWebMvc: This imports the Spring MVC related configuration.
  • @ComponentScan: This is used for declaring the packages that have to be scanned for Spring components (which can be @Configuration, @Service, @Controller, @Component, and so on). If no package is defined, then it scans starting from the package where the class is defined.
  • WebMvcConfigurer: We are going to implement this interface to override some of the default Spring MVC configuration seen in the previous code.

Configuration to deploy to Tomcat without web.xml

As we will be deploying the application to Tomcat, we need to provide the servlet configuration to the application server. We will look at how to deploy to Tomcat in a separate section, but now we will look at the Java configuration, which is sufficient to deploy the application to Tomcat or any application server without the need for an additional web.xml. The Java class definition is given in the following:

public class WorldApplicationInitializer extends
  AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return null;
  }
  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class[] {AppConfiguration.class};
  }
  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }
}

The AbstractAnnotationConfigDispatcherServletInitializerabstract class is an implementation of the WebApplicationInitializerinterface that is used to register Spring's DispatcherServlet instance and uses the other @Configuration classes to configure the DispatcherServlet.

We just need to override the getRootConfigClasses(), getServletConfigClasses(), and getServletMappings() methods. The first two methods point to the configuration classes that need to load into the servlet context, and the last method is used to provide the servlet mapping for DispatcherServlet.

DispatcherServlet follows the front controller pattern, where there is a single servlet registered to handle all the web requests. This servlet uses the RequestHandlerMapping and invokes the corresponding implementation based on the URL mapped to the implementation.

We need to make a small update to the Maven WAR plugin so that it doesn't fail if there is no web.xml found. This can be done by updating the <plugins> tag in the pom.xml file, as shown in the following:

<build>
  <finalName>worldgdp</finalName>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-war-plugin</artifactId>
      <executions>
        <execution>
          <id>default-war</id>
          <phase>prepare-package</phase>
          <configuration>
            <failOnMissingWebXml>false</failOnMissingWebXml>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Now we are all set to implement our controllers. We will show you how to deploy to Tomcat once we have implemented all the RESTful API controllers.

Defining the RESTful API controller for country resource

Let's define the RESTful API controller for the country resource. The following is the template for the controller:

@RestController
@RequestMapping("/api/countries")
@Slf4j
public class CountryAPIController {

  @Autowired CountryDAO countryDao;
  @Autowired WorldBankApiClient worldBankApiClient;

  @GetMapping
  public ResponseEntity<?> getCountries(
    @RequestParam(name="search", required = false) String searchTerm,
    @RequestParam(name="continent", required = false) String continent,
    @RequestParam(name="region", required = false) String region,
    @RequestParam(name="pageNo", required = false) Integer pageNo
  ){
    //logic to fetch contries from CountryDAO
    return ResponseEntity.ok();
  }

  @PostMapping(value = "/{countryCode}", 
      consumes = {MediaType.APPLICATION_JSON_VALUE})
  public ResponseEntity<?> editCountry(
    @PathVariable String countryCode, @Valid @RequestBody Country country ){
    //logic to edit existing country 
    return ResponseEntity.ok();
  }

  @GetMapping("/{countryCode}/gdp")
  public ResponseEntity<?> getGDP(@PathVariable String countryCode){
    //logic to get GDP by using external client
    return ResponseEntity.ok();
  }

}

The following are a few things to note from the previous code:

  • @RestController: This is used to annotate a class as a controller with each of the RESTful methods returning the data in the response body. 
  • @RequestMapping: This is for assigning the root URL for accessing the resources. 
  • @GetMapping and @PostMapping: These are used to assign the HTTP verbs that will be used to invoke the resources. The URL for the resources are passed within the annotation, along with other request headers that consume and produce information. 

Let's implement each of the methods in order, starting with getCountries(), as shown in the following code:

@GetMapping
public ResponseEntity<?> getCountries(
  @RequestParam(name="search", required = false) String searchTerm,
  @RequestParam(name="continent", required = false) String continent,
  @RequestParam(name="region", required = false) String region,
  @RequestParam(name="pageNo", required = false) Integer pageNo
){
  try {
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("search", searchTerm);
    params.put("continent", continent);
    params.put("region", region);
    if ( pageNo != null ) {
      params.put("pageNo", pageNo.toString());
    }

    List<Country> countries = countryDao.getCountries(params);
    Map<String, Object> response = new HashMap<String, Object>();
    response.put("list", countries);
    response.put("count", countryDao.getCountriesCount(params));
    return ResponseEntity.ok(response);
  }catch(Exception ex) {
    log.error("Error while getting countries", ex);
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
          .body("Error while getting countries");
  }
}

The following are some of the things to note from the previous code:

  • @RequestParam: This annotation is used to declare request parameters accepted by the controller endpoint. The parameters can be provided with a default value and can also be made mandatory. 
  • ResponseEntity: This class is used to return the response body, along with other response parameters such as status, headers, and so on.

Next up is the API for editing country details, as follows:

@PostMapping("/{countryCode}")
public ResponseEntity<?> editCountry(
  @PathVariable String countryCode, @Valid @RequestBody Country country ){
  try {
    countryDao.editCountryDetail(countryCode, country);
    Country countryFromDb = countryDao.getCountryDetail(countryCode);
    return ResponseEntity.ok(countryFromDb);
  }catch(Exception ex) {
    log.error("Error while editing the country: {} with data: {}", 
       countryCode, country, ex);
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
        .body("Error while editing the country");
  }
}

The following are a few things to note from the previous code implementation:

  • @PathVariable: This is used to declare any variable that needs to be part of the URL path of the controller endpoint. In our case, we want the country code to be part of the URL. So the URL will be of the /api/countries/IND form.
  • @Valid: This triggers the Bean Validation API to check for the restrictions on each of the class properties. If the data from the client is not valid, it returns a 400.
  • @RequestBody: This is used to capture the data sent in the request body and the Jackson library is used to convert the JSON data in the request body to the corresponding Java object.

The rest of the API implementation can be found in the CountryAPIController class. The tests for the API controller can be found in the CountryAPIControllerTestclass, which is available in the source code of this book.

Defining the RESTful API controller for city resource

For the city resource we would need the following APIs:

  • Get cities for a given country
  • Add a new city to the country
  • Delete the city from the country

The code for this controller can be found in the CityAPIController class and the tests for the API controller can be found in the CityAPIControllerTest class, which is available in the source code of this book.

Defining the RESTful API controller for country language resource

For the CountryLanguage resource we need the following APIs:

  • Get languages for a country
  • Add a language for a country
  • Delete a language from the country

The code for this controller can be found in the CountryLanguageAPIController class and the tests for the API controller can be found in the CountryLanguageAPIControllerTest class, which is available in the source code of this book.