vlambda博客
学习文章列表

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

Chapter 2. Starting in the Spring World – the CMS Application

现在,我们将创建我们的第一个应用程序;至此,我们已经了解了 Spring 的概念,并准备将它们付诸实践。在本章的开头,我们将介绍创建 Web 应用程序的 Spring 依赖项,同时我们也知道 Spring Initializr 是一个很棒的项目,它使开发人员能够创建 Spring 框架项目,并且可以根据需要添加任意数量的依赖项。在本章中,我们将学习如何在 IDE 和命令行上构建我们的第一个 Spring 应用程序,公开我们的第一个端点,了解它是如何工作的,并了解 Spring REST 支持的主要注释。我们将弄清楚如何为 CMS (内容管理系统< /strong>) 应用程序并了解依赖注入如何在 Spring 容器中工作。我们将满足 Spring 的原型并实现我们的第一个 Spring bean。在本章的最后,我们将解释如何创建一个视图层并将其与 AngularJS 集成。 

在本章中,将涉及以下主题:

  • Creating the project structure
  • Running the first Spring application
  • Introducing the REST support
  • Understanding the Dependency Injection in Spring 

Creating the CMS application structure


现在我们将使用 Spring 框架创建我们的第一个 application;我们将为 CMS 应用程序创建一个基本结构 with Spring Initializr。这个页面有助于引导我们的应用程序,它是一种指南,允许我们配置对 Maven 或 Gradle 的依赖项。我们还可以选择 Spring Boot 的语言和版本。

页面如下所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

Project Metadata 部分,我们可以放置 Maven 项目的坐标;有一个 group 字段引用 groupId 标记, 我们有引用 artifactId 的工件。这都是针对 Maven 坐标的。

dependencies 部分启用 Spring 依赖项的配置,该字段具有自动完成功能并帮助开发人员放入正确的依赖项。

The CMS project

在开始写代码和学习amazing的东西之前,我们先来了解一下CMS项目,这个项目的主要目的是帮助企业管理不同主题的CMS内容。该项目有三个主要实体:

  • The News class is the most important, it will store the content of the news.
  • It has a category which makes the search easier, and we can also group news by category, and of course, we can group by the user who has created the news. The news should be approved by other users to make sure it follows the company rules.
  • The news has some tags as well, as we can see the application is pretty standard, the business rules are easy as well; this is intentional because we keep the focus on the new things we will learn.

