Spring MVC

Basis Tecnologia da Informação S.A. - 2019

Agenda

  • O que é MVC?

  • Por que usar?

  • Configurando

  • Executando

  • Annotations

  • Jackson

  • Security

  • Filter

  • Exception

O que é MVC?

Model, View, Controller

Definições

  • "Splits user interface interaction into three distinct roles" (Martin Fowler)

Definições

  • "You’d expect there to be three objects, a model object, a controller object, and a view object. The Model handles the business rules. The Controller handles input. The View handles output" (Uncle Bob)

Definições

mvc

Definições

spring mvc

Por que usar?

Contexto

Client - Server over HTTP

cli ser

Contexto

http

Configurando

Initializr

initializr2

Maven

mav pom

Maven

mvc pom

Spring Roo

  • Spring Boot, Spring Data, Spring IO…​

  • entity, repository, service…​

  • REST & SOAP scaffolding.

    roo.sh
    • project setup

    • web mvc setup

    • jpa setup

Jhipster

Baseado no Yeoman.

  • npm i -g yo

  • npm i -g generator-jhipster

  • yo jhipster

Executando

mvn spring-boot:run ou ./mvnw

Annotations

Padrão desde o Java 1.5 sobre o pacote java.lang.annotation.

Expressam metadados.

Avaliadas em compilation time ou runtime.

Usadas para facilitar configuração, automatização, metaprogramação…​

org.springframework.web.bind.annotation

@RequestMapping

Nas aplicações Spring MVC o RequestDispatcher (Front Controller) é responsável por rotear as requisições HTTP para os métodos dos controlers.

Por ser usada a nível de classe e/ou nível de método.

Exemplo

@RestController
@RequestMapping("/home")
public class IndexController {

    @RequestMapping("/")
    String get(){
        return "Hello from get";
    }

    @RequestMapping("/index")
    String index(){
        return "Hello from index";
    }
}

RequestMethod GET, POST…​

@RestController
@RequestMapping("/home")
public class IndexController {

    @RequestMapping(method = RequestMethod.GET)
    String get(){
        return "Hello from get";
    }

    @RequestMapping(method = RequestMethod.DELETE)
    String delete(){
        return "Hello from delete";
    }
}

RequestMapping Shortcuts

  • @GetMapping

  • @PostMapping

  • @PutMapping

  • @DeleteMapping

  • @PatchMapping

Exemplo

@RestController
@RequestMapping("/home")
public class IndexController {

    @GetMapping
    String get(){
        return "Hello from get";
    }

    @DeleteMapping
    String delete(){
        return "Hello from delete";
    }
}

@RequestParam

Associa um parmâmetro de um método à um parâmetro da requisição.

No Spring MVC, @RequestParam mapeia:

  • query string

  • form data,

  • multipart.

  • body

@RequestParam

A Servlet API combina tudo num mapa chamado "parameters".

No Spring WebFlux, @RequestParam mapeia somente query string. Para os demais é necessário usar databinding via ModelAttribute.

@RequestParam

Por default torna o parâmetro obrigatório. Ou seja, se o parâmetro estiver presente na requisição, será lançada uma Exception.

Interface RequestParam

public @interface RequestParam {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default ValueConstants.DEFAULT_NONE;
}

@RequestParam

String getName(@RequestParam(value="description", required=false), String description)

String getName(@RequestParam(value="description", defaultValue="foo"), String description)

Exemplo

@RestController
@RequestMapping("/home")
public class IndexController {

    @GetMapping(value = "/id")
    String getIdByValue(@RequestParam("id") String personId){
        System.out.println("ID is "+personId);
        return "Get ID from query string of URL with value element";
    }

    @GetMapping(value = "/personId")
    String getId(@RequestParam String personId){
        System.out.println("ID is "+personId);
        return "Get ID from query string of URL without value element";
    }
}

@PathVariable

Associa um parâmetro de um método à uma variável descrita em um template de uma URI.

Exemplo

@RestController
@RequestMapping("/home")
public class IndexController {
    @GetMapping(value = "/fetch/{id}")
    String getDynamicUriValue(@PathVariable String id){
        System.out.println("ID is "+id);
        return "Dynamic URI parameter fetched";
    }

