Spring的學習與實戰

智慧zhuhuix發表於2020-07-10

目錄

一、Spring起步

  • Spring早已經成為企業級開發的業界標準,尤其是Spring Boot 2.0、Spring 5釋出後,Spring的生態系統引領了技術架構發展的潮流,對於Java開發人員,深入掌握Spring全家桶的各種框架應用及必要的底層原理知識,是一件非常重要的事情。

學習路線圖

在這裡插入圖片描述

Spring的基礎知識

什麼是Spring

Spring的核心是提供了一個容器(container),通常稱為Spring應用上下文(Spring application context),它們會建立和管理應用元件。這些元件也可以稱為bean,會在Spring應用上下文中裝配在一起,從而形成一個完整的應用程式。
在這裡插入圖片描述

將bean裝配在一起的行為是通過一種基於依賴注入(dependency injection,DI)的模式實現的。此時,元件不會再去建立它所依賴的元件並管理它們的生命週期,使用依賴注入的應用依賴於單獨的實體(容器)來建立和維護所有的元件,並將其注入到需要它們的bean中。通常,這是通過構造器引數和屬性訪問方法來實現的。

Spring框架核心模組

在這裡插入圖片描述

SpringBoot

在歷史上,一般通過兩種配置方式為Spring應用上下文提供Bean

  1. 使用一個或多個XML檔案描述bean
  2. 使用@Configuration註解會告知Spring這是一個配置類

隨著Spring Boot 2.x的引入,Spring自動配置的能力已經大大加強,Spring Boot能夠基於類路徑中的條目、環境變數和其他因素合理猜測需要配置的元件並將它們裝配在一起。Java程式設計師儘可能多地使用Spring Boot,只有在必要的時候才使用顯式配置。

第一個Spring應用DEMO
  1. 在IntelliJ IDEA中建立新專案
    在這裡插入圖片描述
  2. 通過地址為https://start.spring.io/初始化專案;
    在這裡插入圖片描述
  3. 指定專案通用資訊:
    在這裡插入圖片描述
  4. 選擇專案Starter:
    在這裡插入圖片描述
  5. 生成的專案結構:
    在這裡插入圖片描述
  6. maven規範
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>

可參見本人部落格《Maven POM( Project Object Model,專案物件模型 )》《一圖說清maven常見要素》這兩篇文章。

  1. 入口類
/**
* SpringBoot應用
*/
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        // 執行應用
        SpringApplication.run(DemoApplication.class, args);
    }

}

@SpringBootApplication是一個組合註解,它組合了3個其他的註解。

  • @SpringBootConfiguration:將該類宣告為配置類。儘管這個類目前還沒有太多的配置,但是後續我們可以按需新增基於Java的Spring框架配置。這個註解實際上是@Configuration註解的特殊形式。
  • @EnableAutoConfiguration:啟用Spring Boot的自動配置。我們隨後會介紹自動配置的更多功能。就現在來說,我們只需要知道這個註解會告訴SpringBoot自動配置它認為我們會用到的元件。
  • @ComponentScan:啟用元件掃描。這樣我們能夠通過@Component@Controller、@Service這樣的註解宣告其他類,Spring會自動發現它們並將它們註冊為Spring應用上下文中的元件。

在這裡插入圖片描述
8. 測試類

// SpringBoot測試
@SpringBootTest
class DemoApplicationTests {

    // 測試方法
    @Test
    void contextLoads() {
    }

}
  • 程式啟動
    在這裡插入圖片描述
編寫自己的第一個SpringMVC例子
  • 第一個Controller

index()是一個簡單的控制器方法。它帶有@GetMapping註解,表明如果針對“/”傳送HTTP GET請求,那麼這個方法將會處理請求。該方法所做的只是返回String型別的index值,該控制器方法中還通過Spring自動注入IndexService服務元件,及呼叫服務元件方法。

/**
 * 第一個SpringMVC程式--Controller層
 *
 * @author zhuhuix
 * @date 2020-07-02
 */
@Controller
public class IndexController {
	// Spring注入服務元件
    @Autowired
    private IndexService indexService;

    @GetMapping("/")
    public String index(Model model) {
        String index = indexService.getIndex();
        model.addAttribute("index", index);
        // 返回檢視 即index.html
        return "index";
    }
}
  • 第一個Service

getIndex()是一個簡單的服務方法。該方法所做的只是返回String型別的index值,該服務元件供控制層呼叫。

/**
 * 第一個SpringMVC程式--Service層
 *  * @author zhuhuix
 * @date 2020-07-02
 */
@Service
public class IndexService {
    public String getIndex() {
        return "hello world!!!";
    }
}
  • 第一個View

-- 使用Thymeleaf模板引擎

### application.properties
###ThymeLeaf配置
spring:
  thymeleaf:
    #模板的模式,支援 HTML, XML TEXT JAVASCRIPT
    mode: HTML5
    #編碼 可不用配置
    encoding: UTF-8
    #內容類別,可不用配置
    content-type: text/html
    #開發配置為false,避免修改模板還要重啟伺服器
    cache: false
    #配置模板路徑,預設是templates,可以不用配置
    prefix: classpath:/templates

-- 定義index.html檢視層

<!DOCTYPE html>
<html  xmlns:th="http://www.thymeleaf.org" >
<head>
     <meta charset="UTF-8"/>
     <title>Title</title>
</head>
 <body>
 <p th:text="${index}" />
 </body>
 </html>
  • 執行測試
    在這裡插入圖片描述
嘗試使用Spring Boot DevTools

