1. 概述

Spring BootAngular 是一对非常强大的组合,特别适合构建轻量级、高性能的 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 注解定义了:

  1. selector:HTML 中绑定的标签名
  2. templateUrl:组件的 HTML 模板
  3. 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

你会看到如下界面:

user-list

点击 "Add User" 可以看到表单界面:

user-form

5. 总结

本文我们学习了如何使用 Spring Boot 和 Angular 构建一个完整的 Web 应用,包括:

✅ 后端 REST API
✅ 前端路由和组件
✅ 数据交互与双向绑定
✅ 跨域配置

适合快速搭建中小型项目原型,是现代 Web 开发中非常常见的组合。


原始标题:Building a Web Application with Spring Boot and Angular | Baeldung