Рубрики
Без рубрики

Spring Boot Tutorial – Bootstrap простое приложение

Вот как вы начинаете понимать Spring Boot.

Автор оригинала: baeldung.

1. Обзор

Spring Boot-это самоуверенное, ориентированное на конфигурацию дополнение к платформе Spring, которое очень полезно для начала работы с минимальными усилиями и создания автономных приложений производственного уровня.

Этот учебник является отправной точкой для загрузки – способ начать работу простым способом, с помощью базового веб-приложения.

Мы рассмотрим некоторые основные конфигурации, интерфейс, быструю обработку данных и обработку исключений.

Дальнейшее чтение:

Как изменить порт по умолчанию в Spring Boot

Вступление к стартерам весенней загрузки

2. Настройка

Во-первых, давайте использовать Spring Initializr для создания базы для нашего проекта.

Созданный проект зависит от родительского загрузчика:


    org.springframework.boot
    spring-boot-starter-parent
    2.4.0
    

Начальные зависимости будут довольно простыми:


    org.springframework.boot
    spring-boot-starter-web


    org.springframework.boot
    spring-boot-starter-data-jpa


    com.h2database
    h2

3. Конфигурация приложения

Далее мы настроим простой класс main для нашего приложения:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Обратите внимание, как мы используем @SpringBootApplication в качестве основного класса конфигурации приложения; за кулисами это эквивалентно @Configuration , @EnableAutoConfiguration и @ComponentScan вместе.

Наконец, мы определим простой файл application.properties , который на данный момент имеет только одно свойство:

server.port=8081

server.port изменяет порт сервера с 8080 по умолчанию на 8081; конечно, доступно еще много свойств загрузки Spring .

4. Простое представление MVC

Теперь давайте добавим простой интерфейс, используя Thymeleaf.

Во-первых, нам нужно добавить зависимость spring-boot-starter-thymeleaf в ваш pom.xml :

 
    org.springframework.boot 
    spring-boot-starter-thymeleaf 

Это включает Thymeleaf по умолчанию – дополнительная настройка не требуется.

Теперь мы можем настроить его в нашем application.properties :

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true 
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

spring.application.name=Bootstrap Spring Boot

Далее мы определим простой контроллер и базовую домашнюю страницу – с приветственным сообщением:

@Controller
public class SimpleController {
    @Value("${spring.application.name}")
    String appName;

    @GetMapping("/")
    public String homePage(Model model) {
        model.addAttribute("appName", appName);
        return "home";
    }
}

Наконец, вот наш home.html :


Home Page

Hello !

Welcome to Our App

Обратите внимание, как мы использовали свойство, которое мы определили в наших свойствах, а затем ввели его, чтобы мы могли показать его на нашей домашней странице.

5. Безопасность

Далее, давайте добавим безопасность в наше приложение – сначала включив стартер безопасности:

 
    org.springframework.boot 
    spring-boot-starter-security 

К настоящему времени вы, надеюсь, заметили закономерность – большинство библиотек Spring легко импортируются в наш проект с помощью простых загрузочных стартеров .

После того, как spring-boot-starter-security зависимость от пути к классу приложения – все конечные точки защищены по умолчанию, используя либо httpBasic , либо formLogin на основе стратегии согласования содержимого Spring Security.

Вот почему, если у нас есть стартер на пути к классу, мы обычно должны определить нашу собственную пользовательскую конфигурацию безопасности, расширив класс WebSecurityConfigurerAdapter :

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .permitAll()
            .and().csrf().disable();
    }
}

В нашем примере мы разрешаем неограниченный доступ ко всем конечным точкам.

Конечно, весенняя безопасность – это обширная тема, и ее нелегко охватить в нескольких строках конфигурации, поэтому я определенно призываю вас углубиться в эту тему .

6. Простая Настойчивость

Давайте начнем с определения нашей модели данных – простой Книги сущности:

@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;
}

И его репозиторий, хорошо используя данные Spring здесь:

