如果我們生命夠成熟,心胸夠開擴,會發現,往往完美的競爭就是完美的合作。
接著我們要把前端Angular和後端Spring Boot連在一起,在這裡,區分為三回:
這樣我的壓力小一些,當然,這裡只是蜻蜓點水,孫悟空到此一遊,其中的變化,還要進一步花費時間。另外,這前後端程式通常會在同一部電腦上執行,因此前端程式Angular就不能放在 StartBlitz 網站,這是有安全性考量的。
現在,我們要把 Angular (front-end) and Spring Boot (back-end) 強強整合。Spring也提供 Spring MVC,因此,也可以不要 Angular,用 Spring / Spring MVC 來完成,就好像可以買一部轎貨車,又當轎車,又當貨車,也可以買兩台車,一台是轎車,一台是貨車,各隨主便,和有千秋。而我是喜歡豐富的,因此Angular, Spring boot 都想了解。這兩者各別的介紹,在前面章回均已經加以說明,在這一回中,我們專注如何連結。這個範例是以這個網頁-Baeldung為藍本,在此特別聲明並感謝,讀者有興趣也可以對照比較。
先展示結果:
這是一個典型的CRUD應用,但這一回只是為了介紹 Angular 與 Spring boot 的連結,因此其他部分就簡化,只介紹Retrieve (列出所有的資料),Create (創建資料),資料只有名稱(name)及郵編位址(email)。整個程式有兩個部分,一個是Angular, 一個是Spring boot, 為避免太長,我們區分為兩回來介紹。在這一回中介紹 Spring , 下一回中介紹 Angular。在此先使用者 (User) 的類別,這是記錄的結構:
src/main/java/User.java
@Entity
public class User {
public User() {
super();
this.name="unknown";
this.email="unknown.email";
}
public User(String name, String email) {
super();
this.name = name;
this.email = email;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private final String name;
private final String email;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getName() {return name;}
public String getEmail() {return email;}
}
@Entity, @GeneratedValue 是 H2Database 的 annotation, 必須增加相依關係於 pom.xml
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
H2Database 是一個 in-memory 的資料庫管理系統。@Entity表示是資料(@Repository)中的記錄結構。@Id, @GeneratedValue 讓 @Id 這個欄位逐筆加1. Spring Data JDBC 需要有 id 才能運作,不過上頭的 @Id 並不是 class user中的 Id,而是 org.springframework.data.annotation.Id。
src/main/java/UserREpository.java
@Repository
public interface UserRepository extends CrudRepository<User, Long>{}
創建資料庫只用了一行程式。界面(interface) CrudRepository 定義了所有我們要的功能, 如save()、findById()、delete()、deleteById()、count()等函數。@Repository 是 @Command 的衍生 “符" (annotation),因此,程式中,我們沒有看見 new UserRepository, 而是在UserController 中被注入(injection)。第一個參數User 是Entity的型別,而第二個參數 long 是主鍵 (primary key) 的型別。特別強調,我們所編碼的 UserRepository 是界面(interface),而不是類別(class),這是為什麼我們可以省事的原因。(界面裡沒有實作的邏輯,這些框架處理掉了)。
src/java/UserController.java
@RestController
@CrossOrigin(origins = "http://localhost:4200")
public class UserController {
public UserController(UserRepository userRepository) {
super();
this.userRepository = userRepository;
}
private final UserRepository userRepository;
@GetMapping("/users")
public List<User> getUsers() {
return (List<User>) userRepository.findAll();
}
@PostMapping("/users")
void addUser(@RequestBody User user) {
userRepository.save(user);
}
}
在@CrossOrigin 定義只有 localhost 才可以讀取資料,這是要避免沒有授權的讀取資料。
src/main/java/AngularDemoApplication.java
@SpringBootApplication
public class AngularDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AngularDemoApplication.class, args);
}
@Bean
CommandLineRunner init(UserRepository userRepository) {
return args -> {
Stream.of("Mattrew", "Mark", "Luke", "John", "Lucifer", "Satan").forEach(name -> {
User user = new User(name, name.toLowerCase() + "@domain.com");
userRepository.save(user);
});
userRepository.findAll().forEach(System.out::println);
};
}
}
程式啟動時,會自動執行 CommandLineRunner 所定義的函式。也可以定義繼承 CommandLineRunner 的衍生類別,衍生類別中的 run() 函式,會在啟動時執行。在上例這個起啟函式中,將產生測試的資料。
@Component
publc class MyStartup implements CommandLineRunner{
@Override
Public void run(String… args) throws Exception { … }
…
可以有一個以上的類別繼承 CommandLineRunner,但是 @Bean CommandLineRunner 函式只能有一個。