Knowledge Transfer

Ethickfox kb page with all notes


Project maintained by ethickfox Hosted on GitHub Pages — Theme by mattgraham

Spring MVC

Web framework that allows to create web applications and RESTful services

MVC в Spring

MVC is popular as it isolates the application logic from the user interface layer and supports separation of concerns. Here the Controller receives all requests for the application and then works with the Model to prepare any data needed by the View. The View then uses the data prepared by the Controller to generate a final presentable response.

Component Stereotypes

Controller

Allows next parameters:

Allows next return types:

@RequestMapping

The annotation is used to map web requests to Spring Controller methods.

@RequestMapping(
            path = "/value",                  //endpoint
            params = {"id", "second"},        //?id="123"&second="456"
            headers = {"api-type!=new"},      //accepts only requests where no api-type=new in header
            consumes = "application/json",    //accepts only requests where type application/json
            produces = "application/json",
            method = RequestMethod.GET)
    public String getValue(String id, String second) {
        return "Params: " + id + " " + second;
    }

Spring Framework 4.3 introduced a few new HTTP mapping annotations, all based on @RequestMapping:

@GetMapping("/{id}")
public ResponseEntity<?> getBazz(@PathVariable String id){
    return new ResponseEntity<>(new Bazz(id, "Bazz"+id), HttpStatus.OK);
}

@PostMapping
public ResponseEntity<?> newBazz(@RequestParam("name") String name){
    return new ResponseEntity<>(new Bazz("5", name), HttpStatus.OK);
}

@PutMapping("/{id}")
public ResponseEntity<?> updateBazz(
  @PathVariable String id,
  @RequestParam("name") String name) {
    return new ResponseEntity<>(new Bazz(id, name), HttpStatus.OK);
}

@DeleteMapping("/{id}")
public ResponseEntity<?> deleteBazz(@PathVariable String id){
    return new ResponseEntity<>(new Bazz(id), HttpStatus.OK);
}

@PathVariable

Extracts part of uri

@RequestMapping(value = "/ex/foos/{id}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariable(@PathVariable("id") long id) {
    return "Get a specific Foo with id=" + id;
}

//aslo regexes are available too
@RequestMapping(value = "/ex/bars/{numericId:[\\d]+}", method = GET)
@ResponseBody
public String getBarsBySimplePathWithPathVariable(
  @PathVariable long numericId) {
    return "Get a specific Bar with id=" + numericId;
}

@RequestParam

Извлекает параметры запроса из запроса

// http://localhost:8080/api/foos?id=abc
public String getFoos(@RequestParam String id)

@RequestHeader

Извлекает содержимое заголовка

String greeting(@RequestHeader("accept-language") String language)

@ResponseStatus

If we want to specify the response status of a controller method, we can mark that method with @ResponseStatus. It has two interchangeable arguments for the desired response status: code, and value.

@ResponseStatus(HttpStatus.BAD_REQUEST, reason = "Some parameters are invalid")
void onIllegalArgumentException(IllegalArgumentException exception) {}

We have three ways to use @ResponseStatus to convert an Exception to an HTTP response status:

@ResponseStatus(code = HttpStatus.BAD_REQUEST)
class CustomException extends RuntimeException {}

@ModelAttribute

Позволяет передать модель в вид

@ModelAttribute
public void addAttributes(Model model) {
	model.addAttribute("msg", "Welcome to the Netherlands!");
}

Model

Spring автоматически добавляет этот класс в качестве параметра в контроллер. В нем можно указать все данные, которые необходимо отобразить в представлении.

@GetMapping("/account/{userId}")

public String account(Model model, @PathVariable Integer userId) {
	model.addAttribute("user", userDao.findById(userId));
	return "templates/account"; // (3)
}

Сontent negotiation

Существует множество способов, с помощью которых клиент может указать Spring MVC, какой формат ответа ожидается при выполнении запроса.

The code snippet below will not result in ambiguous mapping error because both methods return different content types:

@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_XML_VALUE)
public String duplicateXml() {
    return "<message>Duplicate</message>";
}
    
@GetMapping(value = "foos/duplicate", produces = MediaType.APPLICATION_JSON_VALUE)
public String duplicateJson() {
    return "{\"message\":\"Duplicate\"}";
}

WebSocket

Rest отображение

Сгенерировать json или xml можно с помощью соответствующей библиотеки. По умолчанию это Jackon. Нужно добавить аннотацию @ResponseBody к одному из маппингов или @RestController классу контроллеру.

@GetMapping("/health")
@ResponseBody // (1)
public HealthStatus health() {
	return new HealthStatus(); // (2)
}

@ExceptionHandler

Spring configuration will detect this annotation and register the method as an exception handler for the argument exception class and its subclasses.

@Controller
public class PageController {

