Skip to content

Instantly share code, notes, and snippets.

@renatoapcosta
Last active December 23, 2019 19:38
Show Gist options
  • Save renatoapcosta/e3981be948ec51011fd2fbe4803d5b54 to your computer and use it in GitHub Desktop.
Save renatoapcosta/e3981be948ec51011fd2fbe4803d5b54 to your computer and use it in GitHub Desktop.
Spring Boot Web

Spring Boot Web

Spring Boot

Introdução

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Retornando dados

@Controller					@RestController
@RequestMapping("/home")			@RequestMapping("/home")
public class HomeController {			public class HomeController {

	@GetMapping("/")				
	@ResponseBody					@GetMapping("/")
	public String index() {				public String index() {
		return "Hello Mundo!!";				return "Hello Mundo!!";
	}						}
}						}

Quando usamos @RestController não precisamos @ResponseBody, pois todos os método recebe esse anotação.

Sem o @ResponseBody o método devolve o local de uma pagina, o caminho de um html ou jsp.

Mapeando o verbo

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

Podemos mapear uma classe ou um método

@RequestMapping(path="/caminho", method = RequestMethod.GET)

O method não é usado na classe, somento nos métodos.

Passando parametros

PathVariable

Single @PathVariable

@GetMapping(path = "/hello-world/path-variable/{name}")
public HelloWorldBean helloWorldPathVariable(@PathVariable String name) {
	return new HelloWorldBean(String.format("Hello World, %s", name));
}
curl http://localhost:8080/hello-world/path-variable/renato

Multiple @PathVariable

@GetMapping(value = "/ex/foos/{fooid}/bar/{barid}")
public String getFoosBySimplePathWithPathVariables
  (@PathVariable long fooid, @PathVariable long barid) {
    return "Get a specific Bar with id=" + barid + 
      " from a Foo with id=" + fooid;
}
curl http://localhost:8080/ex/foos/1/bar/2

Podemos usar @PathVariable em um objeto

@GetMapping(value = "/ex/foos/{fooid}/bar/{barid}")
public String getFoosByPathWithPathVariablesObject
  (Bean bean) {
    return "Get a specific Bar with id=" + bean.getBarid() + 
      " from a Foo with id=" + bean.getFooid();
}

class Bean{
  private String barid;
  private String fooid;
}

RequestBody

@PostMapping("/users")
public void createUser(@RequestBody User user){
	User savedUser = service.save(user);
}

Query String

http://localhost:8080/users?name=Renato

Mapeando parametros

@GetMapping("/users")
public void createUser(@RequestParam String name){
	...
}

Mapeando um objeto

@GetMapping("/users")
public void createUser(User user){
	...
}

Passando pelo Header

@GetMapping(value = "/ex/header1")
public String getFoosAsJsonFromBrowser(@RequestHeader HttpHeaders headers) {
	return headers.get("Accept").toString();
}

Trabalhando com Datas

Por Query String e PathVariable

Quando LocalDate está como parametro no controller como query string necessitamos @RequestParam e também da @DateTimeFormat(pattern = "ddMMyyyy") para informar o formato recebido.

Quando o LocalDate estiver em uma classe que representa um conjunto de parametros por query string, o LocalDate deve receber a anotation @DateTimeFormat.

@GetMapping(value = "/ex")
public void getDate(@RequestParam("datRecepcao") 
	@DateTimeFormat(pattern="ddMMyyyy") LocalDate datRecepcao) {
	...
}
@GetMapping(value = "/ex/{datRecepcao}")
public void getDate(@PathVariable("datRecepcao") 
	@DateTimeFormat(pattern="ddMMyyyy") LocalDate datRecepcao) {
	...
}

Por RequestBody

Quando recebemos um JSON devemos deserializar os dados.

Quando enviamos os dados como response devemos serializar.

Quando o LocalDate estiver em uma classe que é recebida pelo body, ai as configurações do JacksonConfig entra em ação:

public class JacksonConfig {


