1. 概述
Spring Boot 和 Angular 是一对非常强大的组合,特别适合构建轻量级、高性能的 Web 应用。
在本教程中,我们使用 Spring Boot 实现一个 RESTful 后端,使用 Angular 构建一个基于 JavaScript 的前端。
2. Spring Boot 应用
我们的演示应用功能相对简单,主要实现以下两个功能:
- 从内存中的 H2 数据库 中读取并展示一个 JPA 实体列表
- 通过一个简单的 HTML 表单提交并持久化新的实体
2.1. Maven 依赖
以下是我们的 Spring Boot 项目所依赖的核心依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
说明:
spring-boot-starter-web
:用于构建 REST 服务spring-boot-starter-data-jpa
:用于实现数据持久层- H2 数据库的版本由 Spring Boot 父项目统一管理
2.2. JPA 实体类
为了快速搭建应用的领域层,我们定义一个简单的 JPA 实体类 User
来表示用户:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private final String name;
private final String email;
// 标准构造函数 / setter / getter / toString
}
2.3. UserRepository 接口
为了对 User
实体执行基本的 CRUD 操作,我们定义一个 UserRepository
接口:
@Repository
public interface UserRepository extends CrudRepository<User, Long>{}
2.4. REST 控制器
接下来我们实现 REST API,使用一个简单的 REST 控制器:
@RestController
@CrossOrigin(origins = "http://localhost:4200")
public class UserController {
// 标准构造函数
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
注解。这个注解启用了 CORS(跨域资源共享) 功能。
⚠️ 由于我们的 Angular 前端运行在 http://localhost:4200
,而 Spring Boot 后端运行在 http://localhost:8080
,如果不配置 CORS,浏览器会阻止跨域请求。
控制器中的两个方法:
getUsers()
:从数据库获取所有用户addUser()
:将请求体中的用户对象保存到数据库
⚠️ 当前实现没有启用 Spring Boot 验证机制,生产环境务必加上服务端校验,不能仅依赖前端校验。
2.5. 启动 Spring Boot 应用
最后,我们创建一个启动类,并初始化一些用户数据:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
CommandLineRunner init(UserRepository userRepository) {
return args -> {
Stream.of("John", "Julie", "Jennifer", "Helen", "Rachel").forEach(name -> {
User user = new User(name, name.toLowerCase() + "@domain.com");
userRepository.save(user);
});
userRepository.findAll().forEach(System.out::println);
};
}
}
启动应用后,控制台会输出初始化的用户数据:
User{id=1, name=John, [email protected]}
User{id=2, name=Julie, [email protected]}
User{id=3, name=Jennifer, [email protected]}
User{id=4, name=Helen, [email protected]}
User{id=5, name=Rachel, [email protected]}
3. Angular 应用
在 Spring Boot 应用运行后,我们就可以构建一个简单的 Angular 前端来消费 REST API。
3.1. 安装 Angular CLI
我们使用 Angular CLI 来创建和管理 Angular 项目。
安装方式如下(确保已安装 npm):
npm install -g @angular/[email protected]
✅ Angular CLI 是一个非常强大的工具,可以帮助我们快速生成组件、服务、模块等。
3.2. 使用 Angular CLI 初始化项目
在命令行中执行以下命令创建项目:
ng new angularclient
这会在 angularclient
目录下生成完整的 Angular 项目结构。
3.3. Angular 应用入口文件
在 angularclient
目录中,Angular CLI 已经生成了完整的项目结构。
Angular 使用 TypeScript 编写,入口文件是 index.html
:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Spring Boot - Angular Application</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
</head>
<body>
<app-root></app-root>
</body>
</html>
📌 注意:我们在 <body>
中使用了 <app-root>
标签,这是 Angular 的根组件选择器。
3.4. 根组件 app.component.ts
根组件文件 app.component.ts
内容如下:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title: string;
constructor() {
this.title = 'Spring Boot - Angular Application';
}
}
📌 @Component
注解定义了:
selector
:HTML 中绑定的标签名templateUrl
:组件的 HTML 模板styleUrls
:组件使用的 CSS 样式文件
3.5. 根组件模板 app.component.html
我们修改模板,添加两个导航按钮:
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="card bg-dark my-5">
<div class="card-body">
<h2 class="card-title text-center text-white py-3">{{ title }}</h2>
<ul class="text-center list-inline py-3">
<li class="list-inline-item">
<a routerLink="/users" class="btn btn-info">List Users</a>
</li>
<li class="list-inline-item">
<a routerLink="/adduser" class="btn btn-info">Add User</a>
</li>
</ul>
</div>
</div>
<router-outlet></router-outlet>
</div>
</div>
</div>
📌 两个关键点:
{{ title }}
:变量插值语法routerLink
:用于 Angular 路由跳转
3.6. User 实体类
我们创建一个简单的 User
类:
ng generate class user
export class User {
id: string;
name: string;
email: string;
}
3.7. UserService 服务
我们创建一个服务类来处理 HTTP 请求:
ng generate service user-service
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { User } from '../model/user';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class UserService {
private usersUrl: string;
constructor(private http: HttpClient) {
this.usersUrl = 'http://localhost:8080/users';
}
public findAll(): Observable<User[]> {
return this.http.get<User[]>(this.usersUrl);
}
public save(user: User) {
return this.http.post<User>(this.usersUrl, user);
}
}
📌 使用 @Injectable()
注解标记服务,以便 Angular 注入使用。
3.8. 用户列表组件 UserListComponent
ng generate component user-list
import { Component, OnInit } from '@angular/core';
import { User } from '../model/user';
import { UserService } from '../service/user.service';
@Component({
selector: 'app-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
users: User[];
constructor(private userService: UserService) {
}
ngOnInit() {
this.userService.findAll().subscribe(data => {
this.users = data;
});
}
}
模板文件 user-list.component.html
:
<div class="card my-5">
<div class="card-body">
<table class="table table-bordered table-striped">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td>
</tr>
</tbody>
</table>
</div>
</div>
📌 *ngFor
是 Angular 的结构指令,用于循环渲染。
3.9. 用户表单组件 UserFormComponent
ng generate component user-form
import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UserService } from '../service/user.service';
import { User } from '../model/user';
@Component({
selector: 'app-user-form',
templateUrl: './user-form.component.html',
styleUrls: ['./user-form.component.css']
})
export class UserFormComponent {
user: User;
constructor(
private route: ActivatedRoute,
private router: Router,
private userService: UserService) {
this.user = new User();
}
onSubmit() {
this.userService.save(this.user).subscribe(result => this.gotoUserList());
}
gotoUserList() {
this.router.navigate(['/users']);
}
}
表单模板 user-form.component.html
:
<div class="card my-5">
<div class="card-body">
<form (ngSubmit)="onSubmit()" #userForm="ngForm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" [(ngModel)]="user.name"
class="form-control"
id="name"
name="name"
placeholder="Enter your name"
required #name="ngModel">
</div>
<div [hidden]="!name.pristine" class="alert alert-danger">Name is required</div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" [(ngModel)]="user.email"
class="form-control"
id="email"
name="email"
placeholder="Enter your email address"
required #email="ngModel">
<div [hidden]="!email.pristine" class="alert alert-danger">Email is required</div>
</div>
<button type="submit" [disabled]="!userForm.form.valid"
class="btn btn-info">Submit</button>
</form>
</div>
</div>
📌 关键点:
(ngSubmit)
:表单提交事件[(ngModel)]
:双向数据绑定#userForm="ngForm"
:表单引用变量
3.10. 路由配置 app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { UserListComponent } from './user-list/user-list.component';
import { UserFormComponent } from './user-form/user-form.component';
const routes: Routes = [
{ path: 'users', component: UserListComponent },
{ path: 'adduser', component: UserFormComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
3.11. 模块配置 app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { UserListComponent } from './user-list/user-list.component';
import { UserFormComponent } from './user-form/user-form.component';
import { UserService } from './service/user.service';
@NgModule({
declarations: [
AppComponent,
UserListComponent,
UserFormComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule
],
providers: [UserService],
bootstrap: [AppComponent]
})
export class AppModule { }
4. 运行应用
✅ 启动 Spring Boot 应用后,运行以下命令启动 Angular 开发服务器:
ng serve --open
这将自动打开浏览器访问 http://localhost:4200
。
你会看到如下界面:
点击 "Add User" 可以看到表单界面:
5. 总结
本文我们学习了如何使用 Spring Boot 和 Angular 构建一个完整的 Web 应用,包括:
✅ 后端 REST API
✅ 前端路由和组件
✅ 数据交互与双向绑定
✅ 跨域配置
适合快速搭建中小型项目原型,是现代 Web 开发中非常常见的组合。