Advanced technologies for Spring MVC

Alternative to Spring MVC configuration

Extension AbstractAnnotationConfigDispatcherServlet -, Initializer quick set up Spring MVC environment. In this handy base class, assume that we need the basic DispatcherServlet and ContextLoaderListener environment, and that the Spring configuration is Java, not XML.

We may need additional servlets and filters in addition to dispatcherservlets; We may also need to do some additional configuration on the DispatcherServlet itself; Alternatively, if we need to deploy the application to a pre-Servlet 3.0 container, we also need to configure the DispatcherServlet to traditional web.xml.

Customize DispatcherServlet configuration

Although the appearance of the following procedures will not be able to tell, but the Abstract – AnnotationConfigDispatcherServletInitializer thing has done more than it looks. The three methods we wrote in SpittrWebAppInitializer are simply the Abstract methods that must be overridden. But there are actually more methods that can be overridden for additional configuration.

One such approach is customizeRegistration(). In AbstractAnnotation – ConfigDispatcherServletInitializer DispatcherServlet to registration to the Servlet container, is called customizeRegistration (), And pass in the registration.dynamic result of the Servlet’s Registration. Additional configuration of the DispatcherServlet can be done by overloading the customizeRegistration() method.

For example, we’ll see how multipart requests and file uploads are handled in Spring MVC. If you plan to use Servlet 3.0 support for multipart configuration, you will need to enable multipart requests with registration of DispatcherServlet. We can override the customizeRegistration() method to set MultipartConfigElement, as follows:

@Override
prtected void customizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
}
Copy the code

Using customizeRegistration ServletRegistration in () method. The Dynamic, we are able to complete various tasks, including by calling setLoadOnStartup priority set the load – on – startup (), Set the initialization parameters with setInitParameter() and configure Servlet 3.0 support for Multipart with a call to setMultipartConfig(). In the previous example, we set up support for multipart by setting the temporary storage directory for uploaded files to “/ TMP /spittr/uploads”.

Add additional servlets and filters

One of the benefits of Java-based initializers is that we can define any number of initializer classes. Therefore, if you want to register other components in the Web container, you simply create a new initializer. The easiest way is to realize the Spring WebApplicationInitializer interface.

The following program shows how to implement and register a Servlet creates WebApplicationInitializer:

package com.myapp.config;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynameic;
import org.springframework.web.WebApplicationInitializer;
import com.myapp.MyServlet;

public class MyServletInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
        myServlet.addMapping("/custom/**"); }}Copy the code

The above program is a fairly basic Servlet registration initializer class. It registers a Servlet and maps it to a path. We can also register the DispatcherServlet manually in this way. (but it is not necessary, because AbstractAnnotationConfigDispatcherServletInitializer too much useless code will complete the task well.)

You can also create new WebApplicationInitializer implementation to register the Listener and the Filter:

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
    javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class);
    filter.addMappingForUrlPatterns(null.false."/custom/*");
}
Copy the code

If you want to deploy applications to support the Servlet 3.0 container, then WebApplicationInitializer provides a general way, implement registered in Java Servlet, Filter, and the Listener. However, if you just registered the Filter and the Filter will only be mapped to the DispatcherServlet, so there is a shortcut in the AbstractAnnotationConfigDispatcherServletInitializer.

In order to register the Filter and map it to the DispatcherServlet, need to do is just overloaded AbstractAnnotationConfigDispatcherServletInitializer getServlet – Filters () method. For example, in the following code, overloading the AbstractAnnotationConfig – DispatcherServletInitializer getServletFilters register () method to Filter:

@Override
protected Filter[] getServletFilters() {
    return new Filter[] {
        newMyFilter(); }}Copy the code

As you can see, this method returns an array of Javax.servlet. Filter. Here it returns only one Filter, but it can actually return any number of filters. There is no need to declare its mapping path here; all filters returned by the getServletFilters() method are mapped to the DispatcherServlet.

If you want to deploy your application into a Servlet 3.0 container, Spring provides a variety of ways to register servlets (including DispatcherServlets), filters, and listeners without creating a web.xml file. However, if you don’t want to do the above, you can. Assuming you need to deploy your application to a container that doesn’t support Servlet 3.0 (or you just want to use a web.xml file), you can configure Spring MVC through Web.xml the old-fashioned way.