    public static final String PATERNS_DD_MM_YYYY_HH_MM_SS = "dd/MM/yyyy HH:mm:ss";
    public static final String DD_MM_YYYY = "dd/MM/yyyy";

    @Primary
    @Bean
    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder
                .createXmlMapper(false)
                .serializers(new LocalDateSerializer(new DateTimeFormatterBuilder()
                        .appendPattern(DD_MM_YYYY).toFormatter()))
                .serializers(new LocalDateTimeSerializer(new DateTimeFormatterBuilder()
                        .appendPattern(PATERNS_DD_MM_YYYY_HH_MM_SS).toFormatter()))
                .deserializers(new LocalDateDeserializer(new DateTimeFormatterBuilder()
                        .appendPattern(DD_MM_YYYY).toFormatter()))
                .deserializers(new LocalDateTimeDeserializer(new DateTimeFormatterBuilder()
                        .appendPattern(PATERNS_DD_MM_YYYY_HH_MM_SS).toFormatter()))

                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                .defaultUseWrapper(false)
                .build();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
        objectMapper.setDateFormat(new SimpleDateFormat(PATERNS_DD_MM_YYYY_HH_MM_SS));
        return objectMapper;
    }
}

Comentando esta classe de configuração, o Request Body recebe o LocalDate no formato yyyy-MM-dd padrão.

Para personalizar o formato do LocalDate no RequestBody diferente do deserializador padrão do JacksonConfig, crie um:

public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {

    @Override
    public LocalDate deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
        return LocalDate.parse(jsonParser.getText(), DateTimeFormatter.ofPattern("ddMMyyyy"));
    }
}

Para configurar a resposta devemos serializar o LocalDate. O JacksonConfig configura uma serialização padrão e para customizar uma serialização podemos usar um serializador:

public class LocalDateSerializer extends JsonSerializer<LocalDate> {
    @Override
    public void serialize(LocalDate localDate, JsonGenerator jsonParser, SerializerProvider provider) throws IOException {
        jsonParser.writeString(localDate.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")));
    }
}

No DTO devemos anotar

@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate datInicioVigencia;

Os serializadores/deserializadores customizados tem prioridade.

Configurando Rest

Excluindo campos nulls no response:

spring.jackson.default-property-inclusion=NON_NULL

Excluindo os campos nulls de uma classes especifica

@JsonInclude(Include.NON_NULL)
public class MyDto { ... }

Excluindo o atributo null de um campo especifico

public class MyDto {
 
    @JsonInclude(Include.NON_NULL)
    private String stringValue;
 
    private int intValue;

Outra forma de excluir os nulls de forma global:

mapper.setSerializationInclusion(Include.NON_NULL);

ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
MyDto dtoObject = new MyDto();

String dtoAsString = mapper.writeValueAsString(dtoObject);

assertThat(dtoAsString, containsString("intValue"));
assertThat(dtoAsString, containsString("booleanValue"));
assertThat(dtoAsString, not(containsString("stringValue")));

Para excluir o tipo Opcional, use o valor NON_ABSENT.

spring.jackson.default-property-inclusion=NON_ABSENT

Serializando Enum com Jackson

public enum Status {
    SUCCESS("success"), ERROR("error");
 
    private final String status;
 
    @JsonCreator
    Status(String status) {
        this.status = status;
    }
 
    @JsonValue
    public String getStatus() {
        return this.status;
    }
}

Resultando em:

{
  "status": "success"
}

Para mudar o nome de um atributo no response:

@JsonProperty("_postman_id")
private String postmanId = "";

Assim a resposta será:

{
    "_postman_id": ""
}

Validação

Quando recebemos uma requisição, podemos validar o conteúdo, para retornar uma resposta adequada.

Adicionamos no parametro a anotation @Valid do pacote javax.validation.Valid.

@PostMapping("/users")
public ResponseEntity<Object> createUser(@Valid @RequestBody User user) {
	...
}

No DTO podemos usar BeanValidation

public class User {