    @GetMapping(value = "/fetch/{id:[a-z]+}/{name}")
        String getDynamicUriValueRegex(@PathVariable("name") String name){
        System.out.println("Name is "+name);
        return "Dynamic URI parameter fetched using regex";
    }
}

@RequestEntity & @ResponseEntity

Ambos extendem org.springframework.http.HttpEntity

HttpEntity encapsula header e body de uma requisição HTTP, tanto do request quanto do response.

  • RequestEntity: Adicina informações sobre o método e a uri.

  • Response: Permite adicionar status codes explicitamente.

Exemplo

@Controller
@RequestMapping
public class MyController {

    @RequestMapping("test")
    public ResponseEntity<String> handleRequest(
        RequestEntity<String> requestEntity
    ) {
        System.out.println("request body : " + requestEntity.getBody());
        HttpHeaders headers = requestEntity.getHeaders();
        System.out.println("request headers : " + headers);
        HttpMethod method = requestEntity.getMethod();
        System.out.println("request method : " + method);
        System.out.println("request url: " + requestEntity.getUrl());

        ResponseEntity<String> responseEntity =
        new ResponseEntity<>("response body", HttpStatus.OK);
        return responseEntity;
    }
}

@RequestBody & @ResponseBody

@RequestBody

Deserializa automaticamente o conteúdo do HttpRequest body para um objeto. (geralmente, transferência ou domínio)

Exemplo

curl -i \ -H "Accept: application/json" \ -H "Content-Type:application/json" \ -X POST --data '{"username": "john Doe", "password": "password"}' "https://localhost:8080/…​/request"

Exemplo

@PostMapping("/request")
public ResponseEntity postController(@RequestBody LoginForm loginForm) {
    exampleService.fakeAuthenticate(loginForm);
    return ResponseEntity.ok(HttpStatus.OK);
}

Exemplo

public class LoginForm {
    private String username;
    private String password;
    // ...
}

@ResponseBody

Instrui o controller a serializar auotmaticamente o objeto retornado e associá-lo ao objeto HttpResponse.

Exemplo

public class ResponseExample {
    private String text;

    // getters/setters
}

Exemplo

@Controller
@RequestMapping("/post")
public class ExamplePostController {

    @Autowired
    ExampleService exampleService;

    @PostMapping("/response")
    @ResponseBody
    public ResponseExample postResponseController(@RequestBody LoginForm loginForm) {
        return new ResponseExample("Goodbye cruel world!");
    }
}

Exemplo

{"text":"Goodbye cruel world!!"}

@Controller vs @RestController

No Spring MVC, @Controller geralmente cria um mapa que associa um objeto derivado de uma model e sua respectiva view.

@RestController é uma combinação de @Controller e @ResponseBody e simplesmente retorna o objeto que é automaticamente associado ao body em um mime-type HTTP válido, normalmente JSON ou XML.

@Controller

@Controller
@RequestMapping(value = "/test")
public class TestController {

    @GetMapping
    public ModelAndView getTestData() {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("test");
        mv.getModel().put("data", "goodbye cruel world!");

        return mv;
    }
}

@Controller & @ResponseBody

@Controller
@RequestMapping(value = "/test")
public class TestRestController {

    @GetMapping
    public @ResponseBody Test getTestData() {
        Test test = new Test();
        test.setData("goodbye cruel world!");

        return test;
    }
}

@RestController

@RestController
@RequestMapping(value = "/test")
public class RestAnnotatedController {

    @GetMapping
    public Test getData() {
        Test test = new Test();
        test.setData("goodbye cruel world!");

        return test;
    }
}

@RequestHeader

Associa um parâmetro de um método à um cabeçalho de um request HTTP.

Exemplo

@Controller
public class HelloController {

        @RequestMapping(value = "/")
        public String hello(@RequestHeader(value="User-Agent") String userAgent)

                //...
        }
}

@CookieValue

Associa um parâmetro de um método a um cookie HTTP.

Exemplo