Declare DispatcherServlet in web.xml

In a typical Spring MVC application, a DispatcherServlet and context-Loader Listener are required. AbstractAnnotationConfigDispatcherServletInitializer will automatically register them, but if you need on the web. Registered in XML, the need for him to finish the task.

Here is a basic web. XML file that sets up the DispatcherServlet and ContextLoaderListener in the traditional way:

<? xml version="1.0" encoding="UTF-8"? > <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup< / a > 1load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
Copy the code

As mentioned earlier, the ContextLoaderListener and DispatcherServlet each load a Spring application context. The contextConfigLocation parameter specifies the address of an XML file that defines the root application context, which is loaded by ContextLoaderListener. As shown in the above program, the root context loads the bean definition from/web-INF /spring/root-context.xml.

The DispatcherServlet finds a file based on the Servlet name and loads the application context based on that file. In the above application, the Servlet name is appServlet, so DispatcherServlet loads its application context from the “/ web-INF/appservlet-context.xml” file.

If you want to specify the location of the DispatcherServlet configuration file, you can specify a contextConfigLocation initialization parameter on the Servlet. For example, the following configuration, the DispatcherServlet will from “/ WEB – INF/spring/appServlet/servlet – context. XML” bean load it:

<servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup< / a > 1load-on-startup>
    </servlet>
Copy the code

Of course, this is all about getting the DispatcherServlet and ContextLoaderListener to load their respective application context from the XML. For the most part, however, we prefer to use Java configuration over XML configuration. Therefore, we need to have Spring MVC start up with the Configuration loaded from the @Configuration annotated class.

To be used in Spring MVC based on Java configuration, need to tell the DispatcherServlet and use ContextLoaderListener AnnotationConfigWebApplicationContext, This is an implementation class for WebApplicationContext that loads Java configuration classes instead of using XML. To achieve this configuration, set the contextClass context parameter and the initialization parameter of the DispatcherServlet. The following listing shows a new web.xml file in which the Spring MVC it builds uses a Java-based Spring configuration:

<? xml version="1.0" encoding="UTF-8"? > <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.habuma.spitter.config.RootConfig</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.habuma.spitter.config.WebConfig</param-value>
        </init-param>
        <load-on-startup< / a > 1load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
Copy the code

Process multipart data

The Spittr application requires file uploads in two places. When new users sign up for the app, they want to be able to upload a picture associated with their personal information. When users submit a new Spittle, they may upload a photo in addition to a text message.

The result of a typical form submission request is simple: multiple name-value pairs separated by ampersand. For example, when submitting the registration form in the Spittr application, the request would look like this:

firstName=Charles&lastName=Xavier&[email protected]&username=professor&password=letmein01
Copy the code

While this form of encoding is simple and adequate for typical text-based form submission, it is inadequate for transferring binary data, such as uploading images. In contrast, multipart data splits a form into parts, each of which corresponds to an input field. In a normal form input field, it would place text data in the corresponding part, but if a file is uploaded, the corresponding part could be binary. Here is the body of the multipart request:

------WebKitFormBoundaryqgkabn8IHJCuNmiW
Content-Disposition: form-data; name="firstName"

Charles
------WebKitFormBoundaryqgkabn8IHJCuNmiW
Content-Disposition: form-data; name="lastName"

Xavier
------WebKitFormBoundaryqgkabn8IHJCuNmiW
Content-Disposition: form-data; name="email"

charles@xmen.com
------WebKitFormBoundaryqgkabn8IHJCuNmiW
Content-Disposition: form-data; name="username"

professorx
------WebKitFormBoundaryqgkabn8IHJCuNmiW
Content-Disposition: form-data; name="password"

letmein01
------WebKitFormBoundaryqgkabn8IHJCuNmiW
Content-Disposition: form-data; name="profilePicture"; filename="me.jpg"
Content-Type: image/jpeg

	[[ Binary image data goes here]]
------WebKitFormBoundaryqgkabn8IHJCuNmiW--

Copy the code

