Spring Boot Tutorial - Начальная загрузка простого приложения
1. обзор
Spring Boot - это продуманное дополнение к платформе Spring, сфокусированное на соглашениях о конфигурации - очень полезное, чтобы начать работу с минимальными усилиями и создавать автономные приложения промышленного уровня.
This tutorial is a starting point for Boot - простой способ начать работу с базовым веб-приложением.
Мы рассмотрим некоторую базовую конфигурацию, интерфейс, быстрое манипулирование данными и обработку исключений.
Дальнейшее чтение:
Как изменить порт по умолчанию в Spring Boot
Посмотрите, как вы можете изменить порт по умолчанию в приложении Spring Boot.
Введение в Spring Boot Starters
Краткий обзор наиболее распространенных Spring Boot Starters, а также примеры их использования в реальных проектах.
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
Помимо стандартных исключений, которые мы здесь обрабатываем, мы также используем пользовательское исключение:
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.
Мы, конечно, почти не коснулись здесь поверхности - в этой структуре есть гораздо больше, о чем мы можем рассказать в одной вводной статье.
Именно поэтомуwe don’t just have a single article about Boot on the site.
Полный исходный код наших примеров здесь, как всегда,over on GitHub.