•程式碼變更後應用會自動重啟;
•當面向瀏覽器的資源(如模板、JavaScript、樣式表)等發生變化時,會自動重新整理瀏覽器

  • pom.xml
	<dependencies>
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>runtime</scope>
        </dependency>
        ...
	</dependencies>
 	<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <!--fork : devtools生效必須設定成true -->
                    <fork>true</fork>
                </configuration>
            </plugin>
        </plugins>
    </build>
  • idea設定
    -- 需勾選Build project automaticallty
    在這裡插入圖片描述
    -- ctrl+alt+shift+/ :Registry 中第一項必須打勾
    在這裡插入圖片描述

Spring起步小結

  • Spring核心框架:Spring核心框架是Spring領域中一切的基礎。它提供了核心容器和依賴注入框架。
  • Spring Boot:Spring Boot構建在Spring之上,通過簡化依賴管理、自動配置和執行時洞察,使Spring更加易用;
  • Spring MVC:我們通過SpringBoot初始化生成的框架上加入Controller,Service,View的分層,編寫了第一個Spring MVC程式,併成功執行。
  • 使用Idea開發環境,安裝Spring Boot DevTools並進行配置,提高了開發效率。

二、基於SpringMVC開發web應用

. 在上一小節中建立了第一個DEMO,本章將繼續基於SpringMVC框架構建我們的web應用,該應用需要實現使用者登記,具體實現步驟如下:

  1. 建立使用者的資料模型;
  2. 在服務層編寫使用者登記的業務邏輯;
  3. 生成為Web瀏覽器提供使用者登記內容的控制器
  4. 在檢視層運用模板引擎展示資料及校驗表單輸入

在這裡插入圖片描述

建立資料模型

  • 建立一個使用者類,定義使用者id,使用者名稱稱,郵箱三個屬性;
  • 新增預設構造方法與全屬性構造方法;
  • 對使用者屬性新增對應getter,setter方法;
/**
 * 基於SpringMVC框架開發web應用--使用者類
 *
 * @author zhuhuix
 * @date 2020-07-03
 */
public class User implements Serializable {
    // 使用者id
    private Long id;
    // 使用者名稱
    private String name;
    // 郵箱
    private String email;

    User(){ }

    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

建立業務邏輯

  • 實現返回所有使用者資料的列表
  • 實現增加使用者的功能
/**
 * 基於SpringMVC框架開發web應用--使用者服務類
 *
 * @author zhuhuix
 * @date 2020-07-03
 */
@Service
public class UserService {
    public static ArrayList<User> users = new ArrayList<>();

    // mock資料
    public UserService() {
        users.add(new User(1L, "Mike", "mike@gmail.com"));
        users.add(new User(2L, "Jack", "jack@gmail.com"));
        users.add(new User(3L, "Kate", "kate@gmail.com"));
        users.add(new User(4L, "Mary", "mary@gmail.com"));
        users.add(new User(5L, "Rose", "rose@gmail.com"));
    }

    // 返回所有的使用者
    public List<User> listUsers() {
        return users;
    }

    // 增加使用者
    public User saveUser(User user) {
        user.setId(users.size() + 1L);
        users.add(user);
        return user;
    }
}

建立控制器

在Spring MVC框架中,控制器是重要的參與者。它們的主要職責是處理HTTP請求傳遞給檢視以便於渲染HTML(瀏覽器展現)。

