Spring Boot Tutorial - Начальная загрузка простого приложения

Spring Boot Tutorial - Начальная загрузка простого приложения

1. обзор

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

This tutorial is a starting point for Boot - простой способ начать работу с базовым веб-приложением.

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

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

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

Посмотрите, как вы можете изменить порт по умолчанию в приложении Spring Boot.

Read more

Введение в Spring Boot Starters

Краткий обзор наиболее распространенных Spring Boot Starters, а также примеры их использования в реальных проектах.

Read more

2. Настроить

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

Сгенерированный проект опирается на Boot parent:


    org.springframework.boot
    spring-boot-starter-parent
    2.1.6.RELEASE
    

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


    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 Boot properties available.

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

Теперь вы, надеюсь, заметили закономерность -most Spring libraries are easily imported into our project with the use of simple Boot starters.

После зависимости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();
    }
}

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

 

Конечно, Spring Security - обширная тема, и ее нелегко охватить парой строк конфигурации, поэтому я определенно рекомендую вамgo deeper into the topic.

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

Начнем с определения нашей модели данных - простой сущностиBook:

@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 Data здесь:

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

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

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

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

  • @EnableJpaRepositories для сканирования указанного пакета на наличие репозиториев

  • @EntityScan, чтобы забрать наши объекты JPA

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

После того, как мы включим зависимость H2,Spring Boot auto-detects it and sets up our persistence без необходимости в дополнительной настройке, кроме свойств источника данных:

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=

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

7. Интернет и контроллер

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

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

@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.

Стоит отметить одно замечание: здесь мы представляем нашу сущностьBook как внешний ресурс. Это нормально для нашего простого приложения, но в реальном приложении вы, вероятно, захотитеseparate these two concepts.

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

Теперь, когда основное приложение готово к работе, давайте сосредоточимся наa simple centralized error handling mechanism, используя@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);
    }
    // ...
}

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

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


Error Occurred

    

Error Occurred!

[status] error

message

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

server.error.path=/error2

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

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

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

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

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

@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.

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

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