@Controller
public class ExampleController {
        @RequestMapping(value="/")
        public ModelAndView country(HttpSession session, @CookieValue("JSESSIONID") String cookie) {
           String jsessionid = "JSESSIONID:" + cookie;
        }
}

Jackson

  • https://github.com/FasterXML/jackson

  • Serialize, Deserialize, Mapping, Parsing…​

  • Compliance com a JSR-353 (Java JSON API)

  • Suporte aos tipos da org.json (JSONObject, JSONArray…​)

  • Mongo, geojson…​

@JsonProperty

Mapeia uma propriedade de um objeto (geralmente, trasnferência ou entity) à uma propriedade de um objeto json.

Exemplo

private class Employee {

    @JsonProperty("employee-name")
    private String name;

    @JsonProperty("employee-dept")
    private String dept;

    private String position;

    public String getName() {
        return name;
    }

    @JsonProperty("position")
    public String getThePosition() {
        return this.position;
    }
}

@JsonIgnore

Ignora propriedades durante o processo de serializeção/deserialização.

Pode ser usado a nível de classe com @JsonIgnoreProperties(value = { "name" })

Pode ser usado diretamente na propriedade com @JsonIgnore

Exemplo

private class User {

    @JsonProperty("user-name")
    private String name;

    @JsonIgnore
    private String password;

}

@JsonManagedReference & @JsonBackReference

Resolvem problemas de referência cíclica em relações pai/filho.

Exemplo

public class Item {
    public int id;
    public String itemName;

    @JsonManagedReference
    public User owner;
}

Exemplo

public class User {
    public int id;
    public String name;

    @JsonBackReference
    public List<Item> userItems;
}

Conversores customizados

Exemplo

public class ItemSerializer extends StdSerializer<Item> {

    public ItemSerializer() {
        this(null);
    }

    public ItemSerializer(Class<Item> t) {
        super(t);
    }

    @Override
    public void serialize(
        Item value,
        JsonGenerator gen,
        SerializerProvider provider
    ) throws IOException, JsonProcessingException {

        gen.writeStartObject();
        gen.writeNumberField("id", value.id);
        gen.writeStringField("itemName", value.itemName);
        gen.writeNumberField("owner", value.owner.id);
        gen.writeEndObject();
    }
}

Exemplo

@JsonSerialize(using = ItemSerializer.class)
public class Item {
    ...
}

Modules

Instalação

<dependency>
  <groupId>com.bedatadriven</groupId>
  <artifactId>jackson-datatype-jts</artifactId>
  <version>2.2</version>
</dependency>

Exemplo

@Configuration
public class GeoConfiguration {

    @Bean
    public Module jtsModule() {
        return new JtsModule();
    }
}

Security

Gerenciamento autenticação e autorização.

Proteção para ataques como:

  • session fixation

  • clickjacking

  • cross site request forgery…​

Uma classe anotada com @EnableWebSecurity e extendendo WebSecurityConfigurerAdapter

Security

protected void configure(AuthenticationManagerBuilder auth)
  • Tipo de autenticação (memória, jdbc, ldap…​)

  • Especificar DataSources

  • Estratégia de encoding das senhas…​

Security

protected void configure(HttpSecurity http)
  • Autorizar, desautorizar requests

  • Definir tipos de autenticação (basic, bearer…​)

  • Proteger rotas

  • Adicionar filtros

Exemplo

@Configuration
@EnableWebSecurity
public class BasicConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth)
      throws Exception {
        auth
          .inMemoryAuthentication()
          .withUser("user")
            .password("password")
            .roles("USER")
            .and()
          .withUser("admin")
            .password("admin")
            .roles("USER", "ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
          .anyRequest()
          .authenticated()
          .and()
          .httpBasic();
    }
}

@Secured, @RoleAllowed, @hasRole…​

  • Especificam uma lista de ROLES que tem acesso à execução de um método.

  • Não suporta SpEL.

@Secured("ROLE_ADMIN")
public String edit() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return securityContext.getAuthentication().getName();
}

@RoleAllowed

É derivada da JSR-250 e tem o mesmo funcionamento da @Secured

@PreAuthorize("hasRole('ROLE_ADMIN')")
public String edit() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return securityContext.getAuthentication().getName();
}