  • SpirngMVC的請求註解
註解 描述
@RequestMapping 通用的請求
@GetMapping 處理HTTP GET請示
@PostMapping 處理HTTP POST請示
@PutMapping 處理HTTP PUT請示
@DeleteMapping 處理HTTP DELETE請示
  • 使用者控制器的處理內容
    -- 處理路徑為“/user”的HTTP GET請求,向服務層呼叫返回所有使用者資料列表的介面,獲取資料後傳遞給對應的檢視模板,併傳送給發起請求的Web瀏覽器。
    -- 處理路徑為“/user”的HTTP POST請求,向服務層呼叫增加使用者的介面,處理成功後呼叫路徑為“/user”的HTTP GET請求,併傳送給發起請求的Web瀏覽器。
    -- 處理路徑為“/user/form”的HTTP GET請求,產生一個新使用者資料模型,並呼叫對應的檢視模板,傳送給發起請求的Web瀏覽器。
/**
 * 基於SpringMVC框架開發web應用--使用者控制器
 *
 * @author zhuhuix
 * @date 2020-07-03
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    // 儲存使用者並返回到使用者列表頁面
    @PostMapping
    public ModelAndView saveUser(User user) {
        userService.saveUser(user);
        return new ModelAndView("redirect:/user");//重定向到list頁面
    }

    // 獲取建立使用者表單頁面
    @GetMapping("/form")
    public ModelAndView createForm(Model model) {
        model.addAttribute("user",new User());
        return new ModelAndView("register","userModel",model);
    }

    // 獲取使用者列表頁面
    @GetMapping
    public ModelAndView list(Model model) {
        model.addAttribute("userList", userService.listUsers());
        return new ModelAndView("userlist", "userModel", model);
    }
}

設計檢視模板

  • 設計一個使用者列表的檢視模板
    -- Thymeleaf提供了一個屬性“th:each”,它會迭代一個元素集合,為集合中的每個條目渲染HTML,我們可以利用這個屬性,設計出使用者的列表檢視
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultrag.net.nz/thymeleaf/layout"
>
<head>
    <meta charset="UTF-8">
</head>
<body>
<h3>使用者列表</h3>
<div>
    <a th:href="@{/user/form}">建立使用者</a>
</div>
<table border="1">
    <thead>
    <tr>
        <td>ID</td>
        <td>Email</td>
        <td>Name</td>
    </tr>
    </thead>
    <tbody>
    <tr th:if="${userModel.userList.size()} eq 0">
        <td colspan="3">沒有使用者資訊!</td>
    </tr>
    <tr th:each="user:${userModel.userList}">
        <td th:text="${user.id}"></td>
        <td th:text="${user.email}"></td>
        <td th:text="${user.name}"></td>
    </tr>
    </tbody>
</table>
</body>
</html>
  • 設計一個提交新使用者的檢視模板
    -- 使用者通過這個檢視,錄用名稱與郵箱地址,提交新使用者的資訊。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultrag.net.nz/thymeleaf/layout"
>
<head>
    <meta charset="UTF-8">
</head>
<body>
<h3>登記使用者</h3>
<form action="/users" th:action="@{/user}" method="POST" th:object="${userModel.user}">
    <input type="hidden" name="id" th:value="*{id}">
    名稱:<br>
    <input type="text" name="name" th:value="*{name}">
    <br>
    郵箱:<br>
    <input type="text" name="email" th:value="*{email}">
    <input type="submit" value="提交" >
</form>
</body>
</html>

執行Web應用

在這裡插入圖片描述

  • 到目前為止,我們已經開發了User使用者模型、UserService使用者服務類、UserController控制器、userlist使用者列表檢視模板、register使用者登記檢視模板,接下來我們可以嘗試執行一下。開啟瀏覽器並訪問http://localhost:8080/user。

在這裡插入圖片描述

  • 點選頁面上的建立使用者,登記新使用者,並提交
    在這裡插入圖片描述
    在這裡插入圖片描述
    該web應用一切執行正常。

表單校驗

雖然我們已經實現了使用者列表與登記新使用者,但檢視層還存在漏洞,比如使用者名稱稱為空的時候不能儲存,郵箱輸入格式要符合規則,所以程式要對錶單輸入的內容進行校驗。

  • 引入Validation API與Hibernate的Validation API 依賴
  		<dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.13.Final</version>
        </dependency>

        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
  • 在要被校驗的類上宣告校驗規則:即在User類上宣告校驗規則。
public class User implements Serializable {
    // 使用者id
    @NotNull
    private Long id;
    // 使用者名稱
    @NotBlank(message = "使用者名稱稱不能為空")
    private String name;
    // 郵箱
    @Pattern(message ="郵箱格式不符", regexp = "^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")
    private String email;
    ...
 }
  • 在控制器方法中宣告要進行校驗:即在UserController類的saveUser上增加使用者資料的校驗規則。
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    // 儲存使用者並返回到使用者列表頁面
    @PostMapping
    public ModelAndView saveUser(@Valid User user, Errors errors,Model model) {
        if (errors.hasErrors()){
            model.addAttribute("user",user);
            if (errors.getFieldError("name")!=null) {
                model.addAttribute("nameError", errors.getFieldError("name").getDefaultMessage());
            }
            if (errors.getFieldError("email")!=null) {
                model.addAttribute("emailError", errors.getFieldError("email").getDefaultMessage());
            }
            return new ModelAndView("register","userModel",model);
        }
        userService.saveUser(user);
        //重定向到list頁面
        return new ModelAndView("redirect:/user");
    }
   ...
 }
  • 修改register表單檢視以展現校驗錯誤。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
</head>
<body>
<h3>登記使用者</h3>
<form action="/users" th:action="@{/user}" method="POST" th:object="${userModel.user}">
    <input type="hidden" name="id" th:value="*{id}">
    名稱:<br>
    <input type="text" name="name" th:value="*{name}" >
    <br>
    郵箱:<br>
    <input type="text" name="email" th:value="*{email}">
    <br>
    <input type="submit" value="提交" >
    <div style="color:red" th:text="${nameError}"></div>
    <div style="color:red" th:text="${emailError}"></div>
</form>
</body>
</html>
展現校驗錯誤

在這裡插入圖片描述

基於SpringMVC開發web應用小結

  • Spring提供了一個強大Spring MVC的Web框架。
  • Spring MVC是基於註解的,通過像@RequestMapping、@GetMapping和@PostMapping這樣的註解來啟用請求處理方法的宣告。
  • 請求處理方法返回一個Thymeleaf模板,同時會帶有模型資料。
  • Spring MVC支援表單校驗。

三、實現資料持久化

. 在上一小節中基於SpringMVC框架構建了我們的web應用,並在檢視層運用模板引擎展示資料及校驗表單輸入,本章將使用JdbcTemplate及Spring Data實現資料持久化的操作。

資料庫

  • 一說到資料的持久化,首選方案就是關係型資料庫,本文將使用網際網路領域最常用mysql資料庫。

MySQL 最流行的關係型資料庫管理系統,MySQL所使用的 SQL 語言是用於訪問資料庫的最常用標準化語言,MySQL由於效能高、成本低、可靠性好,已經成為最流行的開源資料庫,因此被廣泛地應用在網際網路領域中。
在這裡插入圖片描述

建立使用者資訊登記表
  • 根據使用者資訊模型類,設計使用者資訊登入表
DROP DATABASE IF EXISTS user_info;

CREATE DATABASE user_info
	DEFAULT CHARACTER SET utf8
	DEFAULT COLLATE utf8_general_ci;

use user_info;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

在這裡插入圖片描述

Web應用專案整合mysql
  • 增加依賴
 <!--Mysql依賴包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>
 <!-- 資料庫連線池:druid資料來源驅動 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
  • Spring配置
#配置資料來源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://user_info?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true

使用JdbcTemplate實現資料持久化

Spring對JDBC的支援要歸功於JdbcTemplate類。JdbcTemplate提供了一種特殊的方式,通過這種方式,開發人員在對關係型資料庫執行SQL操作的時候能夠避免使用JDBC時常見的繁文縟節和樣板式程式碼。

  • 增加依賴
 <!-- JDBC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
  • 修改UserService業務邏輯
/**
 * 基於SpringMVC框架開發web應用--使用者服務類
 *
 * @author zhuhuix
 * @date 2020-07-03
 * @date 2020-07-04 增加通過jdbcTemplate處理資料
 */
@Service
public class UserService {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 返回所有的使用者
    public List<User> listUsers() {
        return jdbcTemplate.query("select id,name,email from user;",
                new Object[]{}, new BeanPropertyRowMapper<>(User.class));
    }