现在我们知道 Spring Initializr (https://start.spring.io) 的工作原理和business 规则我们需要遵循,我们准备创建项目。让我们现在就做。

Project metadata section

Group字段和spring-5  >cms 在 Artifact 字段中。如果你想customize 它,没问题,这是一种信息量级的项目配置:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

The dependencies section

在 Search for Dependencies 字段中键入 MVC 词。 Web module 将作为一个选项出现,Web 模块包含嵌入式 Tomcat 和 Spring 的全栈 web 开发MVC,选择它。另外,我们需要在这个模块中加入 Thymeleaf 依赖。它是一个模板引擎,对本章末尾的视图功能很有用。输入Thymeleaf,它包含了 Thymeleaf 模板引擎,并包含与Spring 的集成。该模块将出现,然后也将其选中。现在我们可以看到 WebThymeleaf 在 < strong class="userinput">Selected Dependencies 窗格:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

Generating the project

在我们完成项目definition 并选择项目依赖项后,我们就可以下载项目了。可以使用 Generate Project 按钮完成,点击它。该项目将被下载。在这个阶段,项目已经准备好开始我们的工作了:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

Note

将生成名为 cms.zip 的 zip 文件(Artifact 字段输入信息)下载文件的位置取决于浏览器配置。

Note

>在打开项目之前,我们必须将Spring Initializr生成的工件解压到想要的位置。命令应该是:  unzip -d <target_destination> /<path_to_file>/cms.zip。按照示例进行操作: unzip -d /home/john /home/john/Downloads/cms.zip.

现在,我们可以在 IDE 中打开项目。我们打开它,看看项目的基本结构。

Running the application


在我们运行应用程序之前,让我们先了解一下我们的项目结构。 

使用 Import Project 在 IntelliJ IDEA 上打开项目或 Open 选项(两者类似),会显示如下页面:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

然后我们可以打开或导入 pom.xml文件。

应显示以下项目结构:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

打开pom.xml, 我们有三个依赖, spring-boot-starter-thymeleaf, < code class="literal">spring-boot-starter-web, spring-boot-starter-test,还有一个有趣的插件, spring-boot-maven-plugin.

这些 starter 依赖是开发人员的捷径,因为它们为模块提供了完整的依赖。比如spring-boot-starter-web,有 web-mvc,jackson-databindhibernate-validator-web 等;这些依赖项必须在类路径上才能运行 Web 应用程序,并且启动器使这项任务变得相当容易。

我们来分析一下我们的pom.xml,文件应该是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>spring-five</groupId>
  <artifactId>cms</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>cms</name>
  <description>Demo project for Spring Boot</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.8.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.16</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.7.0</version>
    </dependency>

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.7.0</version>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

此外,我们还有一个 spring-boot-maven-plugin,这个很棒的插件为 Maven 提供 Spring Boot 支持。它使您能够将应用程序打包在 Fat-JAR 中,并且插件 支持 运行、启动和停止目标,以及与我们的应用程序交互。

Note

Fat-JAR:一个 JAR 包含所有项目类文件和资源及其所有依赖项。

现在,这对 Maven 配置来说已经足够了;让我们看一下Java文件。

Spring Initializr 为我们创建了一个类,一般来说,这个类的名称是工件名称加上 Application, 在我们的例子中是 CmsApplication< /code>,这个类应该是这样的:

package springfive.cms;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CmsApplication {

  public static void main(String[] args) {
    SpringApplication.run(CmsApplication.class, args);
  }

}

Looking under the hood

我们这里有一些有趣的东西,让我们了解它们。 @SpringBootApplication 是 Spring Boot 应用的必备注解;它是@Configuration@EnableAutoConfiguration@Component  注解。让我们深入研究:

  • The first annotation, @Configuration indicates that the class can produce a beans definitions for the Spring container. This is an interesting annotation to work with external dependencies such as DataSources; this is the most common use case for this annotation.
  • The second annotation, @EnableAutoConfiguration means that with the Spring ApplicationContext container, it will try to help us configure the default beans for the specific context. For instance, when we create the web MVC application with Spring Boot, we will probably need a web server container to run it. In a default configuration, the Spring container, together with @EnableAutoConfiguration, will configure a bean Tomcat-embedded container for us. This annotation is very helpful for developers.
  • The @Component is a stereotype, the container understands which class is considered for auto-detection and needs to instantiate it.

 SpringApplication 类负责从 main 方法引导 Spring 应用程序,它会创建一个 ApplicationContext 实例,照顾configuration 文件提供的配置,最后,它将加载由注释定义的单例bean。

Note

Stereotype Annotations 表示架构层中的概念划分。它们帮助开发人员了解类的用途和 bean 所代表的层,例如,@Repository 表示数据访问层。

Running the application

我们将在 IntelliJ IDEA 和 command 行中运行应用程序。学习是一项重要的任务,因为我们在不同的开发环境中工作;有时应用程序的配置有点复杂,我们无法使用 IDE 运行它,或者有时公司有不同的 IDE 作为标准,因此我们将学习两种不同的方式。

IntelliJ IDEA

一般来说,IntelliJ IDEA 识别 mainannotated@SpringBootApplication 并为我们创建一个运行配置,但这取决于工具的版本,我们来做吧。

Command line

command 行是一个更generic 工具来运行项目。此外,由于 Spring Boot Maven 插件,这项任务很容易。有两种运行方式,我们将同时介绍这两种方式。

Command line via the Maven goal

第一个是Spring Boot Maven插件的一个goal,简单明了;打开终端然后转到根项目文件夹,注意这是我们有 pom.xml,execute 如下命令:

mvn clean install spring-boot:run

Maven 现在将编译项目并运行主类 CmsApplication,我们应该会看到以下输出:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序
Command line via the JAR file

通过Java文件运行,我们需要编译并打包,然后我们就可以运行项目了 使用 Java 命令行。要编译和打包它,我们可以使用非常标准的 Maven 命令,如下所示:

mvn clean install

项目编译打包成Fat-JAR后,我们可以执行JAR文件,进入目标文件夹,查看该文件夹下的文件,大概结果如下:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

我们的目标文件夹中有两个主要文件,cms-0.0.1-SNAPSHOT.jar 和 cms-0.0.1-SNAPSHOT .jar.original, .original 扩展名的文件不可执行。它是编译产生的原始工件,另一个是我们的可执行文件。就是我们要找的,我们执行一下,输入如下命令:

java -jar cms-0.0.1-SNAPSHOT.jar

结果应如显示。应用程序已启动并正在运行:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

这部分就是这样,在下一节中,我们将创建第一个 REST (Representational State Transfer) 资源并了解 REST 端点的工作方式。

Creating the REST resources


现在,我们在本节中启动并运行了一个应用程序,我们将添加一些 REST 端点并为 CMS 应用程序建模一些初始类,即 REST endpoints 将对 AngularJS 集成很有用。

API 的必需特性之一是文档,而 Swagger 是帮助我们完成这些任务 的流行工具。 Spring 框架支持 Swagger,我们可以通过几个注解来实现。项目的Spring Fox是正确的工具来做这件事,本章我们就来看看这个工具。

我们开工吧。

Models

在我们开始创建我们的类之前,我们将添加Lombok依赖在我们的项目中。这是一个很棒的库,它在编译时提供了一些有趣的东西,例如 GET/SET, Val 关键字使变量成为最终变量,@Data 使类具有一些默认方法,例如 getter/setters,等于hashCode

Adding Lombok dependency

将以下依赖项放入 pom.xml 文件中:

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.16.16</version>
  <scope>provided</scope>
</dependency>

provided 范围指示 Maven 不要在 JAR 文件中include 这个依赖项因为我们在编译时需要它。我们在运行时不需要它。等待 Maven 下载依赖项,现在就是这样。

另外,我们可以使用 Reimport All Maven Projects由 IntelliJ IDEA 提供,位于 Maven 项目选项卡中,如下所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

Creating the models

现在,我们将创建我们的模型,这些模型是用@Data< /代码>。

Tag

这个类代表我们系统中的一个标签。没有必然任何存储库,因为它将与我们的新闻一起保存 实体:

package springfive.cms.domain.models;

import lombok.Data;

@Data
public class Tag {

  String value;

}
Category

我们的 CMS 应用程序的类别 model 可用于对新闻进行分组。此外,另一个重要的事情是,这使我们的新闻分类,使搜索任务变得容易。看看下面的代码:

package springfive.cms.domain.models;

import lombok.Data;

@Data
public class Category {

  String id;

String name;

}
User

它代表我们领域模型中的一个用户。我们 两个不同的个人资料,一个是作为新闻撰稿人的作者,另一个是必须审阅新闻的审稿人在门户网站注册。看看下面的例子:

package springfive.cms.domain.models;

import lombok.Data;

@Data
public class User {

  String id;

String identity;

String name;

Role role;

}
News

这个类代表我们域中的新闻,目前它没有任何行为。只有属性和 getter/setter 被暴露;未来,我们会添加一些行为:

package springfive.cms.domain.models;

import java.util.Set;
import lombok.Data;

@Data
public class News {

  String id;

  String title;

  String content;

  User author;

  Set<User> mandatoryReviewers;

  Set<Review> reviewers;

  Set<Category> categories;

  Set<Tag> tags;

}

Review 类可以在 GitHub 上找到:(https://github.com/PacktPublishing/Spring-5.0-By-Example/tree/master/Chapter02/ src/main/java/springfive/cms/domain/models)。

正如我们所见,它们是代表我们的 CMS 应用程序域的简单 Java 类。它是我们应用程序的核心,所有领域逻辑都将驻留在这些类中。这是一个重要的特征。

Hello REST resources

我们已经创建了模型,我们可以开始思考我们的 REST 资源。我们将创建三个主要资源:

  • CategoryResource which will be responsible for the Category class.
  • The second one is UserResource. It will manage the interactions between the User class and the REST APIs.
  • The last one, and more important as well, will be the NewsResource which will be responsible for managing news entities, such as reviews.

Creating the CategoryResource class

我们将创建我们的第一个 REST 资源,让我们开始 使用 CategoryResource 负责管理我们的 Category 类的类。该实体的实现将很简单,我们 将创建CRUD 端点,例如创建、检索、更新和删除。在创建 API 时,我们必须牢记两件重要的事情。第一个是正确的 HTTP 动词,例如 POSTGETPUT DELETE。 REST API 必须有正确的 HTTP 动词,因为它为我们提供了有关 API 的内在知识。它是任何与我们的 API 交互的模式。另一件事是状态码,它与我们必须遵循的第一个相同,这是开发人员很容易识别的模式。 Richardson 成熟度模型 可以帮助我们创建令人惊叹的 REST API,并且该模型引入了一些级别来衡量 REST API,它是一种温度计。

首先,我们将为我们的 API 创建骨架。想想关于您的应用程序需要哪些功能。 在下一节中,我们< id="id326249813" class="indexterm"> 将解释如何在我们的 REST API 中添加服务层。现在,让我们构建一个 CategoryResource 类,我们的实现可能如下所示:

package springfive.cms.domain.resources;

import java.util.Arrays;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import springfive.cms.domain.models.Category;
import springfive.cms.domain.vo.CategoryRequest;

@RestController
@RequestMapping("/api/category")
public class CategoryResource {

  @GetMapping(value = "/{id}")
  public ResponseEntity<Category> findOne(@PathVariable("id") String id){
    return ResponseEntity.ok(new Category());
  }

  @GetMapping
  public ResponseEntity<List<Category>> findAll(){
    return ResponseEntity.ok(Arrays.asList(new Category(),new Category()));
  }

  @PostMapping
  public ResponseEntity<Category> newCategory(CategoryRequest category){
    return new ResponseEntity<>(new Category(), HttpStatus.CREATED);
  }

  @DeleteMapping("/{id}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public void removeCategory(@PathVariable("id") String id){
  }

  @PutMapping("/{id}")
  public ResponseEntity<Category> updateCategory(@PathVariable("id") String id,CategoryRequest category){
    return new ResponseEntity<>(new Category(), HttpStatus.OK);
  }

}

CategoryRequest 可以在 GitHub (https://github.com/PacktPublishing/Spring-5.0-By-Example/tree/master/Chapter02/src/主/java/springfive/cms/domain/vo)。

我们这里有一些重要的概念。第一个是 @RestController。它指示 Spring 框架 CategoryResource 类将通过 Web-MVC 模块公开 REST 端点。这个注解会在框架中配置一些东西,比如 HttpMessageConverters来处理HTTP请求和响应,比如XML或者JSON。当然,我们需要类路径上的正确库来处理 JSON 和 XML。另外,在请求中添加一些标头,例如 AcceptContent-Type。 这个注解是在4.0版本中引入的.它是一种语法糖注解,因为它使用 @Controller@ResponseBody 进行注解。

第二个是@RequestMapping注解,这个重要的注解负责我们类中的HTTP请求和响应。当我们在类级别使用它时,这段代码的用法非常简单,它将传播给所有方法,并且方法使用它作为相对。 @RequestMapping 注解有不同的用例。它允许我们配置 HTTP 动词、参数和标头。

最后,我们有@GetMapping@PostMapping@DeleteMapping、和 @PutMapping, 这些注解是用正确的 HTTP 动词配置 @RequestMapping 的一种快捷方式;一个优点是这些注释使代码更具可读性。

除了 removeCategory 之外,所有方法都返回 ResponseEntity 类,它使我们能够在接下来处理正确的 HTTP 状态码部分。

UserResource

UserResource class 是 same 作为 CategoryResource,但它使用 User 类。我们可以在 GitHub (https://github.com/PacktPublishing/Spring-5.0-By-Example/tree/master/Chapter02)。

NewsResource

NewsResource class 是必不可少的,这个 endpoint 使用户能够查看news 之前注册过,它还提供了一个端点来返回更新的新闻。这是一个重要特征,因为我们只对相关新闻感兴趣。不相关的新闻不能在门户上显示。资源类应如下所示:

package springfive.cms.domain.resources;

import java.util.Arrays;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import springfive.cms.domain.models.News;
import springfive.cms.domain.models.Review;
import springfive.cms.domain.vo.NewsRequest;

@RestController
@RequestMapping("/api/news")
public class NewsResource {

  @GetMapping(value = "/{id}")
  public ResponseEntity<News> findOne(@PathVariable("id") String id){
    return ResponseEntity.ok(new News());
  }

  @GetMapping
  public ResponseEntity<List<News>> findAll(){
    return ResponseEntity.ok(Arrays.asList(new News(),new News()));
  }

  @PostMapping
  public ResponseEntity<News> newNews(NewsRequest news){
    return new ResponseEntity<>(new News(), HttpStatus.CREATED);
  }

  @DeleteMapping("/{id}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public void removeNews(@PathVariable("id") String id){
  }

  @PutMapping("/{id}")
  public ResponseEntity<News> updateNews(@PathVariable("id") String id,NewsRequest news){
    return new ResponseEntity<>(new News(), HttpStatus.OK);
  }

  @GetMapping(value = "/{id}/review/{userId}")
  public ResponseEntity<Review> review(@PathVariable("id") String id,@PathVariable("userId") String userId){
    return ResponseEntity.ok(new Review());
  }

  @GetMapping(value = "/revised")
  public ResponseEntity<List<News>> revisedNews(){
    return ResponseEntity.ok(Arrays.asList(new News(),new News()));
  }

}

NewsRequest 类可以在 GitHub

注意 HTTP 动词和 HTTP 状态码,因为我们需要遵循正确的语义。

Adding service layer


现在,我们已经为 REST 层准备好了 skeleton,在本节中,我们将开始为我们的应用程序。我们将展示依赖注入如何在底层工作,学习 Spring Framework 上的原型注解,并开始考虑我们的持久性存储,这将在下一节中介绍。

Changes in the model

我们需要对我们的模型进行一些更改,特别是在 News 类。在我们的业务规则中,我们需要保证我们的信息安全,然后我们需要查看所有新闻。我们将添加一些方法来添加用户完成的新评论,并且我们还将添加一个方法来检查新闻是否已被所有强制评论者评论。

Adding a new review

对于这个特性,我们需要在我们的 News 类中创建一个方法,method 将返回一个 Review 应该如下所示:

public Review review(String userId,String status){
final Review review = new Review(userId, status);
  this.reviewers.add(review);
  return review;
}

我们根本不需要检查执行审核操作的用户是否是强制审核者。

Keeping the news safely

此外,我们需要检查 news 是否已被所有强制审阅者完全修改。它 很简单,我们使用的是Java 8,它提供了令人惊叹的 Stream 接口,这使得集合交互比以前更容易。我们开工吧:

public Boolean revised() {
return this.mandatoryReviewers.stream().allMatch(reviewer -> this.reviewers.stream()
      .anyMatch(review -> reviewer.id.equals(review.userId) && "approved".equals(review.status)));
}

谢谢,Java 8,我们很感激。

Before starting the service layer

我们的应用程序需要有一个 persistence 存储,即使应用程序出现故障,我们的记录也可以在其中加载。我们将为我们的存储库创建假实现。   在 第 3 章,使用 Spring Data 和 Reactive 的持久性 时尚 ,我们将介绍 Spring Data 项目,这些项目可帮助开发人员使用出色的 DSL 创建令人惊叹的存储库。现在,我们将创建一些 Spring bean 来将我们的元素存储在内存中,让我们这样做。

CategoryService

让我们从我们最简单的服务开始, CategoryService 类,这个类的预期行为是 CRUD 操作。然后,我们需要一个表示我们的 persistence 存储或存储库实现,目前,我们正在使用临时存储和 < code class="literal">ArrayList 与我们的类别。在下一章中,我们将为我们的 CMS 应用程序添加真正的持久性。

让我们创建我们的第一个 Spring 服务。实现在以下代码段中:

package springfive.cms.domain.service;

import java.util.List;
import org.springframework.stereotype.Service;
import springfive.cms.domain.models.Category;
import springfive.cms.domain.repository.CategoryRepository;

@Service
public class CategoryService {

  private final CategoryRepository categoryRepository;

  public CategoryService(CategoryRepository categoryRepository) {
    this.categoryRepository = categoryRepository;
  }

  public Category update(Category category){
    return this.categoryRepository.save(category);
  }

  public Category create(Category category){
    return this.categoryRepository.save(category);
  }

  public void delete(String id){
    final Category category = this.categoryRepository.findOne(id);
    this.categoryRepository.delete(category);
  }

  public List<Category> findAll(){
    return this.categoryRepository.findAll();
  }

  public Category findOne(String id){
    return this.categoryRepository.findOne(id);
  }

}

这里有一些新东西。这个类将被 Spring 容器检测并实例化,因为它有一个 @Service 注解。正如我们所见,该类没有什么特别之处。它不一定扩展任何类或实现接口。我们在构造函数上收到了 CategoryRepository 这个类将由 Spring 容器提供,因为我们指示容器生成它,但在 Spring 5 中不需要在构造函数中使用 @Autowired 。它之所以有效,是因为我们在该类中只有一个构造函数,而 Spring 会检测到它。此外,我们有几个表示 CRUD 行为的方法,而且很容易理解。

UserService

 UserService 类与 CategoryService非常相似, 但是rules 是关于 User 实体,对于这个实体我们没有什么特别的。我们有 @Service 注解,我们也收到了 UserRepository 构造函数。它非常简单易懂。我们将展示 UserService 实现,它必须是这样的:

package springfive.cms.domain.service;

import java.util.List;
import java.util.UUID;
import org.springframework.stereotype.Service;
import springfive.cms.domain.models.User;
import springfive.cms.domain.repository.UserRepository;
import springfive.cms.domain.vo.UserRequest;

@Service
public class UserService {

  private final UserRepository userRepository;

  public UserService(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  public User update(String id,UserRequest userRequest){
    final User user = this.userRepository.findOne(id);
    user.setIdentity(userRequest.getIdentity());
    user.setName(userRequest.getName());
    user.setRole(userRequest.getRole());
    return this.userRepository.save(user);
  }

  public User create(UserRequest userRequest){
    User user = new User();
    user.setId(UUID.randomUUID().toString());
    user.setIdentity(userRequest.getIdentity());
    user.setName(userRequest.getName());
    user.setRole(userRequest.getRole());
    return this.userRepository.save(user);
  }

  public void delete(String id){
    final User user = this.userRepository.findOne(id);
    this.userRepository.delete(user);
  }

  public List<User> findAll(){
    return this.userRepository.findAll();
  }

  public User findOne(String id){
    return this.userRepository.findOne(id);
  }

}

注意带有 @Service 注解的类声明。这是 Spring 生态系统中非常常见的实现。此外,我们还可以找到 @Component@Repository 注释。 @Service@Component 是服务层通用的,行为上没有区别。 @Repository 稍微改变了一些行为,因为框架将转换数据访问层上的一些异常。

NewsService

这是一个有趣的服务 将负责管理我们新闻的状态。它将像一个 胶水 调用领域模型,在这种情况下, News< /代码> 实体。该服务与其他服务非常相似。我们收到了 NewsRepository 类,一个依赖项并保留了存储库来维护状态,让我们这样做。

 @Service 注解再次出现。这几乎是 Spring 应用程序的标准。此外,我们可以更改为 @Component 注解,但这对我们的应用程序没有任何影响。

Configuring Swagger for our APIs

Swagger 是 document 网络 API 的事实上的工具,该工具允许开发人员对 API 进行建模,创建一种交互式的播放方式使用 API,还提供了一种简单的方法来生成 多种语言的客户端实现。

API 文档是吸引开发人员使用我们的 API 的绝佳方式。

Adding dependencies to pom.xml

在开始配置之前,我们需要添加 required 依赖项。这些依赖项包括我们项目中的 Spring Fox,并提供了许多注释来正确配置 Swagger。让我们添加这些依赖项。

新的依赖项位于 pom.xml 文件中:

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.7.0</version>
</dependency>

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.7.0</version>
</dependency>

第一个依赖是 Swagger 的核心,带有注释和相关的东西。 Spring Fox Swagger UI 依赖项在 HTML 中提供了丰富的接口,允许开发人员与 API 进行交互。

Configuring Swagger

dependencies 已添加,现在我们可以为 Swagger 配置基础架构了。配置非常简单。我们将使用 @Configuration 创建一个类来为 Spring 容器生成 Swagger 配置。我们开始做吧。

看看下面的 Swagger 配置:

package springfive.cms.infra.swagger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

  @Bean
  public Docket documentation() {
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
        .paths(PathSelectors.any())
        .build(); 
  }

}

@Configuration 指示 Spring 为 Swagger 生成 bean 定义。注释@EnableSwagger2 添加了对 Swagger 的支持。 @EnableSwagger2 应该伴随 @Configuration, 它是强制性的。

Docket 类是创建 API 定义的构建器,它为 Spring Swagger MVC 框架的配置提供了合理的默认值和便捷的方法。

方法 .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) 的调用指示框架处理使用 @RestController 注释的类.

自定义 API 文档的方法有很多,例如,有一种方法可以添加身份验证标头。

这就是 Swagger 配置,在下一节中,我们将创建第一个文档化 API。

First documented API

我们将从 CategoryResource 类开始,因为它是 simple 来理解的,我们需要把重点放在技术上。我们将添加几个注释,神奇的事情就会发生,让我们来做个魔术吧。

CategoryResource 类应该是这样的:

package springfive.cms.domain.resources;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import springfive.cms.domain.models.Category;
import springfive.cms.domain.service.CategoryService;
import springfive.cms.domain.vo.CategoryRequest;

@RestController
@RequestMapping("/api/category")
@Api(tags = "category", description = "Category API")
public class CategoryResource {

  private final CategoryService categoryService;

  public CategoryResource(CategoryService categoryService) {
    this.categoryService = categoryService;
  }

  @GetMapping(value = "/{id}")
@ApiOperation(value = "Find category",notes = "Find the Category by ID")
  @ApiResponses(value = {
      @ApiResponse(code = 200,message = "Category found"),
      @ApiResponse(code = 404,message = "Category not found"),
  })
  public ResponseEntity<Category> findOne(@PathVariable("id") String id){
    return ResponseEntity.ok(new Category());
  }

  @GetMapping
@ApiOperation(value = "List categories",notes = "List all categories")
  @ApiResponses(value = {
      @ApiResponse(code = 200,message = "Categories found"),
      @ApiResponse(code = 404,message = "Category not found")
  })
  public ResponseEntity<List<Category>> findAll(){
    return ResponseEntity.ok(this.categoryService.findAll());
  }

  @PostMapping
@ApiOperation(value = "Create category",notes = "It permits to create a new category")
  @ApiResponses(value = {
      @ApiResponse(code = 201,message = "Category created successfully"),
      @ApiResponse(code = 400,message = "Invalid request")
  })
  public ResponseEntity<Category> newCategory(@RequestBody CategoryRequest category){
    return new ResponseEntity<>(this.categoryService.create(category), HttpStatus.CREATED);
  }

  @DeleteMapping("/{id}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
@ApiOperation(value = "Remove category",notes = "It permits to remove a category")
@ApiResponses(value = {
      @ApiResponse(code = 200,message = "Category removed successfully"),
      @ApiResponse(code = 404,message = "Category not found")
  })
  public void removeCategory(@PathVariable("id") String id){
  }

  @PutMapping("/{id}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
@ApiOperation(value = "Update category",notes = "It permits to update a category")
  @ApiResponses(value = {
      @ApiResponse(code = 200,message = "Category update successfully"),
      @ApiResponse(code = 404,message = "Category not found"),
      @ApiResponse(code = 400,message = "Invalid request")
  })
  public ResponseEntity<Category> updateCategory(@PathVariable("id") String id,CategoryRequest category){
    return new ResponseEntity<>(new Category(), HttpStatus.OK);
  }

}

有很多新的注释 需要理解。 @Api 是将此类配置为 Swagger 资源的根注释。有很多配置,但我们将使用标签和描述,因为它们就足够了。

@ApiOperation 描述了我们 API 中的操作,通常针对请求的路径。 value 属性作为 Swagger 上的汇总字段,是操作的简要说明,notes 是对操作的描述一个操作(更详细的内容)。

最后一个是 @ApiResponse,它使开发人员能够描述操作的响应。通常,他们希望配置状态码和消息来描述操作的结果。

Note

在运行应用程序之前,我们应该编译源代码。 可以使用 Maven 命令行使用 mvn clean install, 或通过 IDE 使用 Run Application< /强>。

现在,我们已经配置了 Swagger 集成,我们可以在 Web 浏览器上查看 API 文档。为此,我们需要导航到 http://localhost:8080/swagger-ui.html 这个页面应该会显示出来:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

我们可以看到在我们的 CMS 应用程序中配置的 API 端点。现在,我们来看看我们之前配置的category,点击 显示/隐藏链接。输出应该是:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

如我们所见,我们的Category API中有五个操作,操作有一个路径和一个摘要来帮助理解目的。我们可以单击请求的操作并查看有关操作的详细信息。动手吧,点击List categories查看详细文档。页面如下所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

出色的工作。现在我们有了一个 amazing API 和优秀的文档。做得好。

让我们继续创建我们的 CMS 应用程序。

Integrate with AngularJS


AngularJS 框架已经成为几年来的趋势,社区 非常活跃,该项目是由 Google 创建的。

该框架的主要思想是帮助开发人员处理前端层的复杂性,尤其是在 HTML 部分。 HTML 标记语言是静态的。它是创建静态文档的绝佳工具,但如今它已不再是现代 Web 应用程序的必需品。这些应用程序需要是动态的。世界各地的 UX 团队,努力创造令人惊叹的应用程序,具有不同的效果,这些人试图让应用程序对用户更舒适。

AngularJS 增加了用一些额外的属性和标签来扩展 HTML 的可能性。在本节中,我们将在前端应用程序上添加一些有趣的行为。我们开始做吧。

AngularJS concepts

在我们的 CMS 应用程序中,我们将使用一些 Angular 组件工作。我们将使用 控制器 它将与我们的 HTML 交互并处理某些页面的行为,例如显示错误消息的页面。 Services 负责处理基础设施代码,例如与我们的 CMS API 交互。本书不打算成为 AngularJS 指南。但是,我们将看一些有趣的概念来开发我们的应用程序。

AngularJS 的常用标签有:

  • ng-app
  • ng-controller
  • ng-click
  • ng-hide
  • ng-show

这些标签包含在 AngularJS 框架中。社区创建和维护了更多标签。例如,有一个用于处理 HTML 表单的库,我们将使用它在我们的 CMS 门户中添加动态行为。

Controllers

控制器是 frameworkhandle 应用程序的业务逻辑。它们应该用于控制应用程序中的数据流。控制器通过 ng-controller 指令附加到 DOM。

要向我们的视图添加一些动作,我们需要在控制器上创建函数,方法是创建函数并将它们添加到 $scope 对象中。

控制器不能用于执行 DOM 操作、格式化数据和过滤数据,它被认为是 AngularJS 世界中的最佳实践。

通常,控制器注入服务对象以委托处理业务逻辑。我们将在下一节中了解服务。

Services

服务是在我们的应用程序中处理业务逻辑的对象。在一些 情况下,它们可以用于 处理状态。服务对象是一个单例,这意味着我们在整个应用程序中只有一个实例。

在我们的应用程序中,服务负责与我们基于 Spring Boot 构建的 CMS API 进行交互。让我们这样做。

Creating the application entry point

Spring Boot 框架允许我们服务 静态文件。这些文件应位于以下文件夹之一的类路径中, /static/public/resources, 或 /META-INF/resources

我们将使用 /static 文件夹,在这个文件夹中,我们将放置我们的 AngularJS 应用程序。 有一些标准可以模块化 AngularJS 应用程序文件夹结构,这取决于应用程序大小和要求。我们将使用最简单的风格来保持对 Spring 集成的关注。看项目结构:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

有一些资产可以启动和运行 AngularJS 应用程序。我们将使用内容交付网络 (CDN) 加载 AngularJS 框架,Angular UI-Router 有助于处理我们的 Web 应用程序上的路由,以及帮助开发我们的页面的 Bootstrap 框架。

Note

内容交付网络是分布在世界各地的代理服务器。它使内容具有更高的可用性并提高了性能,因为它将托管在更靠近最终用户的地方。详细说明可以在 CloudFare 页面 (https ://www.cloudflare.com/learning/cdn/what-is-a-cdn/)。

然后我们可以开始配置 我们的AngularJS 应用程序。让我们从开始我们的入口点, index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Spring Boot Security</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body ng-app="cms">

<!-- Header -->
<nav class="navbar navbar-default navbar-fixed-top">
  <div class="container">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">CMS</a>
    </div>
    <div id="navbar" class="collapse navbar-collapse">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Home</a></li>
        <li><a href="#users">Users</a></li>
        <li><a href="#categories">Categories</a></li>
        <li><a href="#news">News</a></li>
      </ul>
    </div>
  </div>
</nav>

<!-- Body -->
<div class="container">
  <div ui-view></div>
</div>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.3/angular-ui-router.js"></script>

<script type="text/javascript" src="app/app.js"></script>

<script type="text/javascript" src="app/controllers.js"></script>
<script type="text/javascript" src="app/services.js"></script>

<script type="text/javascript" src="app/components/categories/category-controller.js"></script>
<script type="text/javascript" src="app/components/categories/category-service.js"></script>

<script type="text/javascript" src="app/components/news/news-controller.js"></script>
<script type="text/javascript" src="app/components/news/news-service.js"></script>

<script type="text/javascript" src="app/components/users/user-controller.js"></script>
<script type="text/javascript" src="app/components/users/user-service.js"></script>

</body>
</html>

这里有一些重要的事情。让我们了解他们。

ng-app 标签是一个用于引导 AngularJS 应用程序的指令。这个标签是应用程序的根元素,通常放在 <body><html> 标签上。

ui-view 标签指示 Angular UI-Router HTML 文档的哪一部分 将由应用程序状态处理,换句话说,指定的部分具有动态行为和变化取决于路由系统。看下面的代码片段:

<!-- Body -->
<div class="container">
  <div ui-view></div>
</div>

这部分代码可以在 index.hml 文件中找到。

ui-view 之后,我们有我们的 JavaScript 文件,第一个是 AngularJS 框架,在这个版本中文件被缩小了。查看我们的 JavaScript 文件,这些文件是在 /static/app/components 文件夹中创建的。看看这里的图片:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》从春天开始-CMS应用程序

第二个是 UI-Router 帮助我们管理路由。最后,我们的 JavaScript 文件配置了 AngularJS 应用程序、我们的控制器以及与我们的 CMS API 交互的服务。

此外,我们还有一些 Bootstrap 类来对齐字段并使设计更容易。 

Creating the Category Controller

现在,我们需要创建我们的控制器。我们将从 simplest 开始,以使示例更易于理解。 CategoryController 负责控制Category实体的数据。有两个控制器,一个使我们能够创建一个类别,另一个列出存储在数据库中的所有类别。

category-controller.js 应该是这样的:

(function (angular) {
'use strict';

// Controllers
angular.module('cms.modules.category.controllers', []).

controller('CategoryCreateController',
['$scope', 'CategoryService','$state',
function ($scope, CategoryService,$state) {

          $scope.resetForm = function () {
            $scope.category = null;
};

$scope.create = function (category) {
            CategoryService.create(category).then(
function (data) {
console.log("Success on create Category!!!")
                  $state.go('categories')
                }, function (err) {
console.log("Error on create Category!!!")
                });
};
}]).

controller('CategoryListController',
['$scope', 'CategoryService',
function ($scope, CategoryService) {
          CategoryService.find().then(function (data) {
            $scope.categories = data.data;
}, function (err) {
console.log(err);
});
}]);
})(angular);

我们创建了一个 AngularJS 模块。它帮助我们保持功能的组织。它对我们来说是一种命名空间。 .controller 函数是创建控制器实例的构造函数。我们收到了一些参数,AngularJS 框架会为我们注入这些对象。

Creating the Category Service

CategoryService 对象是一个 singleton 对象,因为它是一个 AngularJS 服务。该服务将与我们由 Spring Boot 应用程序提供支持的 CMS API 进行交互。

我们将使用 $http 服务。它使 HTTP 通信更容易。

让我们编写 CategoryService

(function (angular) {
'use strict';

/* Services */
</span>angular.module('cms.modules.category.services', []).
service('CategoryService', ['$http',
function ($http) {

var serviceAddress = 'http://localhost:8080';
var urlCollections = serviceAddress + '/api/category';
var urlBase = serviceAddress + '/api/category/';

this.find = function () {
return $http.get(urlCollections);
};

this.findOne = function (id) {
return $http.get(urlBase + id);
};

this.create = function (data) {
return $http.post(urlBase, data);
};

this.update = function (data) {
return $http.put(urlBase + '/id/' + data._id, data);
};

this.remove = function (data) {
return $http.delete(urlBase + '/id/' + data._id, data);
};
}
  ]);
})(angular);

干得好,现在我们已经实现了 CategoryService。 

.service 函数是创建服务实例的构造函数,angular 在幕后起作用。在构造函数上有一个注入,对于服务,我们需要一个 $http 服务来对我们的 API 进行 HTTP 调用。这里有几个 HTTP 方法。注意保持HTTP语义的正确方法。

Summary


在本章中,我们创建了第一个 Spring 应用程序。我们看到了 Spring Initializr,这是一个帮助开发人员创建应用程序骨架的神奇工具。

我们研究了 Spring 如何在幕后工作,以及框架是如何配置几个注解的。现在,我们对 Spring Bootstrap 功能有了基本的了解,我们可以了解框架中存在的依赖注入和组件扫描功能。

这些知识是下一章的基础,现在我们准备开始使用更高级的特性,例如持久性。开始了。下一章见。