מדריך אתחול האביב - Bootstrap יישום פשוט

1. סקירה כללית

Spring Boot הוא תוספת דעתנית וממוקדת-תצורה ממוקדת לפלטפורמת Spring - שימושית מאוד להתחיל במינימום מאמץ וליצור יישומים עצמאיים וייצוריים.

מדריך זה מהווה נקודת התחלה עבור Boot - דרך להתחיל בצורה פשוטה, עם יישום אינטרנט בסיסי.

נעבור על תצורת ליבה כלשהי, טיפול קדמי, טיפול מהיר בנתונים וטיפול חריג.

2. התקנה

ראשית, נשתמש באביב Initializr כדי ליצור את הבסיס לפרויקט שלנו.

הפרויקט שנוצר מסתמך על ההורה Boot:

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

התלות הראשונית תהיה די פשוטה:

 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 

3. תצורת יישום

לאחר מכן, נגדיר פשוט רָאשִׁי כיתה ליישום שלנו:

@SpringBootApplication מחלקה ציבורית יישום {public static void main (String [] args) {SpringApplication.run (Application.class, args); }} 

שימו לב כיצד אנו משתמשים @ SpringBootApplication כשיעור תצורת היישומים העיקרי שלנו; מאחורי הקלעים, זה שווה ערך ל @תְצוּרָה, @EnableAutoConfiguration, ו @ComponentScan יַחַד.

לבסוף, נגדיר פשוט application.properties קובץ - שיש לו כרגע רק מאפיין אחד:

server.port = 8081 

server.port משנה את יציאת השרת מברירת המחדל 8080 ל- 8081; יש כמובן הרבה יותר נכסי Spring Boot.

4. תצוגת MVC פשוטה

בואו כעת להוסיף ממשק קצה פשוט באמצעות Thymeleaf.

ראשית, עלינו להוסיף את אביב-אתחול-התחלה-טימילית תלות שלנו pom.xml:

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

זה מאפשר ל- Thymeleaf כברירת מחדל - אין צורך בתצורה נוספת.

כעת אנו יכולים להגדיר זאת בתוכנת שלנו application.properties:

spring.thymeleaf.cache = spring false.thymeleaf.enabled = אמיתי spring.thymeleaf.prefix = classpath: / templates / spring.thymeleaf.suffix = .html spring.application.name = Bootstrap Boot Boot 

לאחר מכן נגדיר בקר פשוט ודף בית בסיסי - עם הודעת קבלת פנים:

@Controller מחלקה ציבורית SimpleController {@Value ("$ {spring.application.name}") מחרוזת appName; @GetMapping ("/") homePage מחרוזת (מודל מודל) {model.addAttribute ("appName", appName); לחזור הביתה"; }} 

לבסוף, הנה שלנו home.html:

 דף הבית 

ברוך הבא לאפליקציה שלנו

שימו לב כיצד השתמשנו במאפיין שהגדרנו במאפיינים שלנו - ואז הזרנו אותו כדי שנוכל להציג אותו בדף הבית שלנו.

5. אבטחה

לאחר מכן, בואו נוסיף אבטחה ליישום שלנו - על ידי נכלל תחילה את מתנע האבטחה:

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

עד עכשיו אתה מקווה לשים לב לדפוס - מרבית ספריות האביב מיובאות בקלות לפרויקט שלנו תוך שימוש במתחילי אתחול פשוטים.

פעם ה אביב-אתחול-התחלה-אבטחה תלות במסלול הכיתה של היישום - כל נקודות הקצה מאובטחות כברירת מחדל, באמצעות אחת מהן http בסיסי אוֹ formLogin בהתבסס על אסטרטגיית משא ומתן התוכן של Spring Security.

לכן, אם יש לנו המתנע בשביל הכיתה, בדרך כלל עלינו להגדיר את תצורת האבטחה המותאמת אישית שלנו על ידי הרחבת ה- מתאם WebSecurityConfigurer מעמד:

@Configuration @EnableWebSecurity המחלקה הציבורית SecurityConfig מרחיב את WebSecurityConfigurerAdapter {@Override מוגן חלל להגדיר (HttpSecurity http) זורק חריג {http.authorizeRequests () .anyRequest () .permitAll () .and (). Csrf (). השבת (). }}

בדוגמה שלנו, אנו מאפשרים גישה בלתי מוגבלת לכל נקודות הקצה.

כמובן שאביב אבטחה הוא נושא נרחב ונושא שלא מכוסה בקלות בכמה שורות תצורה - אז אני בהחלט ממליץ לך להעמיק בנושא.

6. התמדה פשוטה

נתחיל בהגדרת מודל הנתונים שלנו - פשוט סֵפֶר יֵשׁוּת:

@Entity בכיתה ציבורית ספר {@Id @GeneratedValue (אסטרטגיה = GenerationType.AUTO) מזהה פרטי ארוך; @Column (nullable = false, unique = true) כותרת מחרוזת פרטית; @Column (nullable = false) מחבר מחרוזת פרטי; }

והמאגר שלו, המשתמש היטב בנתוני האביב כאן:

ממשק ציבורי BookRepository מרחיב את CrudRepository {List findByTitle (כותרת מחרוזת); }

לבסוף, עלינו כמובן להגדיר את שכבת ההתמדה החדשה שלנו:

@EnableJpaRepositories ("com.baeldung.persistence.repo") @EntityScan ("com.baeldung.persistence.model") @SpringBootApplication יישום בכיתה ציבורית {...}

שים לב שאנחנו משתמשים:

  • @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 / ספרים") BookController בכיתה ציבורית {@ BookRepository פרטית אוטומטית פרטית; @ GetMapping ציבורי Iterable findAll () {return bookRepository.findAll (); } @ GetMapping ("/ title / {bookTitle}") רשימה ציבורית findByTitle (@PathVariable מחרוזת bookTitle) {return bookRepository.findByTitle (bookTitle); } @GetMapping ("/ {id}") public book findOne (@PathVariable Long ID) {return bookRepository.findById (id) .orElseThrow (BookNotFoundException :: new); } @PostMapping @ResponseStatus (HttpStatus.CREATED) יצירת ספר ציבורי (ספר ספרים @RequestBody) {return bookRepository.save (book); } @DeleteMapping ("/ {id}") מחיקה בטלנית ציבורית (@PathVariable Long id) {bookRepository.findById (id) .orElseThrow (BookNotFoundException :: new); bookRepository.deleteById (id); } @PutMapping ("/ {id}") ספר עדכון ספר ציבורי (@RequestBody Book Book, @PathVariable Long id) {if (book.getId ()! = Id) {זרוק BookIdMismatchException חדש (); } bookRepository.findById (id) .orElseThrow (BookNotFoundException :: new); return bookRepository.save (ספר); }} 

בהתחשב בהיבט זה של היישום הוא ממשק API, עשינו שימוש ב- @RestController ביאור כאן - שווה ערך ל- @בקר ביחד עם @ResponseBody - כך שכל שיטה מארגנת את המשאב המוחזר ישירות לתגובת ה- HTTP.

רק פתק אחד שכדאי לציין - אנחנו חושפים את שלנו סֵפֶר הישות כמשאב החיצוני שלנו כאן. זה בסדר ליישום הפשוט שלנו כאן, אבל ביישום בעולם האמיתי, סביר להניח שתרצה להפריד בין שני המושגים הללו.

8. טיפול בשגיאות

עכשיו, כאשר יישום הליבה מוכן לצאת לדרך, בואו נתמקד מנגנון פשוט וריכוז לטיפול בשגיאות באמצעות @ControllerAdvice:

@ControllerAdvice מחלקה ציבורית RestExceptionHandler מרחיב את ResponseEntityExceptionHandler {@ExceptionHandler ({BookNotFoundException.class}) מוגן ResponseEntity handleNotFound (Exception ex, בקשת WebRequest) {return handleExceptionInternal (לשעבר, "Book not found", HttpHead_http_Head_ThttpHttpHtttt_HttpHtttt_HttpHtt_HttpHtt_HttpHtt_HttpHtt_HttpHtt_Thtttt_Htt_HttpHtttt_HttpHtt_HttpHtttt_HttpHtttt ; } @ExceptionHandler ({BookIdMismatchException.class, ConstraintViolationException.class, DataIntegrityViolationException.class}) ResponseEntity public handleBadRequest (Exception ex, בקשת WebRequest) {return handleExceptionInternal (ex, ex.getLocalizedMessttest, (בקשה) HST,) ); }} 

מעבר לחריגים הסטנדרטיים שאנו מטפלים כאן, אנו משתמשים גם בחריג מותאם אישית:

BookNotFoundException:

מחלקה ציבורית BookNotFoundException מרחיב RuntimeException {public BookNotFoundException (הודעת מחרוזת, סיבה הניתנת לזריקה) {סופר (הודעה, סיבה); } // ...} 

זה אמור לתת לך מושג מה אפשרי עם מנגנון טיפול חריג זה עולמי. אם תרצה לראות יישום מלא, עיין במדריך המעמיק.

שים לב כי Spring Boot מספק גם /שְׁגִיאָה מיפוי כברירת מחדל. אנו יכולים להתאים אישית את תצוגתו על ידי יצירת פשוט error.html:

 אירעה שגיאה שגיאת [סטטוס] 

הוֹדָעָה

כמו רוב ההיבטים האחרים ב- Boot, אנו יכולים לשלוט בכך באמצעות מאפיין פשוט:

server.error.path = / error2

9. בדיקות

לבסוף, בואו נבדוק את ה- API החדש של ספרים.

אנחנו יכולים לעשות שימוש @ SpringBootTest כדי לטעון את הקשר היישום ולוודא שאין שגיאות בהפעלת האפליקציה:

@RunWith (SpringRunner.class) @SpringBootTest מחלקה ציבורית SpringContextTest {@Test public void contextLoads () {}}

לאחר מכן, נוסיף בדיקת JUnit המאמתת את השיחות לממשק ה- API שאנו כתובים, באמצעות RestAssured:

מחלקה ציבורית SpringBootBootstrapLiveTest {סופי סטטי פרטי מחרוזת API_ROOT = "// localhost: 8081 / api / books"; ספר פרטי createRandomBook () {ספר ספר = ספר חדש (); book.setTitle (אקראי Alfabetisk (10)); book.setAuthor (אקראי אלפביתי (15)); ספר החזרה; } מחרוזת פרטית createBookAsUri (ספר ספרים) {Response response = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (book) .post (API_ROOT); החזר API_ROOT + "/" + response.jsonPath (). get ("id"); }} 

ראשית, אנו יכולים לנסות למצוא ספרים בשיטות שונות:

@ מבחן ציבורי בטל כאשר GetAllBooks_thenOK () {תגובה תגובה = RestAssured.get (API_ROOT); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); } @Test ציבורי בטל כאשר GetBooksByTitle_thenOK () {ספר ספר = createRandomBook (); createBookAsUri (ספר); תגובה תגובה = RestAssured.get (API_ROOT + "/ title /" + book.getTitle ()); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); assertTrue (response.as (List.class) .size ()> 0); } @ מבחן פומבי בטל כאשר GetCreatedBookById_thenOK () {ספר ספר = createRandomBook (); מחרוזת מיקום = createBookAsUri (ספר); תגובת תגובה = RestAssured.get (מיקום); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); assertEquals (book.getTitle (), response.jsonPath () .get ("כותרת")); } @Test ציבורי בטל כאשר GetNotExistBookById_thenNotFound () {תגובה תגובה = RestAssured.get (API_ROOT + "/" + randomNumeric (4)); assertEquals (HttpStatus.NOT_FOUND.value (), response.getStatusCode ()); } 