In the multipart request, you can see that the profilePicture section is significantly different from the rest. Among other things, it has its own Content-Type header, indicating that it is a JPEG image. Although not necessarily obvious, the request body of the profilePicture section is binary data, not simple text.

Configure the multipart parser

The DispatcherServlet does not implement any functionality to parse multipart request data. It delegates this task to Spring’s implementation of the MultiPart Resolver policy interface, which parses the contents of the Multipart request. Starting with Spring 3.1, Spring has two built-in implementations of MultipartResolver to choose from:

  • CommonsMultipartResolver: Resolve multipart requests using Jakarta Commons FileUpload
  • StandardServletMultipartResolver: rely on Servlet 3.0 support for multipart request (started in Spring 3.1)

Generally speaking, between the two, StandardServletMultipartResolver could be optimized scheme. It uses the functionality provided by servlets and does not rely on any other project. If you need to deploy your application to a container prior to Servlet 3.0, or if you are not using Spring 3.1 or later, you may need CommonsMultipartResolver.

Parse multipart requests using Servlet 3.0

Compatible with the Servlet 3.0 StandardServletMultipartResolver no constructor parameters, also do not have to set properties. Thus, declaring it as a bean in the context of a Spring application is very simple, as follows:

@Bean
public MultipartResolver multipartResolver(a) throws IOException {
    return new StandardServletMultipartResolver();
}
Copy the code

Configuration StandardServletMultipartResolver constraints. Just not in the Spring configuration StandardServletMultipartResolver, but to specify the multipart configuration in the Servlet. At a minimum, you must specify the path to the temporary file to be written during file upload. Standardservlet-multipart Resolver will not work without this most basic configuration. Specifically, the details of multipart must be included as part of the DispatcherServlet configuration in either web.xml or Servlet initialization classes.

If with the method of the Servlet initialization class to configure the DispatcherServlet, the initialization of classes should be achieved WebApplicationInitializer, We can call setMultipartConfig() on Servlet registration and pass in an instance of Multipartconfig-Element. Here is the basic DispatcherServlet Multipart configuration, which sets the temporary path to ‘/ TMP /spittr/uploads’ :

DispatcherServlet ds = new DispatcherServlet();
Dynamic registration = context.addServlet("appServlet", ds);
registration.addMapping("/");
registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/up; oads"));
Copy the code

If the Servlet initialization class that configures DispatcherServlet inherits Abstract AnnotationConfigDispatcherServletInitializer or AbstractDispatcher – ServletInitializer, You will not create a DispatcherServlet instance directly and register it in the Servlet context. In this case, there will be no references to Dynamic Servlet registration to use. However, you can configure the details of multipart by overloading the customizeRegistration() method, which takes Dynamic as an argument:

@Override
protected void custonizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));
}
Copy the code

So far, you’ve used the MultipartConfigElement constructor with only one parameter, which specifies an absolute directory in the file system to which the uploaded file will be temporarily written. However, there are other constructors available to limit the size of uploaded files. In addition to the location of the temporary path, the constructor accepts the following parameters:

  • The maximum capacity (in bytes) of uploaded files. The default is unlimited
  • The maximum capacity (in bytes) of the entire multipart request, regardless of how many parts there are and the size of each part. The default is unlimited
  • During the upload process, if the file size reaches a specified maximum (in bytes), it will be written to the temporary file path. The default value is 0, which means that all uploaded files are written to disk

Imagine limiting the file size to no more than 2MB, the entire request to no more than 4MB, and all files to disk. The following code sets these threshold values using MultipartConfigElement:

@Override
protect void customizeRegistration(Dynamic registration) {
    registration.setMultipartConfig(new MultipartConfigElement("tmp/spittr/uploads".2097152.4194304.0));
}
Copy the code

If you configure MultipartConfigElement using the more traditional web.xml, you can use the elements in, as follows:

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup< / a > 1load-on-startup>
    <multipart-config>
        <location>/tmp/spittr/uploads</location>
        <max-file-size> 2097152 < /max-file-size>
        <max-request-size> 4194304 < /max-request-size>
    </multipart-config>
</servlet>
Copy the code

The default value is the same as MultipartConfigElement. As with MultipartConfigElement, you must configure.