    // 增加使用者
    public int saveUser(User user) {
        return jdbcTemplate.update("insert into  user(name,email) values(?,?);"
                , user.getName(), user.getEmail());
    }

}

再次執行Web應用

  • 我們只修改了UserService使用者服務類;控制器、檢視模板都未做任何改變,接下來我們可以再次嘗試執行一下。開啟瀏覽器並訪問http://localhost:8080/user。
    在這裡插入圖片描述

  • 輸入使用者資訊並提交
    在這裡插入圖片描述
    在這裡插入圖片描述

  • 檢視資料庫使用者資訊表
    在這裡插入圖片描述

實現資料持久化小結

相對於普通的JDBC,Spring的JdbcTemplate能夠極大地簡化關係型資料庫的使用。但是,你會發現使用JPA會更加簡單。接下來我們會繼續通過Spring Data框架讓資料持久化變得更簡單。

四、使用Spring Data實現資料持久化

在上篇文章中,我們使用mysql資料庫與JdbcTemplate簡單實現了資料持久化操作,並對web程式進行了測試,本篇文章將繼續通過Spring Data框架讓資料持久化變得更簡單。

Spring Data

  • Spring Data是一個用於簡化資料庫訪問,並支援雲服務的開源框架。其主要目標是使得資料庫的訪問變得方便快捷,常用的子專案有:

Spring Data JDBC -資料訪問元件對JDBC的支援。

Spring Data JPA:-基於關係型資料庫進行JPA持久化。

Spring Data MongoDB - 持久化到Mongo文件資料庫。

Spring Data Redis-持久化到Redis key-value記憶體資料庫。

Spring Data REST:通過Spring Data資料訪問元件匯出為RESTful資源。

Spring Data Cassandra:持久化到Cassandra資料庫。

四、使用Spring Data JPA持久化資料

  • 本文會基於原JDBC的實現替換為使用SpringData JPA的repository
新增JPA starter依賴
<!--pom.xml-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
修改實體類,新增JPA對映註解
  • 首先我們給原來的user表增加兩個時間欄位
ALTER TABLE user ADD COLUMN create_time DATETIME;
ALTER TABLE user ADD COLUMN update_time DATETIME;
  • 修改user.class
    -- 給user類新增@Entity註解,宣告為JPA實體
    -- 給id欄位新增@Id註解將其指定為資料庫中唯一標識該實體的屬性
    -- 給id欄位新增@GeneratedValue註解,依賴資料庫自動生成ID值
    -- 給其它欄位新增@Column註解,並宣告對應user表中的欄位名稱
    -- 增加createTime成員,新增@CreationTimestamp註解,使用該註解可以讓Hibernate在插入資料時對註解的屬性對應的日期型別建立預設值
    -- 增加updateTime成員,新增@UpdateTimestamp註解,使用該註解可以讓Hibernate在更新資料時對註解的屬性對應的日期型別進行更新
/**
 * 基於SpringMVC框架開發web應用--使用者類
 *
 * @author zhuhuix
 * @date 2020-07-03
 * @date 2020-07-07 新增JPA對映註解
 */
@Entity
public class User implements Serializable {
    // 使用者id
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // 使用者名稱
    @NotBlank(message = "使用者名稱稱不能為空")
    @Column(name="name")
    private String name;
    // 郵箱
    @Column(name="email")
    @Pattern(message ="郵箱格式不符", regexp = "^[A-Za-z0-9\\u4e00-\\u9fa5]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")
    private String email;

    // 建立時間
    @Column(name = "create_time")
    @CreationTimestamp
    private Timestamp createTime;

    // 更新時間
    @Column(name = "update_time")
    @UpdateTimestamp
    private Timestamp updateTime;

    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

宣告JPA repository介面
  • 藉助Spring Data JPA,我們可以通過繼承CrudRepository介面,快速定義應用的資料層。CrudRepository定義並實現了很多用於CRUD(建立、讀取、更新、刪除)操作的方法,我們根本就不用編寫實現類!當應用啟動的時候,Spring DataJPA會在執行期自動生成實現類。這意味著,我們現在就可以在服務層直接設用repository的增刪改查操作了。
/**
 * 基於SpringMVC框架開發web應用--資料操作層
 *
 * @author zhuhuix
 * @date 2020-07-07
 */
public interface UserRepository extends CrudRepository<User,Long> {

}
服務層repository
  • 接下來我們在UserService層直接呼叫UserRepository中由Spring Data JPA提供的各種方法,實現查詢及增加使用者資訊。
/**
 * 基於SpringMVC框架開發web應用--使用者服務類
 *
 * @author zhuhuix
 * @date 2020-07-03
 * @date 2020-07-04 增加通過jdbcTemplate處理資料
 * @date 2020-07-07 將jdbcTemplate處理資料程式改為Spring Data JPA的處理方式
 */
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;


    // 返回所有的使用者
    public List<User> listUsers() {
       return (List<User>) userRepository.findAll();
    }

    // 增加使用者
    public User saveUser(User user) {
        return userRepository.save(user);
    }

}

再次執行Web應用

  • 我們增加了資料層並修改了UserService服務層;控制器、檢視模板都未做任何改變,接下來我們可以再次嘗試執行一下。開啟瀏覽器並訪問http://localhost:8080/user。
    在這裡插入圖片描述

  • 輸入使用者資訊並提交
    在這裡插入圖片描述
    在這裡插入圖片描述

  • 檢視資料庫使用者資訊表
    在這裡插入圖片描述

  • 測試結論:通過將JdbcTemplate替換成Spring Data JPA,同樣實現了使用者資訊的查詢與增加,而且通過JPA CrudRepository介面,我們沒有書寫一行SQL語句,也實現了資料的增加與查詢。

自定義JPA repository

  • 除了CrudRepository提供的基本CRUD操作之外,還需要通過使用者名稱稱或郵箱地址查詢使用者資訊,那我們需要新增如下的方法宣告到UserRepository中:
public interface UserRepository extends CrudRepository<User,Long> {

     // 自定義新增通過使用者名稱稱查詢使用者資訊
    List<User> findByName(String name);
   
}

按照Spring Data的規範的規定,查詢方法以find | read | get開頭(比如 find、findBy、read、readBy、get、getBy),涉及查詢條件時,條件的屬性用條件關鍵字連線,要注意的是:條件屬性以首字母大寫。框架在進行方法名解析時,會先把方法名多餘的字首擷取掉,然後對剩下部分進行解析。
直接在介面中定義查詢方法,如果是符合規範的,可以不用寫實現,即不用寫SQL。

服務層增加查詢介面
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
   ...
    // 根據名稱查詢使用者
    public List<User> searchUser(String name){
        return userRepository.findByName(name);
    }
}
控制器增加使用者查詢功能
/**
 * 基於SpringMVC框架開發web應用--使用者控制器
 *
 * @author zhuhuix
 * @date 2020-07-03
 * @date 2020-07-07 增加使用者查詢
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
   ...
    // 查詢輸入頁面
    @GetMapping("/index")
    public ModelAndView index(Model model) {
        model.addAttribute("user", new User());
        return new ModelAndView("index", "userModel", model);
    }
    
    // 查詢提交併跳轉使用者列表
    @PostMapping("/search")
    public ModelAndView search(@ModelAttribute User user, Model model) {
        model.addAttribute("userList", userService.searchUser(user.getName()));
        return new ModelAndView("userlist", "userModel", model);
    }
}

檢視層增加使用者查詢頁面
<!-- index.html-->
<!DOCTYPE html>
<html  xmlns:th="http://www.thymeleaf.org" >
<head>
     <meta charset="UTF-8"/>
     <title>Title</title>
</head>
 <body>

     <h3>查詢使用者</h3>
     <form action="/users" th:action="@{/user/search}" method="POST" th:object="${userModel.user}">
         名稱:<br>
         <input type="text" name="name" th:value="*{name}" >
         <br>
         <input type="submit" value="查詢" >
     </form>

 </body>
 </html>
查詢功能測試

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

使用Spring Data實現資料持久化小結

  • Spring Data JPA能夠極大地簡化JPA持久化,我們只需編寫repository介面即可;
  • Spirng Data 對於實體類可以通過各種註解進行資料的管理:比如@Id ,@Column,@CreationTimestamp等等
  • 只需要符合JPA的介面命名規則,我們可以自定義JPA repository各種對於資料的操作方法:比如findBy... ,findAllBy...,findBy..And..等。

五、使用Spring Security安全框架保護web應用

  • 在前四篇文章中已經實現了一個非常簡單的使用者郵箱登記的web應用,並將資料儲存到mysql資料庫中。但這個web應用涉及的資料展示、查詢及操作都是開放的,如果部署在網際網路上,可以訪問應用的人員都可隨意增加及查詢資料,這顯然是不符合安全要求的。
    作為軟體開發人員,我們必須採取措施來保護應用程式中的資訊。

啟用Spring Security

Spring Security是一個功能強大且高度可定製的身份驗證和訪問控制框架。它是用於保護基於Spring的應用程式的標準。

本文將通過Spring Security配置實現web應用的如下功能:

  • 實現頁面的WEB安全保護
  • 實現管理員的帳戶註冊
  • 實現登入驗證的完整流程
Spring Security的基本登入認證
  • 在pom.xml檔案中新增依賴
<!-- Spring Security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security </artifactId>
        </dependency>

通過將security starter新增到專案的構建檔案中,我們得到了如下的安全特性:

  • 所有的HTTP請求路徑都需要認證;
  • 不需要特定的角色和許可權;
  • 沒有登入頁面;
  • 認證過程是通過HTTP basic認證對話方塊實現的;
  • 系統只有一個使用者,使用者名稱為user。

我們試著啟動一下應用,並訪問使用者列表頁面,發現程式竟然跳轉到了一個login登入頁面,並要求輸入使用者和密碼。
在這裡插入圖片描述
這是一個HTTP basic認證對話方塊,提示進行認證。要想通過這個認證,需要一個使用者名稱和密碼。使用者名稱為user,而密碼則是隨機生成的,可以在日誌監控介面上查到:
在這裡插入圖片描述
我們試著在登入介面上輸入使用者名稱user與圖中的密碼,進行認證登入:
在這裡插入圖片描述
在這裡插入圖片描述
程式順利通過了密碼認證,並進入到使用者列表介面。但顯然以上的方式是不符合需求的,我們需要的是:

  • 通過自定義的登入頁面來提示管理員使用者進行認證;
  • 提供一個註冊頁面,新管理員使用者能夠註冊;
  • 對不同的請求路徑,執行不同的安全規則。比如註冊頁面不需要進行認證,其他頁面需要進行認證。

使用Spring Security實現自定義使用者認證

增加管理員實體類
-- ----------------------------
-- Table structure for admin
-- ----------------------------
DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `user_password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
/**
 * 基於SpringMVC框架開發web應用--管理員使用者類,用於登入認證
 *
 * @author zhuhuix
 * @date 2020-07-08
 */
@Entity
public class Admin implements UserDetails {

    // 管理員id
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="user_name")
    private String userName;

    @Column(name="user_password")
    private String userPassword;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public String getPassword() {
        return userPassword;
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public void setUserPassword(String userPassword) {
        this.userPassword = userPassword;
    }
}

  • 管理員類實現了Spring Security的UserDetails介面。通過實現UserDetails介面,我們能夠提供更多資訊給框架,比如使用者都被授予了哪些許可權以及使用者的賬號是否可用。
  • getAuthorities()方法返回使用者被授予許可權的一個集合。各種is…Expired()方法要返回一個boolean值,表明使用者的賬號是否可用或過期。
使用Spring Data JPA定義管理員的持久化介面
/**
 * 基於SpringMVC框架開發web應用--管理員資料操作層
 *
 * @author zhuhuix
 * @date 2020-07-08
 */
public interface AdminRepository extends CrudRepository<Admin,Long> {

    // 根據使用者名稱查詢管理員
    Admin findByUserName(String username);
}
建立獲取管理員詳情資訊的服務層
/**
 * 基於SpringMVC框架開發web應用--管理員服務層
 *
 * @author zhuhuix
 * @date 2020-07-08
 */
@Service
public class AdminService implements UserDetailsService {
    private final Logger logger = LoggerFactory.getLogger(Logger.class);
    @Autowired
    private AdminRepository adminRepository;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        Admin admin = adminRepository.findByUserName(s);
        if (admin == null) {
            logger.error("管理員" + s + "未找到");
            throw new UsernameNotFoundException("User " + s + "not found");
        }
        return admin;
    }

 	// 儲存管理員
    public Admin save(Admin admin){
       return adminRepository.save(admin);
    }
}

配置Spring Security
/**
 * Spring Security配置類
 *
 * @author zhuhuix
 * @date 2020-07-08
 */

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AdminService adminService;
    
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {

        authenticationManagerBuilder
                .userDetailsService(adminService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }

}
測試自定義的登入驗證方法

以上通過建立管理員資訊表,及通過JPA定義資料處理層,編寫獲取管理員資訊的服務實現,最後配置Spring Security Web安全類,實現了自定義的登入驗證方法,下面具體來測試一下:
在這裡插入圖片描述
在這裡插入圖片描述
web應用程式已經實現了自定義的使用者登入驗證。

實現管理員的註冊

  • 以上雖然完成了管理員的登入驗證,但沒有涉及管理員的新增,以下需要完成管理員註冊流程。
管理員註冊控制器
/**
 * 基於SpringMVC框架開發web應用--管理員註冊控制器
 *
 * @author zhuhuix
 * @date 2020-07-08
 */
@RestController
@RequestMapping("/admin")
public class AdminController {

    @Autowired
    private AdminService adminService;

   // 管理註冊頁
    @GetMapping
    public ModelAndView registerForm(Model model) {
        model.addAttribute("admin", new Admin());
        return new ModelAndView("registration", "adminModel", model);
    }

     // 提交註冊資訊
    @PostMapping("/register")
    public ModelAndView save(Admin admin) {
        BCryptPasswordEncoder bCryptPasswordEncoder =new BCryptPasswordEncoder();
        admin.setUserPassword(bCryptPasswordEncoder.encode(admin.getPassword()));
        if (adminService.save(admin) != null) {
            return new ModelAndView("redirect:/login ");
        } else {
            return null;
        }
    }
}

管理員註冊頁面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
</head>
<body>
<h3>管理員註冊</h3>
<form action="/admin" th:action="@{/admin/register}" method="POST" th:object="${adminModel.admin}">
    名稱:<br>
    <input type="text" name="userName" th:value="*{userName}" >
    <br>
    輸入密碼:<br>
    <input type="password" name="userPassword" th:value="*{userPassword}">
    <br>
    <input type="submit" value="註冊" >
</form>
</body>
</html>
配置保護Web請求的安全性規則
  • 以上完成了管理員註冊的業務邏輯、控制層、與頁面檢視,但如果我們直接訪問管理員註冊頁面的時候,web應用程式仍然會跳轉到login頁面,要求輸入使用者名稱和密碼,這顯然不符合需求,我們只需要對需要認證的頁面進行登入驗證保護,對於註冊頁面是開放的,所以我們需要配置保護web請求的安全性規則:
/**
 * Spring Security配置類
 *
 * @author zhuhuix
 * @date 2020-07-08
 */

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AdminService adminService;

    // 自定義使用者驗證
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(adminService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }

    // 保護web請求的安全性規則

     @Override
    protected void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
                .antMatchers("/user/**").access("hasRole('ROLE_USER')")
                .antMatchers("/admin/**").permitAll()
                .and().formLogin().defaultSuccessUrl("/user")
                .and().httpBasic();
    }   
}

完整測試

  • 訪問管理員註冊頁面進行註冊
    在這裡插入圖片描述
    在這裡插入圖片描述
  • 使用註冊的管理員帳戶進行登入驗證
    在這裡插入圖片描述
    在這裡插入圖片描述

使用Spring Security安全框架保護web應用小結

  • Spring Security的自動配置是實現基本安全性功能的好辦法,但是大多數的應用都需要自定義安全規則,這樣才能滿足特定的安全需求。
  • 認證使用者詳情資訊可以通過自定義使用者儲存機制進行管理,它的後端可以是關係型資料庫。
  • 注意:以上實現功能有不完全處:如管理員註冊時,需在頁面進行密碼的兩次確認,服務層需判斷帳戶名是否存在等,請大家借鑑使用。

六、Spring的配置屬性

  • 本小節將瞭解Spring的配置屬性及完成自定義的配置

Spring的環境抽象(Environment)

Spring的環境抽象是各種配置屬性的一站式服務。它抽取了原始的屬性,這樣需要這些屬性的bean就可以從Spring本身中獲取了。Spring環境會拉取多個屬性源,包括:

  • JVM系統屬性;
  • 作業系統環境變數;
  • 命令列引數;
  • 應用屬性配置檔案。

在這裡插入圖片描述

通過應用屬性配置檔案完成Spring的環境配置

  • 我們回顧下原web應用中的application.properties檔案:
###資料來源配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/user_info?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true


#thymelea模板配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.cache=false
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
  • Spring Boot引入了具有層次關係的yml格式的配置檔案:
spring:
  port:8080

顯然具有層次關係的配置檔案更易於理解與書寫,接下來我們將使用application.yml取代application.properties完成各種屬性配置。

配置資料來源
###資料來源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    username: root
    password: root
    driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
    url: jdbc:log4jdbc:mysql://localhost:3306/user_info?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true

配置模板引擎
#thymelea模板配置
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    mode: HTML5
    encoding: UTF-8
    content-type: text/html
    cache: false
配置日誌
  • 預設情況下,Spring Boot通過Logback配置日誌,日誌會以INFO級別寫入到控制檯中,我們希望重新配置顯示日誌的格式;
  • 同時我們希望通過監控sql日誌輸出到控制檯,並將輸出的資訊進行篩選列印;
  • 首先需引入log4jdbc依賴
		 <dependency>
            <groupId>org.bgee.log4jdbc-log4j2</groupId>
            <artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
            <version>1.16</version>
        </dependency>
  • 其次生成配置檔案log4jdbc.log4j2.properties:
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
log4jdbc.auto.load.popular.drivers=false
log4jdbc.drivers=com.mysql.cj.jdbc.Driver
  • 最後建立一個logback.xml檔案完成配置:
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds" debug="false">
    <contextName>web demo</contextName>
    <property name="log.charset" value="utf-8" />
    <property name="log.pattern" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}) - %gray(%msg%n)" />

    <!--輸出到控制檯-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
            <charset>${log.charset}</charset>
        </encoder>
    </appender>

    <!--普通日誌輸出到控制檯-->
    <root level="info">
        <appender-ref ref="console" />
    </root>

    <!--監控sql日誌輸出 -->
    <logger name="jdbc.sqlonly" level="INFO" additivity="false">
        <appender-ref ref="console" />
    </logger>

    <logger name="jdbc.resultset" level="ERROR" additivity="false">
        <appender-ref ref="console" />
    </logger>

    <!--  如想看到表格資料,將OFF改為INFO  -->
    <logger name="jdbc.resultsettable" level="OFF" additivity="false">
        <appender-ref ref="console" />
    </logger>

    <logger name="jdbc.connection" level="OFF" additivity="false">
        <appender-ref ref="console" />
    </logger>

    <logger name="jdbc.sqltiming" level="OFF" additivity="false">
        <appender-ref ref="console" />
    </logger>

    <logger name="jdbc.audit" level="OFF" additivity="false">
        <appender-ref ref="console" />
    </logger>
</configuration>
  • 輸出格式如下:
    在這裡插入圖片描述

建立自定義的配置屬性

為了支援配置屬性的注入,Spring Boot提供了@ConfigurationProperties註解。將它放到Spring 應用上下文的 bean之後,它就會為該bean中那些能夠根據Spring環境注入值的屬性賦值。

  • 我們希望給管理員註冊時,新增一個應用的預設密碼,假設使用者註冊時,不輸入密碼,web應用就以這個預設密碼進行註冊,並寫入資料庫。
在application.yml中加入配置
#管理員預設密碼
admin:
  password: 123456
編寫配置類
  • 使用 @ConfigurationProperties 配置模組
  • 過新增 @Component 註解讓 Component Scan 掃描到
/**
 * 基於SpringMVC框架開發web應用--管理員使用者預設金鑰
 *
 * @author zhuhuix
 * @date 2020-07-09
 */
@Component
@ConfigurationProperties(prefix = "admin")
public class DefaultPasswordProperties {
    private String password;

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
使用自定義的配置屬性
/**
 * 基於SpringMVC框架開發web應用--管理員註冊控制器
 *  * @author zhuhuix
 * @date 2020-07-08
 * @date 2020-07-09 註冊密碼為空時使用自定義的預設密碼屬性
 */
@RestController
@RequestMapping("/admin")
public class AdminController {
    @Autowired
    private AdminService adminService;
    @Autowired
    private DefaultPasswordProperties defaultPasswordProperties;

    // 管理註冊頁
    @GetMapping
    public ModelAndView registerForm(Model model) {
        model.addAttribute("admin", new Admin());
        return new ModelAndView("registration", "adminModel", model);
    }

    // 提交註冊資訊
    @PostMapping("/register")
    public ModelAndView save(Admin admin) {
        // 如果註冊密碼為空,則賦值預設密碼
        if (StringUtils.isEmpty(admin.getPassword())) {
            admin.setUserPassword(defaultPasswordProperties.getPassword());
        }
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        admin.setUserPassword(bCryptPasswordEncoder.encode(admin.getPassword()));
        if (adminService.save(admin) != null) {
            return new ModelAndView("redirect:/login ");
        } else {
            return null;
        }
    }
}
屬性配置測試
  • 註冊admin2管理員,密碼保持為空,並提交
    在這裡插入圖片描述
    在這裡插入圖片描述
    在這裡插入圖片描述
  • 用admin2管理員,密碼為123456進行登入
    在這裡插入圖片描述
  • 登入成功
    在這裡插入圖片描述

Spring的配置屬性小結

  • Spring的配置屬性可以通過命令列引數、環境變數、JVM系統屬性、屬性檔案或YAML檔案等方式進行設定。
  • Spring的配置屬性可以用來覆蓋自動配置相關的設定,包括指定資料來源URL和日誌級別。
  • Spring的配置屬性可以新增@ConfigurationProperties註解,這樣就能夠從多個屬性源中選取一個來注入它的值。

七、Spring整合REST API服務

本節將進入到新的單元:Spring與應用的整合,今天先實現整合REST API服務。

REST API服務

微服務架構,前後端分離目前已成為網際網路專案開發的業界標準,其核心思想就是前端(APP、小程式、H5頁面等)通過呼叫後端的API介面,提交及返回JSON資料進行互動。

