Spring MVC Part 2 Spencer Uresk
Notes This is a training, NOT a presentation Please ask questions This is being recorded Prerequisites – Beginning Spring MVC (and all of its prerequisites)
Overview Last time, we showed how to map requests to handler methods, get information about the request, and how to pass information back to the view We’ll see what an HTTP message looks like This week, we’ll look at some of Spring MVC’s RESTful features, including RequestBody, ResponseBody, HttpMessageConverters, HttpEntity objects, and dealing with exceptions These are useful for RESTful web services and normal form-based interactions
HTTP Message What does an HTTP message look like? Sample Requests: GET /view/1 HTTP/1.1 User-Agent: Chrome Accept: application/json [CRLF] POST /save HTTP/1.1 User-Agent: IE Content-Type: application/x-www-form-urlencoded [CRLF] name=x&id=2 Request Line Headers Request Line Headers Request Body
HTTP Message (Responses) Sample Responses HTTP/ OK Content-Type: text/html Content-Length: 1337 [CRLF] Some HTML Content. HTTP/ Internal Server Error HTTP/ Created Location: /view/7 [CRLF] Some message goes here. Status Line Headers Response Body Status Line Headers Response Body
RequestBody Annotating a handler method parameter will bind that parameter to the request public void String input) public void SomeObject input) {}
ResponseBody Annotating a return type tells Spring MVC that the object returned should be treated as the response body No view is rendered in this String readString() SomeObject readJson() {}
HttpMessageConverters How does Spring MVC know how to turn a JSON string into SomeObject, or vice-versa? HttpMessageConverters These are responsible for converting a request body to a certain type, or a certain type into a response body Spring MVC figures out which converter to use based on Accept and Content-Type headers, and the Java type Your Accept and Content-Type headers DON’T have to match. For example, you can send in JSON and ask for XML back
HttpMessageConverters A number of HttpMessageConverters are already provided You can define your own, but that is outside the scope of this training You don’t specify which ones are used to convert request/response bodies – they are selected based on the Content-Type/Accept headers
MIME Types HttpMessageConverters make heavy use of MIME types (RFC 2046) These are the value for Accept and Content-Type headers Two-part identifier for content formats First part is the type. ie, application Second part is the sub-type. ie, json application/json
StringHttpMessageConverter Reads and writes Strings. Reads text/* Writes text/plain
StringHttpMessageConverter a POST /echo/string HTTP/1.1 Accept: text/plain Content-Type: text/plain String String input) { return “Your Text Was: “ + input; } HTTP/ OK Content-Type: text/plain Content-Length: 17 Your Text Was: Hello! REQUEST RESPONSE
MappingJacksonHttpMessageConverter Maps to/from JSON objects using the Jackson library Reads application/json Writes application/json
MappingJacksonHttpMessageConverter a POST /echo/string HTTP/1.1 Accept: application/json Content-Type: application/json { “name” : “Spencer”, “age” : 5 } public Person { // String name, int age; Person Person person) { // Upper case name, square age return person; } HTTP/ Created Content-Type: application/json { “name” : “SPENCER”, “age” : 25 } REQUEST RESPONSE
Jaxb2RootElementHttpMessageConverter Maps to/from XML objects Must have your object at least annotated Reads text/xml, application/xml Writes text/xml, application/xml
Jaxb2RootElementHttpMessageConverter a POST /echo/string HTTP/1.1 Accept: application/xml Content-Type: application/xml Spencer public Person {// String name, int Person Person person) { // Upper case name, square age return person; } HTTP/ Created Content-Type: application/xml SPENCER 25 REQUEST RESPONSE
ByteArrayHttpMessageConverter Can read/write byte arrays (useful for dealing with binary data, such as images) Reads */* Writes application/octet-stream
ByteArrayHttpMessageConverter a POST /echo/string HTTP/1.1 Accept: text/plain Content-Type: text/plain String byte[] input) { return new String(input); } HTTP/ OK Content-Type: application/octet-stream Content-Length: 6 Hello! REQUEST RESPONSE
Lab 1 Create a handler that takes a request body and echoes it back. Create a handler that takes a request body, creates an object with it, and returns it as JSON. Create a handler that takes an XML input and echoes it back as JSON. Test all of these with your HttpClient
Other parts of the HTTP Message What if you need to get/set headers? Or set the status String String input, HttpServletRequest request, HttpServletResponse response) { String requestType = request.getHeader(“Content-Type”); response.setHeader(“Content-Type”, “text/plain”); response.setStatus(200); return input }
@ResponseStatus There is a convenient way to set what the default status for a particular handler @ResponseStatus(HttpStatus.CREATED) // CREATED == 201 public void echoString(String input) { }
HttpEntity Convenience class for dealing with bodies, headers, and status Converts messages with public ResponseEntity upload(HttpEntity rEntity) { String t = rEntity.getHeaders().getFirst(“Content-Type”); byte[] data = rEntity.getBody(); // Save the file HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.set(“Location”, “/image/1”); return new ResponseEntity (“Created”, responseHeaders, HttpStatus.CREATED); }
Lab 2 Convert all your String controller method to use HttpEntity Convert the Create Person controller method to use an HttpEntity. Also, return a Location header and a 201 (Created) response code.
Dealing with exceptions By default, Spring MVC will map certain exceptions to status codes You can implement your own HandlerExceptionResolver, which takes an exception and returns a ModelAndView You can register a SimpleMappingExceptionResolver to map exceptions to views You can annotate methods in the controller to handle specific exceptions
Default Exception Mappings These take effect if you have no other configuration ConversionNotSupportedException => 500 NoSuchMethodHandlingException => 404 MissingServletRequestParameterException => 400 HttpRequestMethodNotSupportedException => 405 TypeMismatchException => 400 HttpMediaTypeNotSupportedException => 415 HttpMediaTypeNotAcceptableException => 406
HandlerExceptionResolver Allows you to control how exceptions are resolved Implement HandlerExceptionResolver (but you’ll probably extend AbstractHandlerExceptionResolver) class AnExceptionHandler extends AbstractHandlerExceptionResolver { protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { System.out.println("I got an error."); return new ModelAndView("errors/someError"); }
SimpleMappingExceptionResolver Allows you to simply map exceptions to views This is how the Stack comes configured errors/dataAccessFailure errors/resourceNotFound
@ExceptionHandler Create a method to handle the exception, annotate it and pass in the exception(s) that method can handle ExceptionHandler methods look a lot like normal handler public void doSomething() { throw new RecoverableDataAccessException("Unable to access that database."); String handleDataAccessError(DataAccessException ex) { return ex.getMessage(); }
@ResponseStatus We saw this annotation earlier It can also be placed on Exception classes methods to return a specific status code for a public void handleDataAccessError(DataAccessException ex) = HttpStatus.PAYMENT_REQUIRED, message = “I need money.”) public class PaymentRequiredException {}
Lab 3 Look at the SimpleMappingExceptionResolver already configured in your project Create a controller that throws one of those exceptions and verify that your request gets redirected to the corresponding view Remove the config, and change your exception to HttpMediaTypeNotSupportedException. Verify that you get a 415 using your Http Client Implement method