Configure the Jakarta Commons FileUpload Multipart parser

Generally speaking, StandardServletMultipartResolver would be the best choice, but if we need to deploy applications to the Servlet 3.0 container, then need alternative solutions. We can write our own MultipartResolver implementation if we like. However, this is not necessary unless you want to perform specific logic when handling multipart requests. Spring built-in CommonsMultipartResolver, can be used as a StandardServletMultipartResolver alternative.

The easiest way to declare CommonsMultipartResolver as a Spring bean is:

@Bean
public MultipartResolver multipartResolver(a) {
    return new CommonsMultipartResolver();
}
Copy the code

Unlike StandardServletMultipartResolver, CommonsMultipart – Resolver not forced to set up temporary file path. By default, this path is the temporary directory for the Servlet container. However, by setting the uploadTempDir property, we can specify a different location:

@Bean
public MultipartResolver multipartResolver(a) throws IOException {
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    multipartResolver.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads"));
    return multipartResolver;
}
Copy the code

In fact, we can specify other multipart upload details in the same way by setting the CommonsMultipartResolver property. For example, the following configuration is equivalent to the us in the above configuration by MultipartConfigElement StandardServletMultipartResolver:

@Bean
public MultipartResolver multipartResolver(a) throws IOException {
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    multipartResolver.setUploadTempDir(new FileSystemResource("/tmp/spittr/uploads"));
    multipartResolver.setMaxUploadSize(2097152);
    multipartResolver.setMaxInMemorySize(0);
    return multipartResolver;
}
Copy the code

Here, set the maximum file size to 2MB and the maximum memory size to 0 bytes. These two properties correspond directly to the second and fourth constructor parameters of MultipartConfigElement, indicating that files larger than 2MB cannot be uploaded and that all files will be written to disk regardless of their size. But unlike MultipartConfigElement, there is no way to set the maximum size of the entire multipart request.

Handle multipart requests

Assuming that the user is allowed to upload an image when registering the Spittr application, we need to modify the form to allow the user to select the image to upload, and we also need to modify the processRegistration() method in the SpitterController to accept the uploaded image. The following code snippet from the Thymeleaf registrationForm view (registrationform.html) highlights the changes required for the form:

<form method="POST" th:object="${spitter}" enctype="multipart/form-data">... <label>Profile Picture</label> <input type="file" name="profilePicture" accept="image/jpeg,image/png,image/gif" /><br/>
...
</form>
Copy the code

In addition to the input field already in the registration form, we have added a new input field with type File. This allows the user to select the image file to upload. The Accept attribute is used to limit file types to JPEG, PNG, and GIF images. According to its name attribute, the image data will be sent to the profilePicture Part in the Multipart request.

Now you need to modify the processRegistration() method to accept uploaded images. One way to do this is to add a byte array parameter and annotate it with @requestPart. Here is an example:

@RequestMapping(value="/register", method=POST)
  public String processRegistration(@RequestPart("profilePicture") byte[] profilePicture, @Valid Spitter spitter, Errors errors) {... }Copy the code

When the registration form is submitted, the profilePicture property will be given a byte array that contains the data for the part in the request (specified by @requestPart). If the user submits the form without selecting a file, the array is empty (instead of null). Once the image data is retrieved, all that remains for the processRegistration() method is to save the file somewhere.

Accept MultipartFile

Using the raw byte of the uploaded file is relatively simple but has limited functionality. Therefore, Spring also provides the MultipartFile interface, which provides richer objects for processing multipart data. The following program shows an overview of the MultipartFile interface.

package org.springframework.web.multipart;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

public interface MultipartFile {

   String getName(a);

   String getOriginalFilename(a);

   String getContentType(a);

   boolean isEmpty(a);

   long getSize(a);

   byte[] getBytes() throws IOException;

   InputStream getInputStream(a) throws IOException;

   void transferTo(File dest) throws IOException, IllegalStateException;

}
Copy the code

As you can see, MultipartFile provides a way to get the byte of the uploaded file, but it does more than that. You can also get the original filename, size, and content type. It also provides an InputStream for reading file data as a stream.

In addition, MultipartFile provides a convenient transferTo() method that helps write uploaded files to the file system. As an example, you can add the following lines to the process-registration () method to write an uploaded image file to the file system:

profilePicture.transferTo(new File("/data/spittr/" + profilePicture.getOriginalFilename()));
Copy the code

Saving the files to the local file system is straightforward, but it requires us to manage the files. We need to make sure that we have enough space, that the files are backed up in the event of a hardware failure, and that we need to handle the synchronization of these image files across multiple servers in the cluster.

Save the file to Amazon S3

Another option is to let someone else take care of it. With a few more lines of code, we can save the image to the cloud. For example, the saveImage() method shown in the program listing below saves the uploaded file to Amazon S3 and can be called in processRegistration().

private void saveImage(MultipartFile image) throws ImageUploadException {
    try {
        AWSCredentials awsCredentials = new AWSCredentials(s3AccessKey, s2SecretKey);
        S3Service s3 = new RestS3Service(awsCredentials);
        S3Bucket bucket = s3.getBucket("spittrImages");
        S3Object imageObject = new S3Object(image.getOriginalFilename());
        imageObject.setDataInputStream(image.getInputStream());
        imageObject.setContentLength(image.getSize());
        imageObject.setContentType(image.getContentType());
        
        AccessControlList acl = new AccessControlList();
        acl.setOwner(bucket.getOwner());
        acl.grantPermission(GroupGrantee.ALL_USERS, Permission.PERMISSION_READ);
        imageObject.setAcl(acl);
        
        s3.putObject(bucket, imageObject);
    } catch (Exception e) {
        throw new ImageUploadException("Unable to save image", e); }}Copy the code

The first thing the saveImage() method does is build Amazon Web Service (AWS) credentials. To do this, you need to have an S3 Access Key and an S3 Secret Access Key. When you sign up for S3, Amazon will provide it to you. They are supplied to the Spitter-controller via value injection.

With the AWS credentials ready, the saveImage() method creates a RestS3Service instance of JetS3t that you can use to manipulate the S3 file system. It gets a reference to the spitterImages bucket and creates an S3Object to contain the image, and then populates the image data into the S3Object.

Before calling putObject() to write the image data to S3, the saveImage() method sets the S3Object permissions, allowing all users to view it. This is important — without it, these images would not be visible to the users of our application. Finally, if any problems occur, an ImageUploadException will be thrown.

Accepts uploaded files as Part

If you need to deploy your application into a Servlet 3.0 container, there is an alternative to MultipartFile. Spring MVC also accepts javax.servlet.http.Part as a parameter to controller methods. If Part is used instead of MultipartFile, the method signature for processRegistration() will look like this:

@RequestMapping(value="/register", method=POST)
public String processRegistration(@RequestPart("profilePicture") Part profilePicture, @Valid Spitter spitter, Errors errors) {... }Copy the code

The Part interface is not that different from MultipartFile in terms of the subject (no kidding). In the following program, we can see that some methods of the Part interface correspond to MultipartFile.

package javax.servlet.http;

import java.io.*;
import java.util.*;

public interface Part {

    public InputStream getInputStream(a) throws IOException;

    public String getContentType(a);

    public String getName(a);

    public String getSubmittedFileName(a);

    public long getSize(a);

    public void write(String fileName) throws IOException;

    public void delete(a) throws IOException;

    public String getHeader(String name);

    public Collection<String> getHeaders(String name);

    public Collection<String> getHeaderNames(a);

}
Copy the code

In many cases, the name of the Part method is exactly the same as that of the MultipartFile method. Some are similar but slightly different, such as getSubmittedFileName() corresponds to getOriginalFilename(). Similarly, write() corresponds to transferTo(), which writes the uploaded file to the file system:

profilePicture.write("/data/spittr/" + profilePicture.getOriginalFilename());
Copy the code

It is worth mentioning that there is no need to configure MultipartResolver if the controller methods are written to accept file uploads as Part arguments. We only need a MultipartResolver if we use a MultipartFile.

Handle exceptions

No matter what happens, good or bad, the output of a Servlet request is a Servlet response. If an exception occurs while the request is being processed, its output will still be a Servlet response. Exceptions must somehow be translated into responses.