	@NotNull
	@Min(0)
        @Max(999999999999999L)
        private Long id;

	@Size(min = 2, max = 30)
	private String name;

	@Past
	private Date birthDate;
	
	@Pattern(regexp="^(AC|AL|AM|AP|BA|CE|DF|ES|GO|MA|MG|MS|MT|PA|PB|PE|PI|PR|RJ|RN|RO|RR|RS|SC|SE|SP|TO)$",message="UF inválida")
    	private String uf;
	
	@Pattern(regexp = "^(11|12|13|14|15|16|17|18|19|21|22|24|27|28|31|32|33|34|35|37|38|41|42|43|44|45|46|47|48|49|51|53|54|55|61|62|63|64|65|66|67|68|69|71|73|74|75|77|79|81|82|83|84|85|86|87|88|89|91|92|93|94|95|96|97|98|99)([2-7][0-9]{7})$",
            message = "{Pattern.SolicitacaoEntradaDTO.telefone}")
    private String telefone; 

    @Pattern(regexp = "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$",
    message = "{Pattern.SolicitacaoEntradaDTO.email}")
    private String email;

    @Pattern(regexp = "^(11|12|13|14|15|16|17|18|19|21|22|24|27|28|31|32|33|34|35|37|38|41|42|43|44|45|46|47|48|49|51|53|54|55|61|62|63|64|65|66|67|68|69|71|73|74|75|77|79|81|82|83|84|85|86|87|88|89|91|92|93|94|95|96|97|98|99)$",
            message = "{Pattern.SolicitacaoEntradaDTO.codDddCelular}")
    private String codDddCelular;

    @Pattern(regexp = "^(?![9]{9}|9[1]{8}|9[2]{8}|9[3]{8}|9[4]{8}|9[5]{8}|9[6]{8}|9[7]{8}|9[8]{8})([9][0-9]{8})$",
            message = "{Pattern.SolicitacaoEntradaDTO.numCelular}")
    private String numCelular;
	
	
}	

Validando elementos em uma lista:

Neste caso json de envio:

 [{
	"nome": null,
	"data" :"29/12/2019"
},{
	"nome": null,
	"data" :"29/12/2019"
}]

Usamos a anotação @Validated no controller

import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import java.util.List;

@Validated
@RestController
@RequestMapping(path = "/")
public class PessoaController implements PessoaResource {

    @PostMapping()
    public WebAsyncTask<Dto> consulta(@RequestBody @Valid List<Pojo> list) {
        return ...;
    }

}

No caso de usar uma interface no controller para mapear o swagger, a anotação @Valid também deve ser colocado nesta interface senão ele lança a seguinte mensagem de erro:

{
    "mensagem": "HV000151: A method overriding another method must not redefine the parameter constraint configuration, but method ConsultaSrController#consultaSr(List) redefines the configuration of ConsultaSrResource#consultaSr(List).",
    "detalhes": [],
    "data": "18/11/2019 11:22:50"
}

Segue a interface

@SuppressWarnings({"deprecation", "squid:CallToDeprecatedMethod"})
@Api(value = "Pessoa Resources", description = "Consulta Pessoas", tags = {"Endpoints"})
public interface PessoaResource {

    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "OK", response = Dto.class),
            @ApiResponse(code = 400, message = "Bad Request"),
            @ApiResponse(code = 404, message = "Not Found"),
            @ApiResponse(code = 500, message = "Internal Server Error")
    })
    @ApiOperation(value = "Consulta pessoas")
    WebAsyncTask<Sto> consulta(@Valid List<Pojo> list);
}

Validando lista dentro de um objeto

