300 likes | 454 Views
Spring MVC Part 2. Spencer Uresk. Notes. This is a training, NOT a presentation Please ask questions This is being recorded https://tech.lds.org/wiki/Java_Stack_Training Prerequisites Beginning Spring MVC (and all of its prerequisites). Overview.
E N D
Spring MVC Part 2 Spencer Uresk
Notes • This is a training, NOT a presentation • Please ask questions • This is being recorded • https://tech.lds.org/wiki/Java_Stack_Training • 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: Request Line GET /view/1 HTTP/1.1 User-Agent: Chrome Accept: application/json [CRLF] Headers POST /save HTTP/1.1 User-Agent: IE Content-Type: application/x-www-form-urlencoded [CRLF] name=x&id=2 Request Line Headers Request Body
HTTP Message (Responses) • Sample Responses HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1337 [CRLF] <html> Some HTML Content. </html> Status Line Headers Response Body HTTP/1.1 500 Internal Server Error Status Line HTTP/1.1 201 Created Location: /view/7 [CRLF] Some message goes here. Status Line Headers Response Body
RequestBody • Annotating a handler method parameter with @RequestBody will bind that parameter to the request body @RequestMapping("/echo/string") public void writeString(@RequestBody String input) {} @RequestMapping("/echo/json") public void writeJson(@RequestBodySomeObject input) {}
ResponseBody • Annotating a return type with @ResponseBody tells Spring MVC that the object returned should be treated as the response body • No view is rendered in this case @RequestMapping("/echo/string") public @ResponseBodyStringreadString() {} @RequestMapping("/echo/json") public @ResponseBodySomeObjectreadJson() {}
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-Typeheaders • 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 @RequestMapping("/echo/string") public @ResponseBody String echoString(@RequestBody String input) { return “Your Text Was: “ + input; } • a POST /echo/string HTTP/1.1 Accept: text/plain Content-Type: text/plain Hello! REQUEST HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 17 Your Text Was: Hello! RESPONSE
MappingJacksonHttpMessageConverter • Maps to/from JSON objects using the Jackson library • Reads application/json • Writes application/json
MappingJacksonHttpMessageConverter public Person { // String name, int age; } @RequestMapping("/echo/json") public @ResponseBodyPerson echoJson(@RequestBodyPerson person) { // Upper case name, square age return person; } • a POST /echo/string HTTP/1.1 Accept: application/json Content-Type: application/json { “name” : “Spencer”, “age” : 5 } REQUEST HTTP/1.1 201 Created Content-Type: application/json { “name” : “SPENCER”, “age” : 25 } RESPONSE
Jaxb2RootElementHttpMessageConverter • Maps to/from XML objects • Must have your object at least annotated with @XmlRootElement • Reads text/xml, application/xml • Writes text/xml, application/xml
Jaxb2RootElementHttpMessageConverter @XmlRootElement public Person {// String name, int age;} @RequestMapping("/echo/xml") public @ResponseBodyPerson echoXml(@RequestBodyPerson person) { // Upper case name, square age return person; } • a POST /echo/string HTTP/1.1 Accept: application/xml Content-Type: application/xml <thing><name>Spencer</name><age>5</age></thing> REQUEST HTTP/1.1 201 Created Content-Type: application/xml <thing><name>SPENCER</name><age>25</age></thing> RESPONSE
ByteArrayHttpMessageConverter • Can read/write byte arrays (useful for dealing with binary data, such as images) • Reads */* • Writes application/octet-stream
ByteArrayHttpMessageConverter @RequestMapping("/echo/string") public @ResponseBody String echoString(@RequestBody byte[] input) { return new String(input); } • a POST /echo/string HTTP/1.1 Accept: text/plain Content-Type: text/plain Hello! REQUEST HTTP/1.1 200 OK Content-Type: application/octet-stream Content-Length: 6 Hello! 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 code? @RequestMapping("/echo/string") public @ResponseBody String echoString(@RequestBodyString 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 should be • @ResponseStatus @RequestMapping("/create") @ResponseStatus(HttpStatus.CREATED) // CREATED == 201 public void echoString(String input) { }
HttpEntity • Convenience class for dealing with bodies, headers, and status • Converts messages with HttpMessageConverters @RequestMapping("/image/upload") public ResponseEntity<String> upload(HttpEntity<byte[]> rEntity) { String t = rEntity.getHeaders().getFirst(“Content-Type”); byte[] data = rEntity.getBody(); // Save the file HttpHeadersresponseHeaders = new HttpHeaders(); responseHeaders.set(“Location”, “/image/1”); return new ResponseEntity<String>(“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 AnExceptionHandlerextends AbstractHandlerExceptionResolver{ protected ModelAndViewdoResolveException( 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 <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key=".DataAccessException">errors/dataAccessFailure</prop> <prop key=".AccessDeniedException">errors/dataAccessFailure</prop> <prop key=".TypeMismatchException">errors/resourceNotFound</prop> </props> </property> <property name="defaultErrorView" value="errors/generalError"/> <property name="warnLogCategory" value="org.lds.stack"/> </bean>
@ExceptionHandler • Create a method to handle the exception, annotate it with @ExceptionHandler, and pass in the exception(s) that method can handle • ExceptionHandler methods look a lot like normal handler methods @RequestMapping("/error") public void doSomething() { throw new RecoverableDataAccessException("Unable to access that database."); } @ExceptionHandler(DataAccessException.class) public @ResponseBody String handleDataAccessError(DataAccessException ex) { return ex.getMessage(); }
@ResponseStatus • We saw this annotation earlier • It can also be placed on Exception classes or @ExeptionHandler methods to return a specific status code for a particular exception @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(DataAccessException.class) public void handleDataAccessError(DataAccessExceptionex) {} @ResponseStatus(value = 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 an @ExceptionHandler method