Spring provides several ways to turn exceptions into responses:

  • Specific Spring exceptions are automatically mapped to the specified HTTP status code
  • The @responseStatus annotation can be added to the exception to map it to an HTTP status code
  • The @ExceptionHandler annotation can be added to the method to handle exceptions

The easiest way to handle an exception is to map it to an HTTP status code and place it in the response. Next, take a look at how exceptions can be mapped to an HTTP status code.

Map exceptions to HTTP status codes

By default, Spring automatically converts some of its exceptions to appropriate status codes.

Spring abnormal The HTTP status code
BindException 400 – Bad Request
ConversionNotSupportedException 500 – Internal Server Error
HttpMediaTypeNotAcceptableException 406 – Not Acceptable
HttpMediaTypeNotSupportedException 415 – Unsupported Media Type
HttpMessageNotReadableException 400 – Bad Request
HttpMessageNotWritableException 500 – Internal Server Error
HttpRequestMethodNotSupportedException 405 – Method Not Allowed
MethodArgumentNotValidException 400 – Bad Request
MissingServletRequestParameterException 400 – Bad Request
MissingServletRequestPartException 400 – Bad Request
NoSuchRequestHandlingMethodException 404 – Not Found
TypeMismatchException 400 – Bad Request

The exceptions in Table 7 are typically thrown by Spring itself as a result of problems during DispatcherServlet processing or during validation. For example, if the DispatcherServlet unable to find a suitable for processing the request method of controller, so will be thrown NoSuchRequestHandlingMethodException abnormalities, the end result is the response of the 404 status code (Not Found).

While these built-in mappings are useful, they are useless for exceptions thrown by the application. Fortunately, Spring provides a mechanism to map exceptions to HTTP status codes via the @responseStatus annotation.

To illustrate this functionality, refer to the following request handling method in SpittleController, which may produce HTTP 404 status (but is not yet implemented) :

@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(
    @PathVariable("spittleId") long spittleId, 
    Model model) {
  Spittle spittle = spittleRepository.findOne(spittleId);
  if (spittle == null) {
    throw new SpittleNotFoundException();
  }
  model.addAttribute(spittle);
  return "spittle";
}
Copy the code

In this case, the Spittle object is retrieved by ID from the SpittleRepository. If the findOne() method returns a Spittle object, Spittle is placed in the model, and the view named Spittle takes care of rendering it into the response. But if the findOne() method returns NULL, a SpittleNotFoundException will be thrown. Now SpittleNotFoundException is a simple non-checking exception, as follows:

package spittr.web;

public class SpittleNotFoundException extends RuntimeException {}Copy the code

If the spittle() method is called to process the request and the result obtained for the given ID is empty, SpittleNotFoundException (the default) will result in a 500 status code (Internal Server Error) response. In fact, if any exception occurs that is not mapped, the response will have a 500 status code, but this default behavior can be changed by mapping SpittleNotFoundException.

When a SpittleNotFoundException is thrown, this is a scenario where the requested resource was not found. HTTP status code 404 is the most accurate response status code if the resource is not found. So, we’ll use the @responseStatus annotation to map SpittleNotFoundException to HTTP status code 404.

package spittr.web;

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

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found")
public class SpittleNotFoundException extends RuntimeException {}Copy the code

After the @responseStatus annotation is introduced, if the controller method throws SpittleNotFound-Exception, the response will have a 404 status code because SpittleNotFound.

Write methods to handle exceptions

As an example, the DuplicateSpittle Exception will be thrown by the save() method of SpittleRepository, assuming that the Spittle text that the user is trying to create is exactly the same as the Spittle text that was created. This means that SpittleController’s saveSpittle() method may need to handle this exception. The saveSpittle() method handles this exception directly, as shown in the following program.

@RequestMapping(method=RequestMethod.POST)
public String saveSpittle(SpittleForm form, Model model) {
  try {
    spittleRepository.save(new Spittle(null, form.getMessage(), new Date(), 
        form.getLongitude(), form.getLatitude()));
    return "redirect:/spittles";
  } catch (DuplicateSpittleException e) {
    return "error/duplicate"; }}Copy the code

It works fine, but the method is a bit complicated. The method can have two paths, each with a different output. The saveSpittle() method can be simpler if it focuses only on the correct path and lets other methods handle exceptions.