@PreAuthorize

  • Suporta SpEL.

  • Avalia a expressão ANTES de executar o método

@PreAuthorize("#username == authentication.principal.username")
public String getRoles(String username) {
    //...
}

@PostAuthorize

  • Suporta SpEL.

  • Avalia a expressão DEPOIS de executar o método

@PostAuthorize
  ("returnObject.username == authentication.principal.nickName")
public CustomUser loadUserDetail(String username) {
    return userRoleRepository.loadUserByUserName(username);
}

Filters

A infraestrutura interna do Spring MVC é totalmente baseada em cadeia de filters.

Pode ser utilizado para diversas coisas como:

  • interceptar requisições

  • black/white ip list

  • negociação de conteúdo

  • verificação de autenticação

Filters

filter

Exemplo

@Component
@Order(1)
public class TransactionFilter implements Filter {

    @Override
    public void doFilter
      ServletRequest request,
      ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        LOG.info(
          "Starting a transaction for req : {}",
          req.getRequestURI());

        chain.doFilter(request, response);
        LOG.info(
          "Committing a transaction for req : {}",
          req.getRequestURI());
    }

    // other methods
}

Exemplo

 http.addFilter(transactionFilter);

Exceptions

Solução 1 – @ExceptionHandler a nível do controller

Definimos um método para captaturar as exceptions usando a annotation @ExceptionHandler

public class FooController{

    //...
    @ExceptionHandler({ CustomException1.class, CustomException2.class })
    public void handleException() {
        //
    }
}

Solução 2 – Resolver

  • DefaultHandlerExceptionResolver (captura os erros 4xx e 5xx): Define apenas o status code, não adiciona informações do body.

  • ResponseStatusExceptionResolver: Captura exceptions customizadas anotadas com @ResponseStatus e as mapeia para respectivos status codes.

Custom HandlerExceptionResolver

@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    protected ModelAndView doResolveException
      (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            if (ex instanceof IllegalArgumentException) {
                return handleIllegalArgument((IllegalArgumentException) ex, response, handler);
            }
            ...
        } catch (Exception handlerException) {
            logger.warn("Handling of [" + ex.getClass().getName() + "]
              resulted in Exception", handlerException);
        }
        return null;
    }

    private ModelAndView handleIllegalArgument
      (IllegalArgumentException ex, HttpServletResponse response) throws IOException {
        response.sendError(HttpServletResponse.SC_CONFLICT);
        String accept = request.getHeader(HttpHeaders.ACCEPT);
        ...
        return new ModelAndView();
    }
}

Solução 3 – ResponseStatusException (Spring 5 em diante)

@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) {
    try {
        Foo resourceById = RestPreconditions.checkFound(service.findOne(id));

        eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
        return resourceById;
     }
    catch (MyResourceNotFoundException exc) {
         throw new ResponseStatusException(
           HttpStatus.NOT_FOUND, "Foo Not Found", exc);
    }
}

Solução 4 – Solução 4 – @ControllerAdvice

Permite tratar exceções de maneira global.

@ControllerAdvice
public class GlobalExceptionHandler
  extends ResponseEntityExceptionHandler {

    @ExceptionHandler(
        value={IllegalArgumentException.class, IllegalStateException.class }
    )
    protected ResponseEntity<Object> handleConflict(
        RuntimeException ex, WebRequest request
    ) {
        String body = "Should be unique per request";
        return handleExceptionInternal(
            ex, body,
            new HttpHeaders(),
            HttpStatus.CONFLICT,
            request
        );
    }
}

Solução 5 - Zalando

  • Baseado na RFC 7807 - Problem Details for HTTP APIs.

  • application/problem+json

  • Baseado em @ControllerAdvice

Instalação

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem</artifactId>
    <version>${problem.version}</version>
</dependency>
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>jackson-datatype-problem</artifactId>
    <version>${problem.version}</version>
</dependency>

Exemplo

Problem.builder()
.withType(URI.create("https://example.org/out-of-stock"))
.withTitle("Out of Stock")
.withStatus(BAD_REQUEST)
.withDetail("Item B00027Y5QG is no longer available")
.build();