Saudação, Khabrovsk. Assim, a tradução da segunda parte de um artigo preparado especialmente para os alunos do curso "Developer on the Spring Framework" chegou a tempo. A primeira parte pode ser lida aqui .
A primavera é talvez uma das plataformas de desenvolvimento Java mais populares. Esta é uma ferramenta poderosa, mas bastante difícil de aprender. Seus conceitos básicos são bastante fáceis de entender e aprender, mas são necessários tempo e esforço para se tornar um desenvolvedor experiente do Spring.
Neste artigo, examinaremos alguns dos erros mais comuns cometidos ao trabalhar no Spring e relacionados, em particular, ao desenvolvimento de aplicativos da Web e ao uso da plataforma Spring Boot. Conforme observado no site do Spring Boot , o Spring Boot adota uma abordagem padronizada para criar aplicativos prontos para uso, e este artigo seguirá essa abordagem. Ele fornecerá várias recomendações que podem ser efetivamente usadas no desenvolvimento de aplicativos Web padrão baseados no Spring Boot.
Caso você não esteja familiarizado com a plataforma Spring Boot, mas queira experimentar os exemplos deste artigo, criei um repositório GitHub com materiais adicionais para este artigo . Se, em algum momento, você estiver um pouco confuso ao ler este artigo, recomendamos que você crie um clone deste repositório e experimente o código no seu computador.
Erro comum nº 6: não usando validação de dados baseada em anotação
Vamos imaginar que nosso serviço TopTalent dos exemplos anteriores precise de um terminal para adicionar novos dados TopTalent. Além disso, vamos supor que, por algum motivo realmente importante, cada nome que você adicionar deve ter exatamente 10 caracteres. Isso pode ser implementado, por exemplo, da seguinte maneira:
@RequestMapping("/put") public void addTopTalent(@RequestBody TopTalentData topTalentData) { boolean nameNonExistentOrHasInvalidLength = Optional.ofNullable(topTalentData) .map(TopTalentData::getName) .map(name -> name.length() == 10) .orElse(true); if (nameNonExistentOrInvalidLength) {
No entanto, o código acima não é apenas mal estruturado, mas não é realmente uma solução "limpa".
TopTalentData
vários tipos de validação de dados (ou seja, verificamos que o objeto
TopTalentData
não
TopTalentData
nulo, que o valor do campo TopTalentData.name não é nulo e que o comprimento do campo
TopTalentData.name
é de 10 caracteres) e também
TopTalentData.name
uma exceção se os dados estiverem incorretos.
Tudo isso pode ser feito com mais precisão usando o
validador Hibernate no Spring. Vamos primeiro reescrever o método
addTopTalent
, adicionando suporte para validação de dados:
@RequestMapping("/put") public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) { topTalentService.addTopTalent(topTalentData); } @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) {
Além disso, precisamos indicar qual validação de propriedade queremos executar na classe
TopTalentData
:
public class TopTalentData { @Length(min = 10, max = 10) @NotNull private String name; }
O Spring agora interceptará a solicitação e a verificará antes de chamar o método, portanto, nenhuma verificação manual adicional será necessária.
O objetivo desejado também pode ser alcançado criando suas próprias anotações. Sob condições reais, geralmente faz sentido usar suas próprias anotações somente quando suas necessidades excederem os recursos do
conjunto interno de restrições de hibernação , mas, neste exemplo, vamos imaginar que
@Length
anotações
@Length
não existem. Você pode criar um validador de dados que verifique o comprimento de uma sequência criando duas classes adicionais: uma para validação e outra para propriedades de anotação:
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = { MyAnnotationValidator.class }) public @interface MyAnnotation { String message() default "String length does not match expected"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int value(); } @Component public class MyAnnotationValidator implements ConstraintValidator<MyAnnotation, String> { private int expectedLength; @Override public void initialize(MyAnnotation myAnnotation) { this.expectedLength = myAnnotation.value(); } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { return s == null || s.length() == this.expectedLength; } }
Observe que, nesses casos, a aplicação correta do princípio da separação de responsabilidades exige a marcação da propriedade como válida se seu valor for nulo
(s == null
no método
isValid
) e, em seguida, use a anotação
@NotNull
se for necessário adicionalmente para esta propriedade:
public class TopTalentData { @MyAnnotation(value = 10) @NotNull private String name; }
Erro comum nº 7: usando configurações herdadas baseadas em XML
O uso de XML era uma necessidade ao trabalhar com versões anteriores do Spring, mas agora a maioria das tarefas de configuração pode ser implementada usando código e anotações Java. As configurações XML agora agem como código de modelo adicional e opcional.
Neste artigo (assim como nos materiais do repositório GitHub complementar), as anotações são usadas para configurar o Spring, e o Spring sabe quais componentes JavaBean precisam ser vinculados, já que o pacote raiz é anotado usando a anotação composta @SpringBootApplication - assim:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Essa anotação composta (consulte a
documentação do
Spring para obter mais informações) simplesmente informa à plataforma Spring quais pacotes serão verificados para extrair os componentes JavaBean. No nosso caso particular, isso significa que os seguintes subpacotes de co.kukurin serão usados para ligação:
- Componente (TopTalentConverter, MyAnnotationValidator)
- @RestController (TopTalentController)
- Repositório (TopTalentRepository)
- Classes Service (TopTalentService)
Se tivermos classes adicionais com a anotação
@Configuration
, elas também serão verificadas quanto à configuração do Java.
Erro comum nº 8: não usando perfis de configuração
Ao desenvolver sistemas de servidor, um problema comum é alternar entre configurações diferentes (como regra, essas são configurações para os ambientes operacionais e de desenvolvimento). Em vez de alterar manualmente vários parâmetros em cada transição entre os modos de teste e operação, é mais eficiente usar perfis de configuração.
Imagine o caso em que, no ambiente de desenvolvimento local, você usa o banco de dados na RAM e, no ambiente da operação real do seu aplicativo, o banco de dados MySQL é usado. Isso significa essencialmente que você usará URLs diferentes e, presumivelmente, credenciais diferentes para acessar cada um desses bancos de dados. Vamos ver como isso pode ser implementado usando dois arquivos de configuração:
FILE APPLICATION.YAML # set default profile to 'dev' spring.profiles.active: dev # production database details spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal' spring.datasource.username: root spring.datasource.password:
FILE APPLICATION-DEV.YAML spring.datasource.url: 'jdbc:h2:mem:' spring.datasource.platform: h2
Deve-se supor que, ao trabalhar com código, você não deseje executar algumas ações aleatórias com um banco de dados destinado ao ambiente operacional; portanto, faz sentido selecionar o perfil para o ambiente de desenvolvimento (DEV) como o perfil padrão. Posteriormente, você pode substituir manualmente o perfil de configuração no servidor especificando
-Dspring.profiles.active=prod
para a JVM. Além disso, o perfil de configuração padrão pode ser especificado na variável de ambiente do sistema operacional.
Erro comum número 9. Falha no uso do mecanismo de injeção de dependência
O uso adequado do mecanismo de injeção de dependência no Spring significa que o Spring pode vincular todos os seus objetos, varrendo todas as classes de configuração necessárias. Isso é útil para diminuir as interdependências e facilita muito os testes. Em vez de vincular fortemente classes, algo como isto:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController() { this.topTalentService = new TopTalentService(); } }
... permitimos que a plataforma Spring se vincule:
public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController(TopTalentService topTalentService) { this.topTalentService = topTalentService; } }
A palestra de Mishko Hevery no canal Google Tech Talks explica em detalhes por que a injeção de dependência deve ser usada, mas aqui veremos como esse mecanismo é usado na prática. Na divisão de responsabilidades (“Erro comum nº 3”), criamos as classes de serviço e controlador. Suponha que desejamos testar um controlador, assumindo que a classe
TopTalentService
funcionando corretamente. Podemos inserir um objeto simulador em vez da implementação de serviço atual, criando uma classe de configuração separada:
@Configuration public class SampleUnitTestConfig { @Bean public TopTalentService topTalentService() { TopTalentService topTalentService = Mockito.mock(TopTalentService.class); Mockito.when(topTalentService.getTopTalent()).thenReturn( Stream.of("Mary", "Joel").map(TopTalentData::new).collect(Collectors.toList())); return topTalentService; } }
Depois disso, podemos implementar o objeto simulador,
SampleUnitTestConfig
plataforma Spring que precisamos usar
SampleUnitTestConfig
como a fonte de configuração:
@ContextConfiguration(classes = { SampleUnitTestConfig.class })
Posteriormente, isso nos permitirá usar a configuração contextual para incorporar o componente JavaBean personalizado em um teste de unidade.
Erro comum número 10. Falta de teste ou teste incorreto
Apesar do fato de que a idéia de teste de unidade não é de forma alguma nova, parece que muitos desenvolvedores "esquecem" (especialmente se não for
obrigatório ) ou gastam tarde demais. Obviamente, isso está errado, porque os testes não apenas permitem verificar a operação correta do código, mas também servem como documentação que mostra como o aplicativo deve se comportar em várias situações.
Ao testar serviços da Web, você raramente executa testes de unidade excepcionalmente "limpos", porque para uma conexão HTTP, geralmente é necessário usar o servlet Spring
DispatcherServlet
e ver o que acontece quando você recebe uma solicitação
HttpServletRequest
real (ou seja, um teste de
integração que usa validação, serialização etc.). Uma solução elegante e comprovada é usar o
REST Assured , uma biblioteca Java para testar convenientemente os serviços REST, com o MockMVC. Considere o seguinte fragmento de código com injeção de dependência:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { Application.class, SampleUnitTestConfig.class }) public class RestAssuredTestDemonstration { @Autowired private TopTalentController topTalentController; @Test public void shouldGetMaryAndJoel() throws Exception {
SampleUnitTestConfig
vincula a implementação substituta da classe
TopTalentService
ao
TopTalentController
, e todas as outras classes são vinculadas usando a configuração padrão obtida pela verificação de pacotes com base no pacote da classe Application.
RestAssuredMockMvc
simplesmente usado para configurar um ambiente leve e enviar uma solicitação
GET
para o
/toptal/get
.
Use o Spring profissionalmente
O Spring é uma plataforma poderosa e fácil de começar, mas leva tempo e algum esforço para dominá-lo completamente. Se você dedicar algum tempo para se familiarizar com essa plataforma, no final, ela sem dúvida aumentará a eficiência do seu trabalho, ajudará a criar um código mais limpo e a aumentar suas qualificações como desenvolvedor.
Eu recomendo que você preste atenção ao
Spring In Action - este é um bom livro orientado a aplicativos que discute muitos tópicos importantes relacionados à plataforma Spring.
Nesse ponto, a tradução deste artigo chegou ao fim.
Leia a primeira parte .