@RequestMapping(method=RequestMethod.POST)
public String saveSpittle(SpittleForm form, Model model) {
    spittleRepository.save(new Spittle(null, form.getMessage(), new Date(), 
        form.getLongitude(), form.getLatitude()));
    return "redirect:/spittles";
}
Copy the code

As you can see, the saveSpittle() method is much simpler. Because it only focuses on the successful saving of Spittle, there is only one execution path, which is easy to understand (and test).

Now, add a new method for SpittleController, it will be thrown DuplicateSpittleException:

@ExceptionHandler(DuplicateSpittleException.class)
public String handleNotFound(a) {
  return "error/duplicate";
}
Copy the code

HandleDuplicateSpittle @ ExceptionHandler annotations, is added to the () method when throw DuplicateSpittleException anomaly will be entrusted to handle the method. It returns a String, which is consistent with the way requests are handled, specifying the logical view name to render, which tells the user that they are trying to create a duplicate entry.

The interesting thing about the method annotated by the @ExceptionHandler annotation is that it can handle exceptions thrown by all the handler methods in the same controller. So, although we from saveSpittle () extracted code creates handleDuplicateSpittle () method, but it can deal with all the methods in SpittleController DuplicateSpittleException exception thrown by the. We don’t have to thrown in every possible DuplicateSpittleException add exception handling code method, this method will cover all the functions.

Since the method annotated by the @ExceptionHandler annotation can handle exceptions from all handler methods in the same controller class, you might wonder if there is a way to handle exceptions thrown by handler methods in all controllers. Starting with Spring 3.2, this is definitely possible, and we just need to define it in the controller notification class.

Add notifications for controllers

It would be much easier if a specific aspect of the controller class could be applied to all controllers throughout the application. For example, the method annotated by the @ExceptionHandler annotation is useful if you want to handle exceptions in multiple controllers. However, if a particular exception is thrown in multiple controller classes, you may find yourself repeating the same @ExceptionHandler method in all controller methods. Or, to avoid duplication, we create a base controller class that all controller classes extend to inherit the common @ExceptionHandler method.

Spring 3.2 introduces a new solution to this problem: controller notifications. ControllerAdvice is any class annotated @controllerAdvice. This class contains one or more methods of the following types:

  • @ExceptionHandler Method for annotation annotation
  • InitBinder Annotation method
  • @modelAttribute Annotation method

In a class annotated with @ControllerAdvice, the methods described above will apply to methods annotated with @RequestMapping on all controllers throughout the application.

The @ControllerAdvice annotation already uses @Component, so classes annotated by the @ControllerAdvice annotation will automatically be scanned by the Component, just like classes annotated by the @Component annotation.

One of the most useful scenarios for @ControllerAdvice is to collect all @ExceptionHandler methods into a single class so that all controller exceptions are handled consistently in one place. For example, we want the DuplicateSpittleException treatment using all the controller on the entire application. The following program shows AppWideExceptionHandler, a class annotated with @ControllerAdvice, to do this.

package spittr.web;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class AppWideExceptionHandler {

  @ExceptionHandler(DuplicateSpittleException.class)
  public String duplicateSpittleHandler(a) {
    return "error/duplicate"; }}Copy the code

Now, if any controller method throws DuplicateSpittleException, regardless of the method in which the controller that will be called the duplicateSpittleHandler () method to handle the exception. We can write the @ExceptionHandler annotation method the same way we wrote the @requestMapping annotation method. As shown above, it returns “error/ Duplicate” as the logical view name, so a user-friendly error page will be displayed.

Passing data across redirected requests

After a POST request is processed, it is generally a best practice to perform a redirection. Among other things, this prevents the client from re-executing a dangerous POST request when the user hits the browser’s refresh button or back arrow.

In the view name returned by the controller method, we leverage the power of the “redirect:” prefix. When the controller method returns a String that starts with “redirect:”, the String is not used to find the view, but to direct the browser to the redirect path.

return "redirect:/spitter/" + spitter.getUsername();
Copy the code