public interface BookRepository extends CrudRepository {
    List findByTitle(String title);
}

Наконец, нам, конечно, нужно настроить наш новый уровень персистентности:

@EnableJpaRepositories("com.baeldung.persistence.repo") 
@EntityScan("com.baeldung.persistence.model")
@SpringBootApplication 
public class Application {
   ...
}

Обратите внимание, что мы используем:

  • @EnableJpaRepositories для сканирования указанного пакета на наличие репозиториев
  • @EntityScan чтобы забрать наши объекты JPA

Чтобы все было просто, мы используем здесь базу данных H2 в памяти, чтобы при запуске проекта у нас не было никаких внешних зависимостей.

Как только мы включим зависимость H2, Spring Boot автоматически обнаружит ее и настроит нашу сохраняемость без необходимости дополнительной настройки, кроме свойств источника данных:

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=

Конечно, как и безопасность, настойчивость-это более широкая тема, чем этот базовый набор здесь, и вы, безусловно, должны изучить ее дальше .

7. Веб и контроллер

Далее давайте рассмотрим веб – уровень-и начнем с настройки простого контроллера – BookController .

Мы реализуем основные операции CRUD, раскрывающие Книгу ресурсы с помощью простой проверки:

@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);
    }
}

Учитывая, что этот аспект приложения является API, мы использовали здесь аннотацию @ RestController , которая эквивалентна @Controller вместе с @ResponseBody , чтобы каждый метод маршалировал возвращенный ресурс прямо в ответ HTTP.

Только одно замечание, на которое стоит обратить внимание – мы выставляем нашу Книгу сущность в качестве нашего внешнего ресурса здесь. Это хорошо для нашего простого приложения здесь, но в реальном приложении вы, вероятно, захотите разделить эти две концепции .

8. Обработка ошибок

Теперь, когда основное приложение готово к работе, давайте сосредоточимся на простом централизованном механизме обработки ошибок с помощью @ControllerAdvice :

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({ BookNotFoundException.class })
    protected ResponseEntity handleNotFound(
      Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, "Book not found", 
          new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }

    @ExceptionHandler({ BookIdMismatchException.class, 
      ConstraintViolationException.class, 
      DataIntegrityViolationException.class })
    public ResponseEntity handleBadRequest(
      Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, ex.getLocalizedMessage(), 
          new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
    }
}

Помимо стандартных исключений, которые мы здесь обрабатываем, мы также используем пользовательское исключение:

Исключение BookNotFoundException :

public class BookNotFoundException extends RuntimeException {

    public BookNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    // ...
}

Это должно дать вам представление о том, что возможно с помощью этого глобального механизма обработки исключений. Если вы хотите увидеть полную реализацию, ознакомьтесь с подробным учебником .

Обратите внимание, что Spring Boot также по умолчанию предоставляет отображение /error . Мы можем настроить его вид, создав простой error.html :


Error Occurred

    

Error Occurred!

[status] error

message

Как и большинство других аспектов загрузки, мы можем управлять этим с помощью простого свойства:

server.error.path=/error2

9. Тестирование

Наконец, давайте протестируем наш новый API книг.

Мы можем использовать @SpringBootTest для загрузки applicationcontext и проверки отсутствия ошибок при запуске приложения:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringContextTest {

    @Test
    public void contextLoads() {
    }
}

Далее, давайте добавим тест JUnit, который проверяет вызовы API, который мы написали, используя Rest Assured:

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");
    }
}

Во-первых, мы можем попытаться найти книги, используя различные методы:

@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());
}

Далее мы протестируем создание новой книги:

@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());
}

Обновление существующей книги:

@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"));
}

И удалить книгу:

@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. Заключение

Это было быстрое, но исчерпывающее введение в Spring Boot.

Мы, конечно, едва поцарапали поверхность здесь – в этой структуре есть гораздо больше, что мы можем охватить в одной вводной статье.

Именно поэтому у нас на сайте нет ни одной статьи о загрузке .

Полный исходный код наших примеров здесь, как всегда, на GitHub .