לאחר מכן נבדוק יצירת ספר חדש:

@ מבחן ציבורי בטל כאשרCreateNewBook_thenCreated () {ספר ספר = createRandomBook (); תגובת תגובה = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (ספר). Post (API_ROOT); assertEquals (HttpStatus.CREATED.value (), response.getStatusCode ()); } @Test ציבורי בטל כאשרInvalidBook_thenError () {ספר ספר = createRandomBook (); book.setAuthor (null); תגובת תגובה = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (ספר). Post (API_ROOT); assertEquals (HttpStatus.BAD_REQUEST.value (), response.getStatusCode ()); } 

עדכן ספר קיים:

@ מבחן ציבורי בטל כאשרUpdateCreatedBook_thenUpdated () {ספר ספר = createRandomBook (); מחרוזת מיקום = createBookAsUri (ספר); book.setId (Long.parseLong (location.split ("api / books /") [1])); book.setAuthor ("newAuthor"); תגובת תגובה = RestAssured.given () .contentType (MediaType.APPLICATION_JSON_VALUE) .body (ספר) .put (מיקום); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); תגובה = RestAssured.get (מיקום); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); assertEquals ("newAuthor", response.jsonPath () .get ("מחבר")); } 

ומחק ספר:

@ מבחן פומבי בטל כאשר DeleteCreatedBook_thenOk () {ספר ספר = createRandomBook (); מחרוזת מיקום = createBookAsUri (ספר); תגובה תגובה = RestAssured.delete (מיקום); assertEquals (HttpStatus.OK.value (), response.getStatusCode ()); תגובה = RestAssured.get (מיקום); assertEquals (HttpStatus.NOT_FOUND.value (), response.getStatusCode ()); } 

10. מסקנה

זה היה מבוא מהיר אך מקיף לאביב אתחול.

כמובן שכמעט לא גירדנו את פני השטח כאן - יש הרבה יותר למסגרת הזו שנוכל לכסות במאמר מבוא יחיד.

בדיוק בגלל זה אין לנו רק מאמר אחד על Boot באתר.

קוד המקור המלא של הדוגמאות שלנו כאן, כמו תמיד, הסתיים ב- GitHub.