Essa é a forma classica e neste caso json de envio:

 "atributo": [{
	"nome": null,
	"data" :"29/12/2019"
},{
	"nome": null,
	"data" :"29/12/2019"
}]
@PostMapping(value = "/registrar/srs/digitadas", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
public ResponseEntity registrarSrsDigitadas(@Valid @RequestBody NumDTO numSrDTO) { ... }
@ItemProcessoOrderValid
public class NumDTO {

    @Valid
    private List<ItemProcessoDTO> itens;

}

Assim todos os elementos da lista serão validados.

Podemos criar uma anotação para validar a lista especificamente.

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ItemProcessoOrderValidator.class)
@Documented
public @interface ItemProcessoOrderValid {

    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Validando sequencia de itens

public class ItemProcessoOrderValidator implements ConstraintValidator<ItemProcessoOrderValid, NumDTO> {

    public static final String ITENS = "itens";
    public static final String VALIDATION_ITEM_PROCESSO_ORDER_VALID_MESSAGE = "{br.com.ractecnologia.validation.ItemProcessoOrderValid.message}";
    private ItemProcessoOrderValid constraintAnnotation;

    @Override
    public void initialize(ItemProcessoOrderValid constraintAnnotation) {

        this.constraintAnnotation = constraintAnnotation;
    }

    @Override
    public boolean isValid(NumDTO value, ConstraintValidatorContext context) {
        final List<ItemProcessoDTO> itens = value.getItens();
        final int size = itens.size();
        boolean valid = true;
        for (int index = 0; index < size; index++) {
            final String idtItem = itens.get(index).getIdtItem();
            if (isNull(idtItem) || Integer.parseInt(idtItem) != (index + 1)) {
                valid = false;

                context
                        .buildConstraintViolationWithTemplate(VALIDATION_ITEM_PROCESSO_ORDER_VALID_MESSAGE)
                        .addPropertyNode(ITENS).addConstraintViolation();

                break;
            }
        }
        return valid;
    }
}

As messages do BeanValiation ficam no arquivo ValidationMessages_pt_BR.properties, podemos externaliza-lo e redefinir as mensagens.

Tratamento de Erros

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
	public UserNotFoundException(String message) {
		super(message);
	}
}
@Getter
@AllArgsConstructor
public class ExceptionResponse {

	private  Date timestamp;

	private  String message;

	private String details;
}
@ControllerAdvice
@RestController

@RestControllerAdvice


```shell
@RestControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

	@ExceptionHandler(Exception.class)
	public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {

		ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
				request.getDescription(false));
		return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
	}


	@ExceptionHandler(UserNotFoundException.class)
	public final ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex, WebRequest request) {
		ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
				request.getDescription(false));
		return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
	}

	@Override
	protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
		ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "Erro na validação",
				ex.getMessage());
		return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST);
	}


	@Override
	protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
																  HttpHeaders headers, HttpStatus status, WebRequest request) {
		ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "Validation Failed",
				ex.getBindingResult().toString());
		return new ResponseEntity(exceptionResponse, HttpStatus.BAD_REQUEST);
	}

	@Override
	protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

		ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
				request.getDescription(false));
		return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
	}
}

Servidores

A starter spring-boot-starter-web usa o tomcat como servidor base.

Jetty

 <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
          <exclusion>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-tomcat</artifactId>
          </exclusion>
      </exclusions>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jetty</artifactId>
  </dependency>

JBoss

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

Spring Boot com JSP

Maven

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-tomcat</artifactId>
	<scope>provided</scope>
</dependency>

<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>jstl</artifactId>
</dependency>

<dependency>
	<groupId>org.apache.tomcat.embed</groupId>
	<artifactId>tomcat-embed-jasper</artifactId>
	<scope>provided</scope>
</dependency>

Configuração

src/main/resources/application.properties
 
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

Views

src/main/resources/META-INF/resources/WEB-INF/jsp/index.jsp

Spring Boot com JSP projeto war

Maven

Altere o tipo do projeto
<packaging>war</packaging>

As mesmas dependencias

Configuração

Mesmo do anterior

Views

src/main/webapp/WEB-INF/jsp/index.jsp

Thymeleaf

 <dependency>
     <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

@Controller
public class HomeController {

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

Coloque a view 

src/main/resources/template/index.html

Coloque arquivos estatico js, css e images

src/main/resources/static/bootstrap/...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment