Crie uma API REST com Spring e Java Config
1. Visão geral
Este artigo mostra comoset up REST in Spring - os códigos de resposta do controlador e HTTP, configuração do empacotamento de carga útil e negociação de conteúdo.
Leitura adicional:
Usando Spring @ResponseStatus para definir o código de status HTTP
Dê uma olhada na anotação @ResponseStatus e como usá-la para definir o código de status da resposta.
As anotações Spring @Controller e @RestController
Aprenda sobre as diferenças entre as anotações @Controller e @RestController no Spring MVC.
2. Compreendendo o REST na primavera
A estrutura do Spring suporta duas maneiras de criar serviços RESTful:
-
usando MVC comModelAndView
-
usando conversores de mensagens HTTP
A abordagemModelAndView é mais antiga e muito melhor documentada, mas também mais detalhada e com configuração pesada. Ele tenta calçar o paradigma REST no modelo antigo, o que não ocorre sem problemas. A equipe do Spring entendeu isso e forneceu suporte REST de primeira classe a partir do Spring 3.0.
The new approach, based on HttpMessageConverter and annotations, is much more lightweight and easy to implement. A configuração é mínima e fornece padrões razoáveis para o que você esperaria de um serviço RESTful.
3. A configuração Java
@Configuration
@EnableWebMvc
public class WebConfig{
//
}
A nova anotação@EnableWebMvc faz algumas coisas úteis - especificamente, no caso de REST, ela detecta a existência de Jackson e JAXB 2 no classpath e cria e registra automaticamente conversores JSON e XML padrão. A funcionalidade da anotação é equivalente à versão XML:
Este é um atalho e, embora possa ser útil em muitas situações, não é perfeito. Quando uma configuração mais complexa for necessária, remova a anotação e estendaWebMvcConfigurationSupport diretamente.
3.1. Usando o Spring Boot
Se estivermos usando a anotação@SpringBootApplication e a bibliotecaspring-webmvc estiver no caminho de classe, a anotação@EnableWebMvc será adicionada automaticamente coma default autoconfiguration.
Ainda podemos adicionar a funcionalidade MVC a essa configuração implementando a interfaceWebMvcConfigurer em uma classe com anotação@Configuration . Também podemos usar uma instânciaWebMvcRegistrationsAdapter para fornecer nossas próprias simplificaçõesRequestMappingHandlerMapping,RequestMappingHandlerAdapter ouExceptionHandlerExceptionResolver .
Finalmente, se quisermos descartar os recursos MVC do Spring Boot e declarar uma configuração personalizada, podemos fazer isso usando a anotação@EnableWebMvc.
4. Testando o Contexto Spring
A partir do Spring 3.1, temos suporte de teste de primeira classe para as classes@Configuration:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = {WebConfig.class, PersistenceConfig.class},
loader = AnnotationConfigContextLoader.class)
public class SpringContextIntegrationTest {
@Test
public void contextLoads(){
// When
}
}
Estamos especificando as classes de configuração Java com a anotação@ContextConfiguration. O novoAnnotationConfigContextLoader carrega as definições de bean das classes@Configuration.
Observe que a classe de configuraçãoWebConfig não foi incluída no teste porque precisa ser executada em um contexto de Servlet, que não é fornecido.
4.1. Usando o Spring Boot
O Spring Boot fornece várias anotações para configurar o SpringApplicationContext para nossos testes de uma forma mais intuitiva.
Podemos carregar apenas uma fatia específica da configuração do aplicativo ou simular todo o processo de inicialização do contexto.
Por exemplo, podemos usar a anotação@SpringBootTest se quisermos que todo o contexto seja criado sem iniciar o servidor.
Com isso no lugar, podemos adicionar@AutoConfigureMockMvc para injetar uma instânciaMockMvc e enviar solicitações HTTP:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class FooControllerAppIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenTestApp_thenEmptyResponse() throws Exception {
this.mockMvc.perform(get("/foos")
.andExpect(status().isOk())
.andExpect(...);
}
}
Para evitar a criação de todo o contexto e testar apenas nossos controladores MVC, podemos usar@WebMvcTest:
@RunWith(SpringRunner.class)
@WebMvcTest(FooController.class)
public class FooControllerWebLayerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private IFooService service;
@Test()
public void whenTestMvcController_thenRetrieveExpectedResult() throws Exception {
// ...
this.mockMvc.perform(get("/foos")
.andExpect(...);
}
}
Podemos encontrar informações detalhadas sobre este assunto emour ‘Testing in Spring Boot' article.
5. O controlador
The @RestController is the central artifact in the entire Web Tier of the RESTful API. Para o propósito desta postagem, o controlador está modelando um recurso REST simples -Foo:
@RestController
@RequestMapping("/foos")
class FooController {
@Autowired
private IFooService service;
@GetMapping
public List findAll() {
return service.findAll();
}
@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id) {
return RestPreconditions.checkFound(service.findById(id));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Long create(@RequestBody Foo resource) {
Preconditions.checkNotNull(resource);
return service.create(resource);
}
@PutMapping(value = "/{id}")
@ResponseStatus(HttpStatus.OK)
public void update(@PathVariable( "id" ) Long id, @RequestBody Foo resource) {
Preconditions.checkNotNull(resource);
RestPreconditions.checkNotNull(service.getById(resource.getId()));
service.update(resource);
}
@DeleteMapping(value = "/{id}")
@ResponseStatus(HttpStatus.OK)
public void delete(@PathVariable("id") Long id) {
service.deleteById(id);
}
}
Você deve ter notado que estou usando um utilitário simples, estilo GuavaRestPreconditions:
public class RestPreconditions {
public static T checkFound(T resource) {
if (resource == null) {
throw new MyResourceNotFoundException();
}
return resource;
}
}
A implementação do Controlador não é pública - isso porque não precisa ser.
Geralmente, o controlador é o último na cadeia de dependências. Ele recebe solicitações HTTP do controlador frontal do Spring (DispatcherServlet) e simplesmente as delega para uma camada de serviço. Se não houver nenhum caso de uso em que o controlador precise ser injetado ou manipulado por meio de uma referência direta, prefiro não declará-lo como público.
Os mapeamentos de solicitação são diretos. As with any controller, the actual value of the mapping, as well as the HTTP method, determine the target method for the request. @RequestBody vinculará os parâmetros do método ao corpo da solicitação HTTP, enquanto@ResponseBody fará o mesmo para a resposta e o tipo de retorno.
O@RestController é umshorthand para incluir as anotações@ResponseBody e @Controller em nossa classe.
Eles também garantem que o recurso seja empacotado e não empacotado usando o conversor HTTP correto. A negociação de conteúdo ocorrerá para escolher qual dos conversores ativos será usado, com base principalmente no cabeçalhoAccept, embora outros cabeçalhos HTTP também possam ser usados para determinar a representação.
6. Mapeando os códigos de resposta HTTP
Os códigos de status da resposta HTTP são uma das partes mais importantes do serviço REST e o assunto pode rapidamente se tornar muito complicado. Acertar isso pode ser o que faz ou interrompe o serviço.
6.1. Pedidos não mapeados
Se Spring MVC receber uma solicitação que não tem um mapeamento, ele considera a solicitação como não permitida e retorna um 405 METHOD NOT ALLOWED de volta para o cliente.
Também é uma boa prática incluir o cabeçalho HTTPAllow ao retornar405 ao cliente, para especificar quais operações são permitidas. Este é o comportamento padrão do Spring MVC e não requer nenhuma configuração adicional.
6.2. Solicitações mapeadas válidas
Para qualquer solicitação que tenha um mapeamento, o Spring MVC considera a solicitação válida e responde com 200 OK se nenhum outro código de status for especificado.
É por isso que o controlador declara@ResponseStatus diferentes para as açõescreate,updateedelete, mas não paraget, que deve realmente retornar o padrão 200 ESTÁ BEM.
6.3. Erro de cliente
No caso de um erro do cliente, exceções personalizadas são definidas e mapeadas para os códigos de erro apropriados.
Simplesmente lançar essas exceções em qualquer uma das camadas da camada da web garantirá que o Spring mapeie o código de status correspondente na resposta HTTP:
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
//
}
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
//
}
Essas exceções fazem parte da API REST e, como tal, devem ser usadas apenas nas camadas apropriadas correspondentes ao REST; se, por exemplo, existir uma camada DAO / DAL, ela não deve usar as exceções diretamente.
Observe também que essas não são exceções verificadas, mas exceções de tempo de execução - de acordo com as práticas e idiomas do Spring.
6.4. Usando@ExceptionHandler
Outra opção para mapear exceções personalizadas em códigos de status específicos é usar a anotação@ExceptionHandler no controlador. O problema com essa abordagem é que a anotação só se aplica ao controlador no qual está definida. Isso significa que precisamos declarar em cada controlador individualmente.
Claro, existem maisways to handle errors tanto no Spring quanto no Spring Boot que oferecem mais flexibilidade.
7. Additional Maven Dependencies **
Além da dependênciaspring-webmvcrequired for the standard web application, precisamos configurar o empacotamento e descompactação de conteúdo para a API REST:
com.fasterxml.jackson.core
jackson-databind
2.9.8
javax.xml.bind
jaxb-api
2.3.1
runtime
Essas são as bibliotecas usadas para converter a representação do recurso REST em JSON ou XML.
7.1. Usando o Spring Boot
Se queremos recuperar recursos formatados em JSON, o Spring Boot fornece suporte para diferentes bibliotecas, como Jackson, Gson e JSON-B.
A configuração automática é realizada apenas incluindo qualquer uma das bibliotecas de mapeamento no caminho de classe.
Normalmente, se estamos desenvolvendo um aplicativo da web,we’ll just add the spring-boot-starter-web dependency and rely on it to include all the necessary artifacts to our project:
org.springframework.boot
spring-boot-starter-web
2.1.2.RELEASE
O Spring Boot usa Jackson por padrão.
Se quisermos serializar nossos recursos em um formato XML, teremos que adicionar a extensão Jackson XML (jackson-dataformat-xml) às nossas dependências, ou fallback para a implementação JAXB (fornecida por padrão no JDK) usando o Anotação@XmlRootElement em nosso recurso.
8. Conclusão
Este tutorial ilustrou como implementar e configurar um Serviço REST usando a configuração baseada em Spring e Java.
Nos próximos artigos da série, vou me concentrar emDiscoverability of the API, negociação de conteúdo avançado e trabalhar com representações adicionais de aResource.
Todo o código deste artigo está disponívelover on Github. Este é um projeto baseado em Maven, portanto, deve ser fácil importar e executar como está.