  @RequestMapping(value = "/{id}", method = RequestMethod.GET)
  public ModelAndView getPage(@PathVariable("id") String id) throws Exception {
      throw new RecordNotException("Page not found for id : " + id);
  }

  @ExceptionHandler(RecordNotException.class)
  public ModelAndView handleException(RecordNotException ex) {
    ...
    return modelAndView;
  }
}

Handler methods that are annotated with this annotation are allowed to have very flexible signatures. They can accept arguments of different types. For example, an exception argument, request and/or response objects, session object, locale object and model object etc.

Similar to arguments, return types can be of different types. For example, ModelAndView object, Model object, View object, view name as String etc. We can mark the method to void also if the method handles the response itself by writing the response content directly to HttpServletResponse.

We may combine the ExceptionHandler annotation with @ResponseStatus for a specific HTTP error status.

@ExceptionHandler({NullPointerException.class, ArrayIndexOutOfBoundsException.class, IOException.class})
@ResponseBody
public ErrorResponse handleException(AjaxException ex, HttpServletResponse response) {
    ...
}

@ControllerAdvice

If we want to centralize the exception-handling logic to one class that is capable of handling exceptions thrown from any handler class/ controller class – then we can use @ControllerAdvice annotation.

By default, the methods in an @ControllerAdvice apply globally to all controllers. We can create a class and add @ControllerAdvice annotation on top. Then add @ExceptionHandler methods for each type of specific exception class in it.

Notice we extended the exception handler class with ResponseEntityExceptionHandler. It is a convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods.

@ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler
{
  @ExceptionHandler(Exception.class)
  public final ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex, WebRequest request) {
    List<String> details = new ArrayList<>();
    details.add(ex.getLocalizedMessage());
    ErrorResponse error = new ErrorResponse(ApplicationConstants.SERVER_ERROR, details);
    return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
  }
  @ExceptionHandler(RecordNotFoundException.class)
  public final ResponseEntity<ErrorResponse> handleUserNotFoundException(RecordNotFoundException ex,                       WebRequest request) {
    List<String> details = new ArrayList<>();
    details.add(ex.getLocalizedMessage());
    ErrorResponse error = new ErrorResponse(ApplicationConstants.RECORD_NOT_FOUND, details);
    return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
  }
}

Http Request processing

Web container like Tomcat is responsible for creating Servlet and Filter instances and invoking their various life-cycle methods like MVC, service(), destroy(). In the case of an HTTP request, HttpServlet handles that, and depending upon the HTTP request method various doXXX() method is invoked by container like doGet() to process GET request and doPost() to process POST request.

DispatcherServlet

The DispatcherServlet is the main controller of the Spring MVC Application. All incoming web request passes through DispatcherServlet before processed by individual Spring controllers i.e classes annotated using @Controller annotation. A web application can define any number of DispatcherServlet instances. Each instance will operate in its own namespace, loading its own application context with mappings, handlers, etc. Only the root application context is loaded by ContextLoaderListener, if any, will be shared. In most cases, applications have only a single DispatcherServlet with the context-root URL(/), that is, all requests coming to that domain will be handled by it.

Он реализует паттерн Front controller. https://en.m.wikipedia.org/wiki/Front_controller

Untitled 57.png

  1. После получения HTTP-запроса DispatcherServlet обращается к интерфейсу HandlerMapping, который определяет, какой Контроллер должен быть вызван, после чего, отправляет запрос в нужный Контроллер
  2. After receiving HTTP request, Application Server (Standalone or Embedded) searches for Servlet that can handle request, DispatcherServlet is selected based on Servlet Registration and url-pattern.
  3. DispatcherServlet uses HandlerMapping classes to get request mapping information and HandlerAdapter.
  4. DispatcherServlet uses HandlerAdapter to execute controller method that will handle request.
  5. DispatcherServlet interprets results of method execution and renders View with help of ViewResolver classes
<web-app>
	<!-- The front controller of this Spring Web application, responsible 
	for handling all application requests -->
	<servlet>
	  <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
	  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
		   <param-name>contextConfigLocation</param-name>
		   <param-value>/WEB-INF/config/web-application-config.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
	   <servlet-name>example</servlet-name>
	   <url-pattern>*</url-pattern>
	</servlet-mapping>
</web-app>

ViewResolver

The InternalResourceViewResolver is an implementation of the ViewResolver interface in the top10springm which resolves logical view names like "hello" to internal physical resources like Servlet and JSP files like jsp files placed under the WEB-INF folder.

It's also a  best practice to put all JSP files inside the WEB-INF directory, to hide them from direct access (e.g. via a manually entered URL). If we put them inside the WEB-INF directory then only controllers will be able to access them.

There are next types of view resolvers:

Client

RestTemplate

Spring REST Api Client