十二时辰手把手教你入门Spring

背景

本文章通过实战的方式掌握Spring的企业级开发功能,适合刚开始学习Spring框架的Java开发人员快速上手。

  • 第1时辰、Spring起步
  • 第2时辰、基于SpringMVC开发web应用
  • 第3时辰、实现数据持久化
  • 第4时辰、使用Spring Data实现数据持久化
  • 第5时辰、使用Spring Security安全框架保护web应用
  • 第6时辰 、Spring的配置属性
  • 第7时辰、Spring集成REST API服务
  • 第8时辰、Spring集成JavaMailSender实现邮件发送
  • 第9时辰、Spring集成RabbitMQ实现异步消息处理
  • 第10时辰、Spring Integration集成模式实现企业ESB
  • 第11时辰、Spring Boot集成MyBatis实现增删改查
  • 第12时辰、集成Mongodb与WebFlux实现反应式API

一、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视图层


<html  xmlns:th="http://www.thymeleaf.org" >
<head>
     <meta charset="UTF-8"/>
     <title>Titletitle>
head>
 <body>
 <p th:text="${index}" />
 body>
 html>
  • 运行测试
    在这里插入图片描述
尝试使用Spring Boot DevTools

•代码变更后应用会自动重启;
•当面向浏览器的资源(如模板、JavaScript、样式表)等发生变化时,会自动刷新浏览器

  • pom.xml
	<dependencies>
		<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <optional>trueoptional>
            <scope>runtimescope>
        dependency>
        ...
	dependencies>
 	<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    
                    <fork>truefork>
                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", "[email protected]"));
        users.add(new User(2L, "Jack", "[email protected]"));
        users.add(new User(3L, "Kate", "[email protected]"));
        users.add(new User(4L, "Mary", "[email protected]"));
        users.add(new User(5L, "Rose", "[email protected]"));
    }

    // 返回所有的用户
    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,我们可以利用这个属性,设计出用户的列表视图

<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>IDtd>
        <td>Emailtd>
        <td>Nametd>
    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>
  • 设计一个提交新用户的视图模板
    – 用户通过这个视图,录用名称与邮箱地址,提交新用户的信息。

<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 依赖
  		>
            >org.hibernate.validator>
            >hibernate-validator>
            >6.0.13.Final>
        >

        >
            >javax.validation>
            >validation-api>
            >2.0.1.Final>
        >
  • 在要被校验的类上声明校验规则:即在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表单视图以展现校验错误。

<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依赖包-->
        
            mysql
            mysql-connector-java
            5.1.47
            runtime
        
 <!-- 数据库连接池:druid数据源驱动 -->
        
            com.alibaba
            druid-spring-boot-starter
            1.1.10
        
  • 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 -->
        
            org.springframework.boot
            spring-boot-starter-jdbc
        
  • 修改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依赖

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        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。
    在这里插入图片描述

  • 输入用户信息并提交
    在这里插入图片描述
    十二时辰手把手教你入门Spring_第1张图片

  • 查看数据库用户信息表
    十二时辰手把手教你入门Spring_第2张图片

  • 测试结论:通过将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);
    }
}

视图层增加用户查找页面


<html  xmlns:th="http://www.thymeleaf.org" >
<head>
     <meta charset="UTF-8"/>
     <title>Titletitle>
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_第3张图片
十二时辰手把手教你入门Spring_第4张图片
十二时辰手把手教你入门Spring_第5张图片
十二时辰手把手教你入门Spring_第6张图片

使用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文件中添加依赖

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-security artifactId>
        dependency>

通过将security starter添加到项目的构建文件中,我们得到了如下的安全特性:

  • 所有的HTTP请求路径都需要认证;
  • 不需要特定的角色和权限;
  • 没有登录页面;
  • 认证过程是通过HTTP basic认证对话框实现的;
  • 系统只有一个用户,用户名为user。

我们试着启动一下应用,并访问用户列表页面,发现程序竟然跳转到了一个login登录页面,并要求输入用户和密码。
十二时辰手把手教你入门Spring_第7张图片
这是一个HTTP basic认证对话框,提示进行认证。要想通过这个认证,需要一个用户名和密码。用户名为user,而密码则是随机生成的,可以在日志监控界面上查到:
十二时辰手把手教你入门Spring_第8张图片
我们试着在登录界面上输入用户名user与图中的密码,进行认证登录:
十二时辰手把手教你入门Spring_第9张图片
十二时辰手把手教你入门Spring_第10张图片
程序顺利通过了密码认证,并进入到用户列表界面。但显然以上的方式是不符合需求的,我们需要的是:

  • 通过自定义的登录页面来提示管理员用户进行认证;
  • 提供一个注册页面,新管理员用户能够注册;
  • 对不同的请求路径,执行不同的安全规则。比如注册页面不需要进行认证,其他页面需要进行认证。