The “redirect:” prefix makes redirection very simple. You might think Spring would have a hard time making redirection any easier. But wait a minute: The Spring redirection feature also provides some other ancillary features.

In general, when a processor method is complete, the model data specified by the method is copied to the request as properties in the request, and the request is forward to the view for rendering. Because controller methods and views handle the same request, request attributes can be preserved during forwarding.

When the controller results in a redirect, the original request ends and a new GET request is issued. The model data contained in the original request dies with the request. In the new request attribute, there is no model data, and the request must calculate the data itself.

Obviously, for redirection, the model can’t be used to pass data. But we also have some other schemes that can pass data from the originating redirection method to the processing redirection method:

  • Use URL templates to pass data in the form of path variables and/or query parameters
  • Send data through flash properties

Redirect using a URL template

Passing data through path variables and query parameters seems simple enough. For example, we passed the username that created the Spitter as a path variable. But the way it’s written now, the value of username is attached directly to the redirected String. This works fine, but it is far from problem-free. Using String concatenation can be dangerous when building URLS or SQL queries.

return "redirect:/spitter/{username}";
Copy the code

In addition to concatenating strings to build redirect urls, Spring also provides a way to define redirect urls using templates. For example, the last line of the processRegistration() method could be rewritten as follows:

@RequestMapping(value="/register", method=POST)
public String processRegistration(Spitter spitter, Model model) {
    spitterRepository.save(spitter);
    model.addAttribute("username", spitter.getUsername());
    return "redirect:/spitter/{username}";
}
Copy the code

The redirection String returned has not changed much. However, because the spitterId attribute in the model does not match any placeholders in the redirected URL, it is automatically attached to the redirected URL as a query parameter.

If the username attribute is habuma and the spitterId attribute is 42, the resulting redirected URL path will be “/spitter/habuma? SpitterId = 42 “.

Passing data across redirects in the form of path variables and query parameters is straightforward, but it has its limitations. It can only be used to send simple values, such as String and number values. There is no way to send more complex values in a URL, but this is an area where Flash attributes can help.

Using Flash properties

Spring also thinks it’s a good way to put data that survives across redirects into a session. However, Spring doesn’t think we need to manage this data. Instead, Spring provides the ability to send data as Flash attributes. By definition, flash properties carry this data until the next request and then disappear.

Spring provides a way to set Flash attributes through directAttributes, a subinterface to the Model introduced in Spring 3.1. Directattributes provides all the functionality of the Model, but there are several methods for setting Flash attributes.

Specifically, DirectAttributes provides a set of addFlashAttribute() methods to addFlash attributes. Looking again at the processRegistration() method, you can add the Spitter object to the model using addFlashAttribute() :

@RequestMapping(value="/register", method=POST)
public String processRegistration(Spitter spitter, Model model) {
    spitterRepository.save(spitter);
    model.addAttribute("username", spitter.getUsername());
    model.addFlashAttribute("spitter", spitter);
    return "redirect:/spitter/{username}";
}
Copy the code

Here, the addFlashAttribute() method is called with the Spitter as the key and the spitter object as the value. In addition, the key can be inferred based on the value type without setting the key parameter:

model.addFlashAttribute("spitter", spitter);
Copy the code

Since a Spitter object was passed to the addFlashAttribute() method, the inferred key will be Spitter.

All flash attributes are copied into the session before the redirect is executed. After the redirect, flash properties existing in the session are fetched and moved from the session to the model. The method that handles the redirection accesses the Spitter object from the model, just as it would any other model object.

To complete the flash property flow, here is an updated version of the showSpitterProfile() method, which first checks the Spitter object from the model before looking it up from the database:

@RequestMapping(value="/{username}", method=GET)
public String showSpitterProfile(
        @PathVariable String username, Model model) {
  if(! model.containsAttribute("spitter")) {
    model.addAttribute(
        spitterRepository.findByUsername(username));
  }
  return "profile";
}
Copy the code

As you can see, the first thing the showSpitterProfile() method does is check to see if there is a Model property with a Spitter key. If the model contains the Spitter attribute, you don’t need to do anything. The Spitter object contained in this will be passed to the view for rendering. If the model does not contain the spitter attribute, showSpitterProfile() will find the Spitter in the Repository and store it in the model.