By Subham Aggarwal | 5/30/2017 | General |Beginners

Configuring Spring Boot for Web

Configuring Spring Boot for Web

In the previous article, we learned how to create a basic app template with Spring Initializer, add some basic functionalities, and set up a connection to a database.

 

Now, we will continue to evolve our DiscoverStudent application and give it a web presence.

 

In this article, we will learn about the following concepts:

  • Creating a basic RESTful application
  • Creating a Spring Data REST service
  • Configuring custom interceptors
  • Configuring custom HttpMessageConverters
  • Configuring custom PropertyEditors

Adding web dependencies

First thing we need to do to make our Spring Boot application to be able to accessed via a web browser.

 

  • Here is a modified Gradle file:
dependencies {
 compile("org.springframework.boot:spring-boot-starter-data-jpa")
 compile("org.springframework.boot:spring-boot-starter-jdbc")
 compile("org.springframework.boot:spring-boot-starter-web")
 runtime("com.h2database:h2")
 testCompile("org.springframework.boot:spring-boot-
   starter-test")
}

Adding a REST controller

We will create a Spring REST controller that will handle the web requests for the student data for our application. Let's start by creating a new package structure to put our controllers into so that we have our code nicely grouped by their appropriate purposes.

 

Create a package folder called controllers in the src/main/java/com/discoversdk directory from the root of our project.

 

  • As we will be exposing the Student data, let's create the controller class file called StudentController in our newly created package with the following content:

 

@RestController
@RequestMapping("/students")
public class StudentController {


 @Autowired
 private StudentRepository repository;

 @RequestMapping(value = "", method = RequestMethod.GET)
 public Iterable<Student> getAllStudents() {
   return repository.findAll();
 }

 @RequestMapping(value = "/{name}", method = 
   RequestMethod.GET)
 public Student getStudent(@PathVariable String name) {
   return repository.findByName(name);
 }
}
  • Start the application by running

 

  • After the application has started, open the browser and go to http://localhost:8080/students and you should see a response: [] as no data is present yet in the database.

spring boot 2

@RestController annotation handles the request and expose it as a Web endpoint. In @RestController, two annotations are defined: @Controller and @ResponseBody. So we could just as easily annotate StudentController, as follows:

@Controller
@ResponseBody
@RequestMapping("/students")
public class StudentController {...}
  • @Controller is a Spring stereotype annotation that is similar to @Bean and @Repository and declares the annotated class as an MVC Controller.
  • @ResponseBody is a Spring MVC annotation indicating that responses from the web-request-mapped methods constitute the entire content of the HTTP response body payload, which is typical for RESTful applications.

Custom Interceptors

A simple way to wrap our web requests is by using Spring’s HandlerInterceptor.

 

With the use of a HandlerInterceptor, we can intercept a request at different stages like before a request gets to the controller, after the processing is done, before the view has been rendered, and at the very end, after the request has been fully completed.

Adding a LocaleChangeInterceptor

Adding an interceptor is not as straightforward as adding a Bean.

 

  • We actually need to do it via WebMvcConfigurer. To do this, add a new class as WebConfiguration.java as follows:
public class WebConfiguration extends WebMvcConfigurerAdapter {
   
   @Bean
   public LocaleChangeInterceptor localeChangeInterceptor() {
       return new LocaleChangeInterceptor();
   }
}
  • This will actually create the interceptor spring bean but will not add it to the request handling chain. For this to happen, we will need to override the addInterceptors method and add our interceptor to the provided registry:
@Override
public void addInterceptors(InterceptorRegistry registry) {
 registry.addInterceptor(localeChangeInterceptor());
}
  • Start the application by running ./gradlew clean bootRun.
  • In the browser, go to http://localhost:8080/students?locale=foo.
  • Now, if you look at the console logs, you will see a bunch of stack trace errors basically saying the following:
Caused by: java.lang.UnsupportedOperationException: Cannot change HTTP accept header - use a different locale resolution strategy

Configuring custom HttpMessageConverters

While writing our application, we defined many components like a controller, a repository but we did not write any code to convert our response to JSON.

 

Actually, Spring Boot automatically configured HttpMessageConverters to translate our entity beans objects into a JSON representation using Jackson library, writing the resulting JSON data to an HTTP response output stream. When multiple converters are available, the most applicable one gets selected based on the message object class and the requested content type.

 

Now, let’s add a custom HttpMessageConverters to our application.

 

  • Let's add ByteArrayHttpMessageConverter as @Bean to our WebConfiguration class in the following manner:
@Bean
public 
 ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {
   return new ByteArrayHttpMessageConverter();
}
  • If you want to have a bit more control, we can override the extend MessageConverters method in the following way:
@Override
public void 
 extendMessageConverters(List<HttpMessageConverter<?>> 
   converters) {
 converters.clear();
 converters.add(new ByteArrayHttpMessageConverter());
}

Declaring HttpMessageConverter as @Bean is the quickest and simplest way of adding a custom converter to an app. If Spring detects a bean of the HttpMessageConverter type, it will add it to the list automatically. If we did not have a WebConfiguration class that extends WebMvcConfigurerAdapter, it would have been the preferred approach.

 

If we need to do something even more drastic such as removing all the other converters from the list or clearing it of duplicate converters, this is where overriding extendMessageConverters comes into play. This method gets invoked after all the WebMvcConfigurers get called for configureMessageConverters and the list of converters is fully populated.

 

Of course, it is entirely possible that some other instance of WebMvcConfigurer could override the extendMessageConverters as well. However, the chances of this are very low so you have a high degree of having the desired impact.

Configuring custom PropertyEditors

When we declare a mapping method in a controller, Spring allows us to freely define the method signature with the exact object types that we require. The way in which this is achieved is via the use of the PropertyEditor implementations.

 

PropertyEditor is a default concept defined as part of the JDK and designed to allow the transformation of a textual value to a given type. It was initially intended to be used to build Java Swing/AWT GUI and later proved to be a good fit for Spring's need to convert web parameters to method argument types.

 

  1. First, we will need to remove the extendMessageConverters method from our WebConfiguration class as the converters.clear() call will break the rendering because we removed all of the supported type converters.
  2. Next, we will add the definition of an Teacher object and TeacherEditor class as well as a method, initBinder, to our StudentController where we will configure the TeacherEditor with the following content:
public class Teacher {
 
 private String name;

 public Teacher(String name) {
   this.name = name;
 }

 public String getTeacher() {
   return name;
 }
}

public class TeacherEditor extends PropertyEditorSupport {
 
 @Override
 public void setAsText(String text) throws IllegalArgumentException {
     if (StringUtils.hasText(text)) {
       setValue(new Teacher(text.trim()));
     }
     else {
       setValue(null);
     }
   }

 @Override
 public String getAsText() {
   Teacher teacher = (Teacher) getValue();
   if (teacher != null) {
     return teacher.getName();
   }
   else {
     return "";
   }
 }
}

@InitBinder
public void initBinder(WebDataBinder binder) {
 binder.registerCustomEditor(Teacher.class, new TeacherEditor());
}
  1. Our getTeacher method in the StudentController will also change in order to accept the Teacher object, in the following way:

 

@RequestMapping(value = "/{teacher}", method = RequestMethod.GET)
public Student get(@PathVariable Teacher teacher) {
 return repository.findByName(teacher.getName());
}
  1. Start the application by running ./gradlew clean bootRun.
  2. In the browser, go to http://localhost:8080/teacher/bae.
  3. While we will not observe any visible changes, the TeacherEditor is indeed at work, creating an instance of an Teacher class object from the {name} parameter.

 

Spring automatically configures a large number of default editors but for custom types, we have to manually instantiate new editors for every web request. This is done in the controller in a method that is annotated with @InitBinder. This annotation is scanned and all the detected methods should have a signature of accepting WebDataBinder as an argument.

 

Among other things, WebDataBinder provides us with an ability to register as many custom editors as we require for the controller methods to be bound properly.

 

Check back for the next article in our Spring Boot series.

By Subham Aggarwal | 5/30/2017 | General

{{CommentsModel.TotalCount}} Comments

Your Comment

{{CommentsModel.Message}}

Recent Stories

Top DiscoverSDK Experts

User photo
3355
Ashton Torrence
Web and Windows developer
GUI | Web and 11 more
View Profile
User photo
3220
Mendy Bennett
Experienced with Ad network & Ad servers.
Mobile | Ad Networks and 1 more
View Profile
User photo
3060
Karen Fitzgerald
7 years in Cross-Platform development.
Mobile | Cross Platform Frameworks
View Profile
Show All
X

Compare Products

Select up to three two products to compare by clicking on the compare icon () of each product.

{{compareToolModel.Error}}

Now comparing:

{{product.ProductName | createSubstring:25}} X
Compare Now