1. Gambaran keseluruhan
Spring Boot adalah tambahan yang berfokus pada konvensyen dan konvensyen ke platform Spring - sangat berguna untuk memulakan dengan usaha minimum dan membuat aplikasi kelas produksi yang berdiri sendiri.
Tutorial ini adalah titik permulaan untuk Boot - cara untuk memulakan dengan cara yang mudah, dengan aplikasi web asas.
Kami akan membahas beberapa konfigurasi teras, front-end, manipulasi data cepat, dan pengendalian pengecualian.
2. Persediaan
Pertama, mari gunakan Spring Initializr untuk menghasilkan asas projek kami.
Projek yang dihasilkan bergantung pada Boot induk:
org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE
Pergantungan awal akan cukup mudah:
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa com.h2database h2
3. Konfigurasi Aplikasi
Seterusnya, kami akan mengkonfigurasi kelas utama sederhana untuk aplikasi kami:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Perhatikan bagaimana kita menggunakan @SpringBootApplication sebagai kelas konfigurasi aplikasi utama kami; di belakang tabir, itu bersamaan dengan @Configuration , @EnableAutoConfiguration , dan @ComponentScan bersama-sama.
Akhirnya, kami akan menentukan fail application.properties yang mudah - yang buat masa ini hanya mempunyai satu harta:
server.port=8081
server.port menukar port pelayan dari lalai 8080 hingga 8081; sudah tentu terdapat banyak lagi sifat Spring Boot yang ada.
4. Paparan MVC ringkas
Mari sekarang tambahkan bahagian depan yang mudah menggunakan Thymeleaf.
Pertama, kita perlu menambahkan pergantungan spring-boot-starter-thymeleaf ke pom.xml kami :
org.springframework.boot spring-boot-starter-thymeleaf
Itu membolehkan Thymeleaf secara lalai - tidak perlu konfigurasi tambahan.
Kita sekarang boleh mengkonfigurasinya dalam aplikasi kita. Sifat :
spring.thymeleaf.cache=false spring.thymeleaf.enabled=true spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html spring.application.name=Bootstrap Spring Boot
Seterusnya, kami akan menentukan pengawal sederhana dan halaman utama - dengan mesej selamat datang:
@Controller public class SimpleController { @Value("${spring.application.name}") String appName; @GetMapping("/") public String homePage(Model model) { model.addAttribute("appName", appName); return "home"; } }
Akhirnya, inilah rumah kami.html :
Home Page Welcome to Our App
Perhatikan bagaimana kami menggunakan harta yang kami tetapkan dalam sifat kami - dan kemudian disuntikkan supaya kami dapat menunjukkannya di laman utama kami.
5. Keselamatan
Seterusnya, mari tambah keselamatan pada aplikasi kami - dengan memasukkan keselamatan pertama:
org.springframework.boot spring-boot-starter-security
Pada masa ini, semoga anda melihat corak - kebanyakan perpustakaan Spring mudah diimport ke dalam projek kami dengan penggunaan Boot permulaan .
Setelah kebergantungan keselamatan-starter-boot pada jalan kelas aplikasi - semua titik akhir diamankan secara lalai, menggunakan httpBasic atau formLogin berdasarkan strategi perundingan kandungan Spring Security.
Itulah sebabnya, jika kita mempunyai starter di classpath, kita biasanya harus menentukan konfigurasi Keamanan tersuai kita sendiri dengan memperluas kelas WebSecurityConfigurerAdapter :
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest() .permitAll() .and().csrf().disable(); } }
Dalam contoh kami, kami membenarkan akses tanpa had ke semua titik akhir.
Sudah tentu, Spring Security adalah topik yang luas dan tidak mudah dibahas dalam beberapa baris konfigurasi - jadi saya pasti mendorong anda untuk mendalami topik ini dengan lebih mendalam.
6. Ketekunan sederhana
Mari mulakan dengan menentukan model data kami - entiti Buku ringkas :
@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; }
Dan repositorinya, memanfaatkan Spring Data di sini:
public interface BookRepository extends CrudRepository { List findByTitle(String title); }
Akhirnya, kita perlu mengkonfigurasi lapisan ketekunan baru kita:
@EnableJpaRepositories("com.baeldung.persistence.repo") @EntityScan("com.baeldung.persistence.model") @SpringBootApplication public class Application { ... }
Perhatikan bahawa kami menggunakan:
- @EnableJpaRepositories untuk mengimbas pakej yang ditentukan untuk repositori
- @EntityScan untuk mengambil entiti JPA kami
Untuk mempermudah, kami menggunakan pangkalan data dalam memori H2 di sini - supaya kami tidak mempunyai kebergantungan luaran semasa kami menjalankan projek.
Sebaik sahaja kami memasukkan kebergantungan H2, Spring Boot mengesannya secara automatik dan mengatur kegigihan kami tanpa memerlukan konfigurasi tambahan, selain sifat sumber data:
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=
Of course, like security, persistence is a broader topic than this basic set here, and one you should certainly explore further.
7. Web and the Controller
Next, let's have a look at a web tier – and we'll start that by setting up a simple controller – the BookController.
We'll implement basic CRUD operations exposing Book resources with some simple validation:
@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); } }
Given this aspect of the application is an API, we made use of the @RestController annotation here – which equivalent to a @Controller along with @ResponseBody – so that each method marshalls the returned resource right to the HTTP response.
Just one note worth pointing out – we're exposing our Book entity as our external resource here. That's fine for our simple application here, but in a real-world application, you will likely want to separate these two concepts.
8. Error Handling
Now that the core application is ready to go, let's focus on a simple centralized error handling mechanism using @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); } }
Beyond the standard exceptions we're handling here, we're also using a custom exception:
BookNotFoundException:
public class BookNotFoundException extends RuntimeException { public BookNotFoundException(String message, Throwable cause) { super(message, cause); } // ... }
This should give you an idea of what's possible with this global exception handling mechanism. If you'd like to see a full implementation, have a look at the in-depth tutorial.
Note that Spring Boot also provides an /error mapping by default. We can customize its view by creating a simple error.html:
Error Occurred [status] error message
Like most other aspects in Boot, we can control that with a simple property:
server.error.path=/error2
9. Testing
Finally, let's test our new Books API.
We can make use of @SpringBootTest to load the application context and verify there are no errors when running the app:
@RunWith(SpringRunner.class) @SpringBootTest public class SpringContextTest { @Test public void contextLoads() { } }
Next, let's add a JUnit test that verifies the calls to the API we're written, using RestAssured:
public class SpringBootBootstrapLiveTest { private static final String API_ROOT = "//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"); } }
First, we can try to find books using variant methods:
@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()); }
Next, we'll test creating a new book:
@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()); }
Update an existing book:
@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")); }
And delete a book:
@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. Conclusion
This was a quick but comprehensive intro to Spring Boot.
Kita tentu saja tidak menggaru permukaannya - terdapat banyak lagi kerangka kerja ini yang dapat kita bahas dalam satu artikel intro.
Itulah sebabnya kami tidak mempunyai satu artikel pun mengenai Boot di laman web ini.
Kod sumber lengkap contoh kami di sini, seperti biasa, ada di GitHub.