使用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安全类,实现了自定义的登录验证方法,下面具体来测试一下:
十二时辰手把手教你入门Spring_第11张图片
十二时辰手把手教你入门Spring_第12张图片
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;
        }
    }
}

管理员注册页面

<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_第13张图片
    十二时辰手把手教你入门Spring_第14张图片
  • 使用注册的管理员帐户进行登录验证
    十二时辰手把手教你入门Spring_第15张图片
    十二时辰手把手教你入门Spring_第16张图片

使用Spring Security安全框架保护web应用小结

  • Spring Security的自动配置是实现基本安全性功能的好办法,但是大多数的应用都需要自定义安全规则,这样才能满足特定的安全需求。
  • 认证用户详情信息可以通过自定义用户存储机制进行管理,它的后端可以是关系型数据库。
  • 注意:以上实现功能有不完全处:如管理员注册时,需在页面进行密码的两次确认,服务层需判断帐户名是否存在等,请大家借鉴使用。

六、Spring的配置属性

  • 本小节将了解Spring的配置属性及完成自定义的配置

Spring的环境抽象(Environment)

Spring的环境抽象是各种配置属性的一站式服务。它抽取了原始的属性,这样需要这些属性的bean就可以从Spring本身中获取了。Spring环境会拉取多个属性源,包括:

  • JVM系统属性;
  • 操作系统环境变量;
  • 命令行参数;
  • 应用属性配置文件。

十二时辰手把手教你入门Spring_第17张图片

通过应用属性配置文件完成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-log4j2groupId>
            <artifactId>log4jdbc-log4j2-jdbc4.1artifactId>
            <version>1.16version>
        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文件完成配置:

<configuration scan="true" scanPeriod="30 seconds" debug="false">
    <contextName>web democontextName>
    <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>

    
    <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>

    
    <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_第18张图片

创建自定义的配置属性

为了支持配置属性的注入,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管理员,密码保持为空,并提交
    十二时辰手把手教你入门Spring_第19张图片
    在这里插入图片描述
    在这里插入图片描述
  • 用admin2管理员,密码为123456进行登录
    十二时辰手把手教你入门Spring_第20张图片
  • 登录成功
    十二时辰手把手教你入门Spring_第21张图片

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依赖
 
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger2artifactId>
            <version>2.9.2version>
            <exclusions>
                <exclusion>
                    <groupId>io.swaggergroupId>
                    <artifactId>swagger-annotationsartifactId>
                exclusion>
                <exclusion>
                    <groupId>io.swaggergroupId>
                    <artifactId>swagger-modelsartifactId>
                exclusion>
            exclusions>
        dependency>
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger-uiartifactId>
            <version>2.9.2version>
        dependency>
        <dependency>
            <groupId>io.swaggergroupId>
            <artifactId>swagger-annotationsartifactId>
            <version>1.5.21version>
        dependency>
        <dependency>
            <groupId>io.swaggergroupId>
            <artifactId>swagger-modelsartifactId>
            <version>1.5.21version>
        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();
    }
}

十二时辰手把手教你入门Spring_第22张图片

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请求)

十二时辰手把手教你入门Spring_第23张图片
十二时辰手把手教你入门Spring_第24张图片
在这里插入图片描述

查找用户(HTTP GET请求)

十二时辰手把手教你入门Spring_第25张图片
十二时辰手把手教你入门Spring_第26张图片
在这里插入图片描述

修改用户(HTTP PUT请求)

十二时辰手把手教你入门Spring_第27张图片
十二时辰手把手教你入门Spring_第28张图片
在这里插入图片描述

删除用户(HTTP DELETE请求)

十二时辰手把手教你入门Spring_第29张图片
十二时辰手把手教你入门Spring_第30张图片
在这里插入图片描述

Spring集成REST API服务小结

至此我们完成了Spring集成restful api服务,并通过集成Swagger2,简单直观地对http的各种请求进行了完整地测试,下面做个总结:

  • Rest端点可以通过Spring MVC来创建,这里的控制器与面向浏览器的控制器遵循相同的编程模型。
  • @RestController注解简化了REST控制器,使用它的话,处理器方法中就不需要添加@ResponseBody注解了。
  • Restful Api一般会添加JWT认证机制进行安全验证,具体可参见《SpringBoot整合SpringSecurity实现JWT认证》。

八、Spring集成JavaMailSender实现邮件发送

JavaMailSender

Spring框架提供了一种使用JavaMailSender接口发送电子邮件的简单抽象方法,而Spring Boot为其提供了自动配置以及启动程序模块。

  • JavaMailSender接口具有特殊的JavaMail功能,例如MIME消息支持。
public interface JavaMailSender extends MailSender {
    MimeMessage createMimeMessage();

    MimeMessage createMimeMessage(InputStream var1) throws MailException;

    void send(MimeMessage var1) throws MailException;

    void send(MimeMessage... var1) throws MailException;

    void send(MimeMessagePreparator var1) throws MailException;

    void send(MimeMessagePreparator... var1) throws MailException;
}

Spring集成邮件发送功能

Spirng实现邮件发送功能,需要以下步骤:
1. 添加maven依赖
2. 添加Spring邮件配置
3. 创建邮件管理Bean并注入Spring应用上下文
4. 修改业务逻辑,调用邮件发送功能
1. 添加maven依赖
 	
 	<dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-mailartifactId>
        dependency>
    dependencies>
2. 添加Spring邮件配置
### application.yml
spring:
#mail配置
  mail:
    host: smtp.163.com
    username: [email protected]
    password: 自行设置邮箱密码
    default-encoding: UTF-8
3. 创建邮件管理Bean并注入Spring应用上下文
/**
 * 邮件发送Bean
 *
 * @author zhuhuix
 * @date 2020-07-13
 */
@Service
@Component
public class MailManager {
    private final org.slf4j.Logger logger = LoggerFactory.getLogger(Logger.class);
    // 发件人
    @Value("${spring.mail.username}")
    private String from;

    @Autowired
    private JavaMailSender javaMailSender;

    /**
     * 普通文本邮件发送
     *
     * @param to      收件人
     * @param subject 主题
     * @param text    内容
     */
    public void sendSimpleMail(String to, String subject, String text) {
        SimpleMailMessage msg = new SimpleMailMessage();
        msg.setFrom(this.from);
        msg.setTo(to);
        msg.setSubject(subject);
        msg.setText(text);
        try {
            this.javaMailSender.send(msg);
            logger.info(msg.toString());
        } catch (MailException ex) {
            logger.error(ex.getMessage());
        }

    }

    /**
     * HTML邮件发送
     *
     * @param to      收件人
     * @param subject 主题
     * @param text    内容
     */
    public void sendHTMLMail(String to, String subject, String text) {
        try {
            MimeMessage msg = javaMailSender.createMimeMessage();
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(msg, true);
            mimeMessageHelper.setFrom(this.from);
            mimeMessageHelper.setTo(to);
            mimeMessageHelper.setSubject(subject);
            mimeMessageHelper.setText(text, true);
            this.javaMailSender.send(msg);
            logger.info("to=" + to + "," + "subject=" + subject + "," + "text=" + text);
        } catch (MailException ex) {
            logger.error(ex.getMessage());
        } catch (MessagingException ex) {
            logger.error(ex.getMessage());
        }

    }

    /**
     * 发送带有附件的邮件
     * @param to 收件人
     * @param subject 主题
     * @param text 内容
     * @param filePath 附件
     */
    public void sendAttachmentMail(String to, String subject, String text, String filePath) {
        try {
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();
            MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
            mimeMessageHelper.setFrom(from);
            mimeMessageHelper.setTo(to);
            mimeMessageHelper.setSubject(subject);
            mimeMessageHelper.setText(text, true);
            FileSystemResource fileSystemResource = new FileSystemResource(new File(filePath));
            String fileName = fileSystemResource.getFilename();
            mimeMessageHelper.addAttachment(fileName, fileSystemResource);
            javaMailSender.send(mimeMessage);
            logger.info("to=" + to + "," + "subject=" + subject + "," + "text=" + text);
        } catch (MailException ex) {
            logger.error(ex.getMessage());
        } catch (MessagingException ex) {
            logger.error(ex.getMessage());
        }
    }

}
4. 修改业务逻辑,调用邮件发送功能
  • 业务流程图
    十二时辰手把手教你入门Spring_第31张图片
  • 业务逻辑
/**
 * 基于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 增加删除deleteUser和查找findUser
 * @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
 */
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private MailManager mailManager;

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

    // 保存用户
    public User saveUser(User user) {
        boolean firstRegister = false;
        if ((user.getId() == null || user.getId().equals(0L))) {
            firstRegister = true;
        }
        // 首次保存用户成功后发送通知邮件
        if (userRepository.save(user) != null && firstRegister == true) {
            sendMail(user);
        }
        return user;
    }

    // 发送邮件
    private void sendMail(User user) {
        // 发送文本邮件
        String text = "您的邮箱信息已登记!";
        mailManager.sendSimpleMail(user.getEmail(), "用户通知(文本邮件)", text);

        // 发送HTML邮件
        String content = "\n" +
                "\n" +
                "

" + text + "

\n"
+ "\n" + ""; mailManager.sendHTMLMail(user.getEmail(), "用户通知(HTML邮件)", content); // 发送带有附件的邮件 String attachFilePath = "c:\\csdn-logo.png"; mailManager.sendAttachmentMail(user.getEmail(), "用户通知(带有附件的邮件)", content, attachFilePath); } // 删除用户 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); } }

邮件发送功能测试

  • 登记用户
    十二时辰手把手教你入门Spring_第32张图片
    十二时辰手把手教你入门Spring_第33张图片
  • 登录邮箱查看
    十二时辰手把手教你入门Spring_第34张图片
    十二时辰手把手教你入门Spring_第35张图片
    十二时辰手把手教你入门Spring_第36张图片
    十二时辰手把手教你入门Spring_第37张图片

Spring集成JavaMailSender实现邮件发送小结

以上我们通过JavaMailSender接口实现了文本、超文本及带有附件的邮件的发送功能。
在书写这些程序时,采用了硬编码,可能会碰到如下问题:

  • 用Java代码创建基于HTML的电子邮件内容很繁琐且容易出错。
  • UI和业务逻辑之间没有明确区分。
  • 更改电子邮件内容及重新排列UI时,需要编写Java代码,重新编译,重新部署。
    解决这些问题的方法是使用模板库(例如我们已经用到的thymelea或者freemaker),当需要发送的邮件的内容变得相当复杂时,就变得非常必要,读者可自行尝试。


九、Spring集成RabbitMQ实现异步消息处理

RabbitMQ

RabbitMQ可以说是AMQP(Advanced Message Queue,高级消息队列协议)最杰出的实现。

RabbitMQ的基本概念

十二时辰手把手教你入门Spring_第38张图片

概念 描述
发送者 消息的生产者,也可以是一个向交换器发布消息的客户端应用程序
接收者 消息的消费者,也可以认为是向消息队列接收消息的服务端程序
Exchange(交换器) 用来接收发送者发送的消息并将这些消息路由给服务器中的队列
Binding (绑定) 用于消息队列和交换器之间的关联
队列 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。
Binding key 在绑定(Binding)Exchange与Queue的同时,一般会指定一个Binding key;Routing key结合Exchange可实现路由规则。
Routing key 通过指定Routing key,结合Exchange和Routing key,可以决定消息流向哪里。
  • RabbitMQ还有象Channel 信道、Virtual Host 虚拟主机、Broker 消息队列服务器实体等概念,请读者自行研究。
RabbitMQ的消息路由走向
  • RabbitMQ的消息路由走向由Exchange的类型决定;分发消息时根据Exchange类型的不同分发策略有区别,见下表:
类型 描述
Direct 如果消息的routing key与队列的binding key相同,那么消息将会路由到该队列上。
Topic 如果消息的routing key与队列binding key(可能会包含通配符)匹配,那么消息将会路由到一个或多个这样的队列上。
Fanout 不管routing key和binding key是什么,消息都将会路由到所有绑定队列上。
Headers 与Topic Exchange类似,只不过要基于消息的头信息进行路由,而不是routing key。

十二时辰手把手教你入门Spring_第39张图片

本文只对Direct模型进行展开处理,其他类型请读者自行研究。关于如何绑定队列到Exchange的更详细的描述,可以参考Alvaro Videla和Jason J.W. Williams编写的RabbitMQ in Action (RabbitMQ实战)。

Spring集成RabbitMQ实现异步消息处理

十二时辰手把手教你入门Spring_第40张图片

1. 添加maven依赖
 
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>
2. Spring添加RabbitMQ配置
### application.yml
spring:
#RabbitMQ配置
  rabbitmq:
    host: 192.168.0.1
    port: 5672
    username: rabbitmq
    password: rabbitmq
    virtual-host: /
    connection-timeout: 10000
    listener:
      simple:
        acknowledge-mode: auto # 自动应答
        auto-startup: true
        default-requeue-rejected: false # 不重回队列
        concurrency: 5
        max-concurrency: 20
        prefetch: 1 # 每次只处理一个信息
        retry:
          enabled: false
    template:
      exchange: web.demo
      routing-key: user.key

十二时辰手把手教你入门Spring_第41张图片

3. 创建RabbitMQ配置类
/**
 * rabbitmq 配置类
 *
 * @author zhuhuix
 * @date 2020-07-14
 */
@Configuration(value = "rabbitMQConfig")
public class RabbitMQConfig {

    // 获取exchange和routing-key定义
    @Value("${spring.rabbitmq.template.exchange}")
    private  String exchange;
    @Value("${spring.rabbitmq.template.routing-key}")
    private String routingKey;

    public String getExchange() {
        return exchange;
    }

    public String getRoutingKey() {
        return routingKey;
    }

    // 自定义消息转换器
    @Bean
    public MessageConverter messageConverter() {

        return new SimpleMessageConverter() {
            @Override
            protected Message createMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
                Message message = super.createMessage(object, messageProperties);
                return message;
            }
        };
    }
}

4. 创建接收消息监听程序
  • 监听消息队列,收到完整消息后,调用邮件发送程序
/**
 * rabbitmq 接收器
 *
 * @author zhuhuix
 * @date 2020-07-14
 */
@Component
public class RabbitMQReceiver {
    private final org.slf4j.Logger logger = LoggerFactory.getLogger(Logger.class);
	// 邮件集成请参考上篇文章《Spring全家桶的深入学习(八):Spring集成JavaMailSender实现邮件发送》
    @Autowired
    private MailManager mailManager;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 监听消息队列
    @RabbitHandler
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange("#{rabbitMQConfig.getExchange()}"),
            key = "#{rabbitMQConfig.getRoutingKey()}",
            value = @Queue("user.queue")))
    public void receiveMessage(Message message) {
        try {
            User user = (User) rabbitTemplate.getMessageConverter().fromMessage(message);
            logger.info("接收到消息:[{}]", user.toString());
            // 收到完整消息后,调用邮件发送程序,发送通知邮件
            if (user != null) {
                sendMail(user);
            }
        } catch (Exception ex) {
            logger.error(ex.getMessage());
        }
    }

    // 发送邮件
    // 请参考上篇文章《Spring全家桶的深入学习(八):Spring集成JavaMailSender实现邮件发送》
    private void sendMail(User user) {
        // 发送文本邮件
        String text = "您的邮箱信息已登记!";
        mailManager.sendSimpleMail(user.getEmail(), "用户通知(文本邮件)", text);

        // 发送HTML邮件
        String content = "\n" +
                "\n" +
                "

" + text + "

\n"
+ "\n" + ""; mailManager.sendHTMLMail(user.getEmail(), "用户通知(HTML邮件)", content); // 发送带有附件的邮件 String attachFilePath = "c:\\csdn-logo.png"; mailManager.sendAttachmentMail(user.getEmail(), "用户通知(带有附件的邮件)", content, attachFilePath); } }

十二时辰手把手教你入门Spring_第42张图片
十二时辰手把手教你入门Spring_第43张图片

5. 修改业务逻辑,实现发送消息功能
/**
 * 基于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 增加删除deleteUser和查找findUser
 * @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
 * @date 2020-07-14 将同步发送通知邮件的功能变更为通过消息队列异步发送
 */
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RabbitTemplate rabbitTemplate;

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

    // 保存用户
    public User saveUser(User user) {
        boolean firstRegister = false;
        if ((user.getId() == null || user.getId().equals(0L))) {
            firstRegister = true;
        }
        // 首次保存用户成功后发送消息队列实现异步发送通知邮件
        if (userRepository.save(user) != null && firstRegister == true) {
            rabbitTemplate.convertAndSend(user);
        }
        return 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);
    }

}

功能测试

十二时辰手把手教你入门Spring_第44张图片
十二时辰手把手教你入门Spring_第45张图片
十二时辰手把手教你入门Spring_第46张图片

Spring集成RabbitMQ实现异步消息处理小结

  • 异步消息在要通信的应用程序之间提供了一个中间层,这样能够实现更松散的耦合和更强的可扩展性。利用消息队列的这种特性我们可以很方便地实现系统应用间的解耦:
    • 用户登记成功后,向客户端返回登记成功的同时,只是向消息队列发送消息,并不等待邮件的发送事件的结果;
    • 而消息队列接收者收到消息后,对消息进行解析,并根据解析中的邮件地址,发送通知邮件。
  • Spring支持集成RabbitMQ实现异步消息,通过使用消息监听器注解@RabbitListener,消息也可以推送至消费者的bean方法中。

十、Spring Integration集成模式实现企业ESB

Spring Integration

  • 在企业级开发中,某个应用程序或模块往往需要完成读取消息、通过路由与外部API交互、生成日志文件、消息异步处理等一系列任务;我们会把这一系列任务组成一个或多个管道流,并定义为ESB(Enterprise Service Bus )企业服务总线(总线方式把点对点、多对多的连接方式变成一对一的方式):
    在这里插入图片描述

Spring Integration 正是企业级服务总线的实现:Spring Integration实现通用的集成模式,每个模式都实现为一个组件,消息会通过该组件在管道中传递数据。借助Spring配置,我们可以将这些组件组装成一个管道,数据可以通过这个管道来流动。

Spring Integration在系统内提供实现轻量级、事件驱动交互行为的框架
Spring Integration在系统间提供一种基于适配器的平台,以支持灵活的系统间交互

Spring Integration的几个重要概念
  1. 消息(Message):在Spring Integration中,消息是任何Java对象的通用包装。
  2. 网关(gateway):通过接口将数据传递到集成流中,即总线入口。
  3. 通道(channel):将消息从一个元素传递到另一个元素。
  4. 过滤器(filter):基于某些判断,条件化地允许某些消息通过流。
  5. 转换器(transformer):改变消息的值和/或将消息载荷从一种类型转换成另一种类型。

以上重要概念具体可参考 《Enterprise Integration Patterns》(企业集成模式)

Spring Integration集成模式实战

  • 在上一节中,UseService与RabbitMQ发送消息服务直接点对点对接,如果我们对这个通知过程再加入根据邮箱地址信息过滤发送,记录日志信息等模块 ,则用户服务类需要同步扩充。
    十二时辰手把手教你入门Spring_第47张图片
  • 显然,我们希望把《通知用户处理模块》这个流程进行集成,UseService只与这个流程的入口进行单点连接,屏蔽在这个流程中的复杂处理单元,更有利于流程的扩展。
    十二时辰手把手教你入门Spring_第48张图片
  • 用户通知集成服务
    十二时辰手把手教你入门Spring_第49张图片
入口网关
/**
 * Spring Integration网关入口
 *
 * @author zhuhuix
 * @date 2020-07-15
 */
@Component
@MessagingGateway(defaultRequestChannel = "registerChannel")
public interface RegisterMessageGateway {
    /**
     * 注册通知流程
     *
     * @param user 用户信息
     */
    void registerMessageFlow(User user);
}

消息过滤器
/**
 * Spring Integration过滤器
 *
 * @author zhuhuix
 * @date 2020-07-15
 */
@Component
public class RegisterMessageFilter implements GenericSelector<User> {
    @Override
    public boolean accept(User user) {
        if ((user == null || user.getId() == null || user.getId().equals(0L))) {
            return false;
        } else {
            return true;
        }
    }
}
处理单元(发送消息)
/**
 * Spring Integration消息发送
 *
 * @author zhuhuix
 * @date 2020-07-15
 */
@Component
public class RegisterRabbitMQSendHandler implements GenericHandler<User> {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public Object handle(User user, MessageHeaders messageHeaders) {
        rabbitTemplate.convertAndSend(user);
        return null;
    }
}

集成组件
/**
 * Spring Integration DSL配置
 *
 * @author zhuhuix
 * @date 2020-07-15
 */
@Configuration
public class RegisterIntegrationConfig {

    @Bean
    public IntegrationFlow registerFlow(RegisterRabbitMQSendHandler registerRabbitMQSendHandler
            , RegisterMessageFilter registerMessageFilter) {
        return IntegrationFlows
                // 从registerChannel消息通道获取消息
                .from(MessageChannels.direct("registerChannel"))
                // 过滤
                .filter(registerMessageFilter)
                // 发送邮件
                .handle(registerRabbitMQSendHandler)
                .get();

    }
}
修改用户服务业务逻辑
/**
 * 基于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 增加删除deleteUser和查找findUser
 * @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
 * @date 2020-07-14 将同步发送通知邮件的功能变更为通过消息队列异步发送
 * @date 2020-07-15 通过调用注册通知流程组件,屏蔽调用消息队列
 */
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RegisterMessageGateway registerMessageGateway;


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

    // 保存用户
    public User saveUser(User user) {
        // 保存数据
        userRepository.save(user);
        // 调用注册通知流程
        registerMessageGateway.registerMessageFlow(user);

        return 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);
    }

}

功能测试

十二时辰手把手教你入门Spring_第50张图片
十二时辰手把手教你入门Spring_第51张图片
十二时辰手把手教你入门Spring_第52张图片

小结

  • 借助Spring Integration能够定义ESB集成流,在总线内部可以对流中的消息(数据)进行相应处理。
  • 消息网关做为集成流的入口,
  • 在消息流动的过程中,消息可以进行过滤、转换、切分、聚合、路由等处理。
  • 消息通道连接集成流中的各个组件。
  • Spring Integration可以实现快速封装企业流程,统一入出接口,可以简化大规模的业务系统中错综复杂的组件调用关系。
  • 由于现阶段互联网微服务架构的流行,就象ESB企业服务总线一样,Spring Integration目前也处于比较尴尬的境地。

十一、Spring Boot集成MyBatis实现增删改查

Mybatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO为数据库中的记录。
在这里插入图片描述

Spring Boot集成MyBatis实现增删改查

1. 添加maven依赖
 
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>1.3.2version>
        dependency>
2. Spring添加MyBatis配置
  • 修改application.yml配置文件:定位mapper扫描路径
#MyBatis配置
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml

十二时辰手把手教你入门Spring_第53张图片

3.持久层接口
/**
 * mybatis数据层
 *
 * @author zhuhuix
 * @date 2020-07-23
 */
@Repository
public interface UserMapper {

    // 查找所有用户
    List<User> findAll();

    // 根据id查找用户
    User findById(Long id);

    // 新增用户
    Long addUser(User user);

    // 更新用户
    void updateUser(User user);

    // 删除用户
    void deleteById(Long id);

    // 自定义添加通过用户名称查找用户信息
    List<User> findByName(String name);
}

4.Mapper映射


<mapper namespace="com.example.demo.register.UserMapper">
    <select id="findAll" resultType="com.example.demo.register.User" >
        select * from user;
    select>
    <insert id="addUser"  parameterType="com.example.demo.register.User" useGeneratedKeys="true" keyProperty="id">
        insert into user (name,email) values (#{name},#{email});
    insert>
    <update id="updateUser" parameterType="com.example.demo.register.User">
        update user set name=#{name},email=#{email} where id=#{id}
    update>
    <delete id="deleteById">
        delete from user where id=#{id}
    delete>
    <select id="findById" resultType="com.example.demo.register.User" >
        select * from user where id=#{id}
    select>
    <select id="findByName" resultType="com.example.demo.register.User">
        select * from user where name=#{name}
    select>

mapper>

5. 修改业务层逻辑
  • 将业务层UserService中的调用JPA的方法修改成新的UserMapper方法
/**
 * 基于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 增加删除deleteUser和查找findUser
 * @date 2020-07-13 首次保存用户后通过邮件管理器发送通知邮件
 * @date 2020-07-14 将同步发送通知邮件的功能变更为通过消息队列异步发送
 * @date 2020-07-15 通过调用注册通知流程,屏蔽调用消息队列
 * @date 2020-07-23 将业务层UserService中的调用JPA的方法修改成新的UserMapper方法
 */
@Service
public class UserService {
    @Autowired
    //private UserRepository userRepository;
    private UserMapper userMapper;

    @Autowired
    private RegisterMessageGateway registerMessageGateway;

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

    // 保存用户
    public User saveUser(User user) {
        // 保存数据
        if (user.getId() != null && user.getId() != 0) {
            userMapper.updateUser(user);
        } else {
            userMapper.addUser(user);
        }
        // 调用注册通知流程
        registerMessageGateway.registerMessageFlow(user);

        return user;
    }

    // 删除用户
    public void deleteUser(Long id) {
        userMapper.deleteById(id);
    }

    // 查找用户
    public User findUser(Long id) {
        return userMapper.findById(id);
    }

    // 根据名称查找用户
    public List<User> searchUser(String name) {
        return userMapper.findByName(name);
    }
}

6. 视图层增加修改和删除功能

十二时辰手把手教你入门Spring_第54张图片


<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>IDtd>
        <td>Emailtd>
        <td>Nametd>
    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>
        <td><a th:href="@{/user/form(id=${user.id})}"> 修改 a>td>
        <td><a th:href="@{/user/del(id=${user.id})}"> 删除 a>td>
    tr>
    tbody>
table>
body>
html>
7. 修改控制器逻辑
/**
 * 基于SpringMVC框架开发web应用--用户控制器
 *
 * @author zhuhuix
 * @date 2020-07-03
 * @date 2020-07-07 增加用户查找
 * @date 2020-07-23 增加修改和删除功能
 */
@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");
    }

    // 获取用户表单页面
    @GetMapping("/form")
    public ModelAndView createForm(Model model, @RequestParam(defaultValue = "0") Long id) {
        if (id > 0) {
            model.addAttribute("user", userService.findUser(id));
        } else {
            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);
    }

    // 查找输入页面
    @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);
    }

    // 删除用户
    @RequestMapping(path = "/del")
    public ModelAndView del(@RequestParam(name = "id") Long id) {
        userService.deleteUser(id);
        return new ModelAndView("redirect:/user");

    }

}

8. 在启动类里加上注解用于给出需要扫描的mapper文件路径
@SpringBootApplication
@MapperScan(basePackages = "com.example.demo.register")
public class DemoApplication {

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

}

完整测试

增加用户

十二时辰手把手教你入门Spring_第55张图片
在这里插入图片描述

修改用户

十二时辰手把手教你入门Spring_第56张图片
十二时辰手把手教你入门Spring_第57张图片
在这里插入图片描述

查找用户

十二时辰手把手教你入门Spring_第58张图片
在这里插入图片描述

删除用户

十二时辰手把手教你入门Spring_第59张图片
在这里插入图片描述

小结

至此,我们通过集成MyBatis完整地实现了用户信息的增删改查,完整源代码已上传guthub SpringDemo。


十二、集成Mongodb与WebFlux实现反应式API

Spring WebFlux

  • Spring WebFlux与Spring MVC非常相似;但Spring MVC在本质上都是阻塞和多线程的,即每个连接都会使用一个线程。
  • Spring 5引入了一个非阻塞、异步的Web框架,该框架在很大程度上是基于Reactor项目的,能够解决Web应用和API中对更好的可扩展性的需求
    十二时辰手把手教你入门Spring_第60张图片

通过Spring Data集成mongodb

  • 要实现反应式的API,首先要让数据流变成反应式和非阻塞的。Spring Data提供对反应式repository的支持,其中包括使用Cassandra、MongoDB、Couchbase或Redis持久化数据的反应式编程模型。
添加依赖
  • pom.xml
 		
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-mongodb-reactiveartifactId>
        dependency>
Spring环境配置
  • application.yml
spring:
  data:
    mongodb:
      host: 127.0.0.1
      port: 27017
      database: user_info
将领域对象映射为文档
  • @Id:将某个属性指明为文档的ID。
  • @Document:将领域类型声明为要持久化到MongoDB中的文档。
  • @Field:指定某个属性持久化到文档中的字段名称。
/**
 * 基于Spring WebFlux框架开发反应式API--对应mongodb文档的用户类
 *
 * @author zhuhuix
 * @date 2020-07-28
 */
@Document(collection = "user")
public class UserDocument implements Serializable {
    @Id
    private Long id;

    @Field
    private String name;

    @Field
    private String 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;
    }

    public UserDocument() {
    }

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

    @Override
    public String toString() {
        return "UserDocument{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

编写反应式的MongoDB repository接口
  • Spring Data MongoDB提供的自动化repository功能与Spring Data JPA类似。具体可参考《四、使用Spring Data实现数据持久化》
/**
 * 基于mongodb的反应式Repository
 *
 * @author zhuhuix
 * @date 2020-07-28
 */
public interface UserReactRepository extends ReactiveCrudRepository<UserDocument,Long> {


}

使用Spring WebFlux

  • 在使用Spring WebFlux时,我们需要添加Spring Boot WebFlux starter依赖项,而不是标准的Webstarter(例如,spring-boot-starter-web)。
添加依赖
  • pom.xml
 		<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webfluxartifactId>
        dependency>
编写反应式控制器
/**
 * 基于SpringWebFlux用户控制器
 *
 * @author zhuhuix
 * @date 2020-07-27
 */
@RestController
@RequestMapping("/user/react")
public class UserReactController {
    // 自动注入数据处理组件
    @Autowired
    private UserReactRepository userReactRepository;

    // 异步测试
    @GetMapping("/test")
    public Flux<UserDocument> test() {
        List<UserDocument> list = new ArrayList<>();
        for(int i=1;i<=100;i++){
            list.add(new UserDocument(Integer.toUnsignedLong(i),"s"+i,"s"+i+"@163.com"));
        }
        return userReactRepository.saveAll(list);
    }

    // 异步获取用户列表
    @GetMapping
    public Flux<UserDocument> list() {
        return userReactRepository.findAll();
    }

    // 异步根据名称获取单个用户
    @GetMapping("/{id}")
    public Mono<UserDocument> search(@PathVariable Long id) {

        return userReactRepository.findById(id);
    }


    // 异步保存用户:新增或修改
    @PostMapping
    public Mono<UserDocument> save(@RequestBody UserDocument user) {
        return userReactRepository.save(user);
    }

    // 异步删除用户
    @DeleteMapping("/{id}")
    public Mono<Void> delete(@PathVariable Long id) {
        return  userReactRepository.deleteById(id);
    }
}

通过jmeter进行测试

  • 通过集成mongodb实现反应式数据流及使用webflux编写反应式控制器,我们基本上实现了反应式的webAPI框架:
    十二时辰手把手教你入门Spring_第61张图片
测试配置

十二时辰手把手教你入门Spring_第62张图片
十二时辰手把手教你入门Spring_第63张图片
十二时辰手把手教你入门Spring_第64张图片

测试结果

十二时辰手把手教你入门Spring_第65张图片
十二时辰手把手教你入门Spring_第66张图片
十二时辰手把手教你入门Spring_第67张图片
十二时辰手把手教你入门Spring_第68张图片

你可能感兴趣的:(spring,java,spring,java)