Tutorial de inicialização do Spring - Bootstrap uma aplicação simples
1. Visão geral
O Spring Boot é uma adição opinativa e focada na convenção sobre configuração à plataforma Spring - altamente útil para começar com o mínimo esforço e criar aplicativos independentes e com nível de produção.
This tutorial is a starting point for Boot - uma maneira de começar de maneira simples, com um aplicativo web básico.
Veremos algumas configurações básicas, um front-end, manipulação rápida de dados e tratamento de exceções.
Leitura adicional:
Como alterar a porta padrão no Spring Boot
Veja como você pode alterar a porta padrão em um aplicativo Spring Boot.
Introdução aos Spring Boot Starters
Uma rápida visão geral dos Spring Boot Starters mais comuns, além de exemplos de como usá-los em um projeto do mundo real.
2. Configuração
Primeiro, vamos usarSpring Initializr para gerar a base para nosso projeto.
O projeto gerado depende do pai de inicialização:
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
As dependências iniciais serão bastante simples:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
com.h2database
h2
3. Configuração da Aplicação
A seguir, vamos configurar uma classemain simples para nosso aplicativo:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Observe como estamos usando@SpringBootApplication como nossa classe de configuração de aplicativo principal; nos bastidores, isso é equivalente a@Configuration,@EnableAutoConfiguration e@ComponentScan juntos.
Por fim, definiremos um arquivoapplication.properties simples - que por enquanto tem apenas uma propriedade:
server.port=8081
server.port altera a porta do servidor do padrão 8080 para 8081; é claro que existem muitos maisSpring Boot properties available.
4. Visualização MVC Simples
Vamos agora adicionar um front end simples usando Thymeleaf.
Primeiro, precisamos adicionar a dependênciaspring-boot-starter-thymeleaf ao nossopom.xml:
org.springframework.boot
spring-boot-starter-thymeleaf
Isso habilita o Thymeleaf por padrão - nenhuma configuração extra é necessária.
Agora podemos configurá-lo em nossoapplication.properties:
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.application.name=Bootstrap Spring Boot
A seguir, definiremos um controlador simples e uma página inicial básica - com uma mensagem de boas-vindas:
@Controller
public class SimpleController {
@Value("${spring.application.name}")
String appName;
@GetMapping("/")
public String homePage(Model model) {
model.addAttribute("appName", appName);
return "home";
}
}
Finalmente, aqui está nossohome.html:
Home Page
Hello !
Welcome to Our App
Observe como usamos uma propriedade que definimos em nossas propriedades - e depois a injetamos para que possamos mostrá-la em nossa página inicial.
5. Segurança
A seguir, vamos adicionar segurança ao nosso aplicativo - primeiro incluindo o iniciador de segurança:
org.springframework.boot
spring-boot-starter-security
Agora, você deve estar notando um padrão -most Spring libraries are easily imported into our project with the use of simple Boot starters.
Uma vez que a dependência despring-boot-starter-security no caminho de classe do aplicativo - todos os endpoints são protegidos por padrão, usandohttpBasic ouformLogin com base na estratégia de negociação de conteúdo do Spring Security.
É por isso que, se tivermos o iniciador no caminho de classe, devemos geralmente definir nossa própria configuração de segurança personalizada estendendo a classeWebSecurityConfigurerAdapter:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.permitAll()
.and().csrf().disable();
}
}
Em nosso exemplo, estamos permitindo acesso irrestrito a todos os terminais.
Claro, Spring Security é um tópico extenso e não é facilmente abordado em algumas linhas de configuração - então, eu definitivamente encorajo você ago deeper into the topic.
6. Persistência Simples
Vamos começar definindo nosso modelo de dados - uma entidadeBook simples:
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(nullable = false, unique = true)
private String title;
@Column(nullable = false)
private String author;
}
E seu repositório, fazendo bom uso dos Spring Data aqui:
public interface BookRepository extends CrudRepository {
List findByTitle(String title);
}
Por fim, é claro que precisamos configurar nossa nova camada de persistência:
@EnableJpaRepositories("org.example.persistence.repo")
@EntityScan("org.example.persistence.model")
@SpringBootApplication
public class Application {
...
}
Observe que estamos usando:
-
@EnableJpaRepositories para escanear o pacote especificado para repositórios
-
@EntityScan para pegar nossas entidades JPA
Para manter as coisas simples, estamos usando um banco de dados H2 em memória aqui - para que não tenhamos dependências externas quando executamos o projeto.
Uma vez que incluímos a dependência H2,Spring Boot auto-detects it and sets up our persistence sem necessidade de configuração extra, a não ser as propriedades da fonte de dados:
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:bootapp;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
Claro, como segurança, persistência é um tópico mais amplo do que este conjunto básico aqui, e você devecertainly explore further.
7. Web e o controlador
A seguir, vamos dar uma olhada em uma camada da web - e vamos começar configurando um controlador simples - oBookController.
Implementaremos operações CRUD básicas expondo recursosBook com algumas validações simples:
@RestController
@RequestMapping("/api/books")
public class BookController {
@Autowired
private BookRepository bookRepository;
@GetMapping
public Iterable findAll() {
return bookRepository.findAll();
}
@GetMapping("/title/{bookTitle}")
public List findByTitle(@PathVariable String bookTitle) {
return bookRepository.findByTitle(bookTitle);
}
@GetMapping("/{id}")
public Book findOne(@PathVariable Long id) {
return bookRepository.findById(id)
.orElseThrow(BookNotFoundException::new);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Book create(@RequestBody Book book) {
return bookRepository.save(book);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
bookRepository.findById(id)
.orElseThrow(BookNotFoundException::new);
bookRepository.deleteById(id);
}
@PutMapping("/{id}")
public Book updateBook(@RequestBody Book book, @PathVariable Long id) {
if (book.getId() != id) {
throw new BookIdMismatchException();
}
bookRepository.findById(id)
.orElseThrow(BookNotFoundException::new);
return bookRepository.save(book);
}
}
Dado que este aspecto do aplicativo é uma API, usamos a anotação @RestController aqui - que equivale a@Controller junto com@ResponseBody - de modo que cada método empacote o recurso retornado corretamente para a resposta HTTP.
Apenas uma observação que vale a pena destacar - estamos expondo nossa entidadeBook como nosso recurso externo aqui. Isso é bom para nosso aplicativo simples aqui, mas em um aplicativo do mundo real, você provavelmente desejaráseparate these two concepts.
8. Manipulação de erros
Agora que o aplicativo principal está pronto para funcionar, vamos nos concentrar ema simple centralized error handling mechanism usando@ControllerAdvice:
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({ BookNotFoundException.class })
protected ResponseEntity
Além das exceções padrão que estamos tratando aqui, também estamos usando uma exceção personalizada:
BookNotFoundException:
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException(String message, Throwable cause) {
super(message, cause);
}
// ...
}
Isso deve dar uma ideia do que é possível com este mecanismo de tratamento de exceção global. Se você gostaria de ver uma implementação completa, dê uma olhada emthe in-depth tutorial.
Observe que Spring Boot também fornece um mapeamento/error por padrão. Podemos personalizar sua visualização criando umerror.html simples:
Error Occurred
Error Occurred!
[status]
error
message
Como a maioria dos outros aspectos do Boot, podemos controlar isso com uma propriedade simples:
server.error.path=/error2
9. Teste
Por fim, vamos testar nossa nova API de livros.
Usaremos@SpringBootTest imediatamente para carregar o contexto do aplicativo:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { Application.class }, webEnvironment
= WebEnvironment.DEFINED_PORT)
public class SpringBootBootstrapLiveTest {
private static final String API_ROOT
= "http://localhost:8081/api/books";
private Book createRandomBook() {
Book book = new Book();
book.setTitle(randomAlphabetic(10));
book.setAuthor(randomAlphabetic(15));
return book;
}
private String createBookAsUri(Book book) {
Response response = RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(book)
.post(API_ROOT);
return API_ROOT + "/" + response.jsonPath().get("id");
}
}
Primeiro, podemos tentar encontrar livros usando métodos variantes:
@Test
public void whenGetAllBooks_thenOK() {
Response response = RestAssured.get(API_ROOT);
assertEquals(HttpStatus.OK.value(), response.getStatusCode());
}
@Test
public void whenGetBooksByTitle_thenOK() {
Book book = createRandomBook();
createBookAsUri(book);
Response response = RestAssured.get(
API_ROOT + "/title/" + book.getTitle());
assertEquals(HttpStatus.OK.value(), response.getStatusCode());
assertTrue(response.as(List.class)
.size() > 0);
}
@Test
public void whenGetCreatedBookById_thenOK() {
Book book = createRandomBook();
String location = createBookAsUri(book);
Response response = RestAssured.get(location);
assertEquals(HttpStatus.OK.value(), response.getStatusCode());
assertEquals(book.getTitle(), response.jsonPath()
.get("title"));
}
@Test
public void whenGetNotExistBookById_thenNotFound() {
Response response = RestAssured.get(API_ROOT + "/" + randomNumeric(4));
assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
}
A seguir, testaremos a criação de um novo livro:
@Test
public void whenCreateNewBook_thenCreated() {
Book book = createRandomBook();
Response response = RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(book)
.post(API_ROOT);
assertEquals(HttpStatus.CREATED.value(), response.getStatusCode());
}
@Test
public void whenInvalidBook_thenError() {
Book book = createRandomBook();
book.setAuthor(null);
Response response = RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(book)
.post(API_ROOT);
assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode());
}
Atualize um livro existente:
@Test
public void whenUpdateCreatedBook_thenUpdated() {
Book book = createRandomBook();
String location = createBookAsUri(book);
book.setId(Long.parseLong(location.split("api/books/")[1]));
book.setAuthor("newAuthor");
Response response = RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(book)
.put(location);
assertEquals(HttpStatus.OK.value(), response.getStatusCode());
response = RestAssured.get(location);
assertEquals(HttpStatus.OK.value(), response.getStatusCode());
assertEquals("newAuthor", response.jsonPath()
.get("author"));
}
E exclua um livro:
@Test
public void whenDeleteCreatedBook_thenOk() {
Book book = createRandomBook();
String location = createBookAsUri(book);
Response response = RestAssured.delete(location);
assertEquals(HttpStatus.OK.value(), response.getStatusCode());
response = RestAssured.get(location);
assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
}
10. Conclusão
Esta foi uma introdução rápida, mas abrangente, ao Spring Boot.
É claro que mal tocamos a superfície aqui - há muito mais nesta estrutura que podemos cobrir em um único artigo de introdução.
É exatamente por isso quewe don’t just have a single article about Boot on the site.
O código-fonte completo de nossos exemplos aqui é, como sempre,over on GitHub.