  • 移動裝置、平板電腦、智慧裝置已經非常常見,許多應用程式採用了一種通用的設計,那就是將使用者介面推到更接近客戶端的地方,而讓伺服器公開API,通過這種API,各種客戶端都能與後端功能進行互動。
  • REST API(REpresentation State Transfer Application Programming Interface):通過URL定位資源,用HTTP動詞(GET,POST,DELETE,PUSH等)描述操作,與後端服務進行互動。

Spring整合REST API服務

  • 在前幾篇文章中我們用了模板引擎開發了多頁應用(MultiPage Application,MPA),我們將在原有基礎上按以下步驟實現整合API服務:

  • 建立使用者管理的Restful Api(UserRestfulApi.java)的介面,實現增加、刪除、修改、查詢使用者資訊的API互動服務;

  • 整合Swagger2,對上述API介面進行測試。

用Spring MVC實現使用者管理Restful Api
  • Controller
/**
 * 基於SpringMVC框架開發web應用--使用者restful api
 * 增加、刪除、修改、查詢使用者資訊的API互動服務
 *
 * @author zhuhuix
 * @date 2020-07-10
 */
@RestController
@RequestMapping("/user/api")
public class UserRestfulApi {
    @Autowired
    private UserService userService;

    // 增加使用者資訊
    @PostMapping
    public ResponseEntity<User> addUser(User user) {
        return ResponseEntity.ok(userService.saveUser(user));
    }

    // 根據id刪除使用者
    @DeleteMapping("/{id}")
    public ResponseEntity deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.ok(HttpStatus.OK);
    }

     // 根據id修改使用者
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@RequestBody User user) {
        return ResponseEntity.ok(userService.saveUser(user));
    }

    // 根據id查詢使用者
    @GetMapping("/{id}")
    public ResponseEntity<User> findUser(@PathVariable Long id) {
        return ResponseEntity.ok(userService.findUser(id));
    }
}
  • Service
/**
 * 基於SpringMVC框架開發web應用--使用者服務類
 *
 * @author zhuhuix
 * @date 2020-07-03
 * @date 2020-07-04 增加通過jdbcTemplate處理資料
 * @date 2020-07-07 將jdbcTemplate處理資料程式改為Spring Data JPA的處理方式
 * @date 2020-07-10 增加
 */
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;


    // 返回所有的使用者
    public List<User> listUsers() {
       return (List<User>) userRepository.findAll();
    }

    // 儲存使用者
    public User saveUser(User user) {
        return userRepository.save(user);
    }

    // 刪除使用者
    public void deleteUser(Long id){
        userRepository.deleteById(id);
    }

    // 查詢使用者
    public User findUser(Long id){
       return userRepository.findById(id).get();
    }

    // 根據名稱查詢使用者
    public List<User> searchUser(String name){
        return userRepository.findByName(name);
    }

}
  • Repository
/**
 * 基於SpringMVC框架開發web應用--資料操作層
 *
 * @author zhuhuix
 * @date 2020-07-07
 */
public interface UserRepository extends CrudRepository<User,Long> {

    // 自定義新增通過使用者名稱稱查詢使用者資訊
    List<User> findByName(String name);

}
Spring 整合Swagger2

Swagger2 作為一個規範和完整的框架,可以用於生成、描述、呼叫和視覺化 RESTful 風格的 Web 服務:
1、 介面文件線上自動生成,文件隨介面變動實時更新,節省維護成本
2、 支援線上介面測試,不依賴第三方工具

  • 新增maven依賴
 <!-- RESTful APIs swagger2 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
            <exclusions>
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-annotations</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-models</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.21</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.21</version>
        </dependency>
  • 建立 Swagger2配置類
/**
 * Swagger2配置類
 *
 * @author zhuhuix
 * @date 2020-07-10
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    @SuppressWarnings("all")
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .enable(true)
                .apiInfo(apiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Restful Api介面測試")
                .version("1.0")
                .build();
    }
}

在這裡插入圖片描述

API介面進行測試
  • 在測試之前,為防止post、put、delete請求出現 403 Forbidden ,需要禁用跨域請求的安全驗證
/**
 * Spring Security配置類
 *
 * @author zhuhuix
 * @date 2020-07-08
 * @date 2020-07-10 禁用跨域請求的安全驗證
 */

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AdminService adminService;

    // 自定義使用者驗證
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(adminService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }

    // 保護web請求的安全性規則

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
                .antMatchers("/user/**").access("hasRole('ROLE_USER')")
                .antMatchers("/admin/**").permitAll()
                .and().formLogin().defaultSuccessUrl("/user")
                .and().httpBasic()
                // 禁用 CSRF
                .and().csrf().disable();
    }
}
增加使用者(HTTP POST請求)

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

查詢使用者(HTTP GET請求)

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

修改使用者(HTTP PUT請求)

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

刪除使用者(HTTP DELETE請求)

在這裡插入圖片描述
在這裡插入圖片描述
在這裡插入圖片描述

Spring整合REST API服務小結

至此我們完成了Spring整合restful api服務,並通過整合Swagger2,簡單直觀地對http的各種請求進行了完整地測試,下面做個總結:

  • Rest端點可以通過Spring MVC來建立,這裡的控制器與面向瀏覽器的控制器遵循相同的程式設計模型。
  • @RestController註解簡化了REST控制器,使用它的話,處理器方法中就不需要新增@ResponseBody註解了。
  • Restful Api一般會新增JWT認證機制進行安全驗證,具體可參見《SpringBoot整合SpringSecurity實現JWT認證》。

相關文章