<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.
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;
}
@PostMapping("/users")
public void createUser(@RequestBody User user){
User savedUser = service.save(user);
}
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){
...
}
@GetMapping(value = "/ex/header1")
public String getFoosAsJsonFromBrowser(@RequestHeader HttpHeaders headers) {
return headers.get("Accept").toString();
}
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) {
...
}
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.
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
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": ""
}
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;
}
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);
}
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.
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);
}
}
A starter spring-boot-starter-web
usa o tomcat como servidor base.
<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>
<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>
<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>
src/main/resources/application.properties
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
src/main/resources/META-INF/resources/WEB-INF/jsp/index.jsp
Altere o tipo do projeto
<packaging>war</packaging>
As mesmas dependencias
Mesmo do anterior
src/main/webapp/WEB-INF/jsp/index.jsp
<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/...