Spring Boot和 Spring Cloud 的关系详解
要了解 Spring Boot 的发展背景,还得从 2004 年 Spring Framework1.0 版本发布开始说起,不过大家都是从开始学习 Java 就使用 Spring Framework 了,所以就不做过多展开。
随着使用 Spring Framework 进行开发的企业和个人越来越多,Spring 也慢慢从一个单一简洁的小框架编程了一个大而全的开源软件,Spring Framework 的边界不断进行扩张,到了现在 Spring 几乎可以做任何事情。目前市面上绝大部分的开源组件和中间件,都有 Spring 对应组件的支持,
你们如果去关注 Spring 目前的官网,你会发现他的 slogan 是:Spring makes Java Simple。它让 Java 的开发变得更加简单。
虽然 Spring 的组件代码是轻量级的,但是它的配置却是重量级的,Spring 每集成一个开源软件,就需要增加一些基础配置,慢慢的随着我们开发的项目越来越庞大,往往需要集成很多开源软件,因此后期使用 Spirng 开发大型项目需要引入很多配置文件,太多的配置非常难以理解,并容易配置出错,这个给开发人员带来了不少的负担。
大家想象一个场景,就是假如你需要用 spring 开发一个简单的 Hello World Web 应用程序,应该要做哪些动作呢?
-
创建一个项目结构,必然包含依赖 Maven 或者 Gradle 的构建文件。
-
至少需要添加 spring mvc 和 servlet api 的依赖
-
一个 web.xml,声明 spring 的 DispatcherServlet
-
一个启用了 Spring MVC 的 spring 配置
-
一个控制器类,“以 HelloWord”为响应的 http 请求
-
一个用于部署应用程序的 web 应用服务器,比如 Tomcat
在整个过程中,我们发现只有一个东西和 Hello Word 功能相关,那就是控制器(controller),剩下的都是 Spring 开发的 Web 应用程序必须要的通用模版,既然所有 Spring Web 应用程序都要用到他们,那为什么还要你来提供这些东西呢?
所以,直到 2012 年 10 月份,一个叫 Mike Youngstrom(扬斯特罗姆)在 Spring Jira 中创建了一个功能请求,要求在 Spring Framework 中支持无容器 Web 应用程序体系结构,他谈到了在主容器引导 spring 容器内配置 Web 容器服务。
I think that Spring's web application architecture can be significantly simplified if it were to provided tools and a reference architecture that leveraged the Spring component and configuration model from top to bottom. Embedding and unifying the configuration of those common web container services within a Spring Container bootstrapped from a simple main() method.
我认为,如果要提供从上到下充分利用Spring组件和配置模型的工具和参考体系结构,则可以大大简化Spring的Web应用程序体系结构。在通过简单main()方法引导的Spring容器中嵌入和统一那些通用Web容器服务的配置。
而且 Spring 开发团队也意识到了这些问题,急需要一套软件来解决这个问题,而这个时候微服务的概念也慢慢的起来,快速开发微小独立的应用也变得很急迫。
而 Spring 恰好处在这样一个交叉点上,所以顺势而为在 2013 年初的时候,开始投入 Spring Boot 项目的研发,直到 2014 年 4 月,Spring Boot1.0 版本发布。从那以后,Spring Boot 开启了一些列的迭代和升级的过程。
经过 7 年时间的发展,到目前为止,Spring Boot 最新稳定版为 2.6.0 版本。
Spring Boot 的发展
Spring Boot 刚出生的时候,引起了很多开源社区的关注,并且也有个人和企业开始尝试使用 Spring Boot。其实直到 2016 年,Spring Boot 才真正在国内被使用起来。我之前在挖财的时候,2015 年公司就开始采用 Spring Boot 来构建基于 Dubbo 的微服务架构。到现在,Spring Boot 几乎是所有公司的第一选择。
Build Anything
Spring Boot 被官方定位为“BUILD ANYTHING”,Spring Boot 官方的概述是这么描述 Spring Boot 的。
Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".
// 通过Spring Boot可以轻松的创建独立的、生产级别的基于Spring 生态下的应用,你只需要运行即可。
We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.
//对于Spring平台和第三方库,我们提供了一个固化的视图,这个视图可以让我们在构建应用是减少很多麻烦。大部分spring boot应用只需要最小的Spring 配置即可。
如果大家在java培训学习过程中不习惯看英文文档,可能理解起来比较复杂,翻译成人话就是:Spring Boot 能够帮助使用 Spring Framework 生态的开发者快速高效的构建一个基于 Spring 以及 spring 生态体系的应用。
为了让大家对这句话的理解更加深刻,我们来做两个小实验,一个是基于传统的 Spring MVC 框架构建一个项目、另一种是使用 Spring Boot。
Spring MVC With Spring Boot
通过 Spring MVC 项目搭建过程来对比 Spring Boot 的差异和优势。
Spring MVC 项目搭建过程
-
创建一个 maven-webapp 项目
-
添加 jar 包依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
spring-context
spring-context-support
spring-core
spring-expression
spring-web
spring-webmvc
修改 web.xml 文件
<context-param><!--配置上下文配置路径-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--配置监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!--配置Spring MVC的请求拦截-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatcher-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-patter>
</servlet-mapping>
在 resources 目录下添加 dispatcher-servlet.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 扫描 controller -->
<context:component-scan base-package="com.gupaoedu.controller" />
<!--开启注解驱动-->
<mvc:annotation-driven/>
<!-- 定义视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
创建一个 Controller
@Controller
public class HelloController {
@RequestMapping(method = RequestMethod.GET,path = "/index")
public String index(Model model){
model.addAttribute("key","Hello Gupao");
return "index";
}
}
修改默认的 index.jsp,设置 el 表达式的解析
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8" isELIgnored="false" %>
${key}
运行项目
Spring Boot 搭建过程
直接基于 start.spring.io 这个脚手架搭建即可。
思考和总结
咱们再回到最开始 Spring Boot 的定义部分,Spring Boot 能够帮助使用 Spring Framework 生态的开发者快速高效的构建一个基于 Spring 以及 spring 生态体系的应用。
再对比两种构建过程,似乎也能够理解 Spring Boot 的作用了吧。当然它的作用不仅于此,后续会逐步揭开它的真实面目。
通过上面这个案例我们发现,如果没有 spring boot,要去构建一个 Spring MVC 的 web 应用,需要做的事情很多
-
引入 jar 包
-
修改 web.xml,添加监听和拦截
-
创建 spring mvc 核心配置文件 dispatcher-servlet.xml
-
创建 controller
-
部署到 tomcat
这个过程如果不熟悉,很可能需要 1~2 个小时,如果是新手,可能需要更长时间。但是 spring boot,不管是新手还是老手,都能够分分钟解决问题。
理解约定优于配置
我们知道,Spring Boot 是约定由于配置理念下的产物,那么什么是约定由于配置呢?
约定优于配置是一种软件设计的范式,主要是为了减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。
简单来说,就是你所使用的工具默认会提供一种约定,如果这个约定和你的期待相符合,就可以省略那些基础的配置,否则,你就需要通过相关配置来达到你所期待的方式。
约定优于配置有很多地方体现,举个例子,比如交通信号灯,红灯停、绿灯行,这个是一个交通规范。你可以在红灯的时候不停,因为此时没有一个障碍物阻碍你。但是如果大家都按照这个约定来执行,那么不管是交通的顺畅度还是安全性都比较好。
而相对于技术层面来说,约定有很多地方体现,比如一个公司,会有专门的文档格式、代码提交规范、接口命名规范、数据库规范等等。这些规定的意义都是让整个项目的可读性和可维护性更强。
Spring Boot Web 应用中约定优于配置的体现
那么在前面的案例中,我们可以思考一下,Spring Boot 为什么能够把原本繁琐又麻烦的工作省略掉呢?实际上这些工作并不是真正意义上省略了,只是 Spring Boot 帮我们默认实现了。
而这个时候我们反过来思考一下,Spring Boot Web 应用中,相对 Spring MVC 框架的构建而言,它的约定由于配置体现在哪些方面呢?
-
Spring Boot 的项目结构约定,Spring Boot 默认采用 Maven 的目录结构,其中
-
src.main.java 存放源代码文件
-
src.main.resource 存放资源文件
-
src.test.java 测试代码
-
src.test.resource 测试资源文件
-
target 编译后的 class 文件和 jar 文件
-
内置了嵌入式的 Web 容器,在 Spring 2.2.6 版本的官方文档中 3.9 章节中,有说明 Spring Boot 支持四种嵌入式的 Web 容器
-
Tomcat
-
Jetty
-
Undertow
-
Reactor
-
Spring Boot 默认提供了两种配置文件,一种是 application.properties、另一种是 application.yml。Spring Boot 默认会从该配置文件中去解析配置进行加载。
-
Spring Boot 通过 starter 依赖,来减少第三方 jar 的依赖。
这些就是 Spring Boot 能够方便快捷的构建一个 Web 应用的秘密。当然 Spring Boot 的约定优于配置还不仅体现在这些地方,在后续的分析中还会看到 Spring Boot 中约定优于配置的体现。
Spring Boot 整合 Mybatis
实际上 Spring Boot 的本质就是 Spring,如果一定要从技术发展的过程中找到一些相似的对比的话,你们可以对比一下 Jsp/Servlet 和 Spring MVC, 两者都可以用来开发 Web 项目,但是在使用上,Spring MVC 的使用会更加简单。
而 Spring Boot 和 Spring 就相当于当年的 JSP/Servlet 和 Spring MVC 的关系。所以它本身并没有所谓新的技术,接下来,我带着大家来通过 Spring Boot 整合 Mybatis 实现数据的基本操作的案例,来继续认识一下 Spring Boot。
创建 Spring Boot 应用
创建一个 Web 项目
引入项目中需要的 starter 依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
创建数据库表
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`address` varchar(80) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
配置数据库连接
spring:
datasource:
url: jdbc:mysql://192.168.13.106:3306/test_springboot
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
开发数据库访问层
创建实体对象
public class User {
private int id;
private String name;
private String address;
}
创建 Mapper
//@Repository可以支持在你的持久层作为一个标记,可以去自动处理数据库操作产生的异常
@Repository
@Mapper
public interface UserMapper {
User findById(int id);
List<User> list();
int insert(User user);
int delete(int id);
int update(User user);
}
编写 mapper 文件
在 resource 文件目录下创建 UserMapper.xml 文件,内容如下
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC
"-//mybatis.org//DTD com.example.Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<resultMap id="resultMap" type="com.example.demo.entity.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="address" column="address"/>
</resultMap>
<select id="findById" resultMap="resultMap" parameterType="java.lang.Integer">
select * from t_user where id=#{id}
</select>
<select id="list" resultMap="resultMap">
select * from t_user
</select>
<insert id="insert" parameterType="com.example.demo.entity.User" keyProperty="id" useGeneratedKeys="true">
insert into t_user(name,address) values(#{name,jdbcType=VARCHAR},#{address,jdbcType=VARCHAR})
</insert>
<delete id="delete" parameterType="java.lang.Integer">
delete from t_user where id=#{id}
</delete>
<update id="update" parameterType="com.example.demo.entity.User">
update t_user set name=#{name,jdbcType=VARCHAR},address=#{address,jdbcType=VARCHAR} where id=#{id,jdbcType=INTEGER}
</update>
</mapper
定义 service 及实现
public interface IUserService {
User findById(int id);
List<User> list();
int insert(User user);
int delete(int id);
int update(User user);
}
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
}
创建 Controller
@RestController
public class Controller {
@Autowired
private IUserService userService;
@GetMapping("/user/{id}")
public User user(@PathVariable("id") int id){
return userService.findById(id);
}
@GetMapping("/users")
public List<User> users(){
return userService.list();
}
@PostMapping("/user")
public String insertUser(User user){
int row=userService.insert(user);
return row>0?"SUCCESS":"FAILED";
}
@PutMapping("/user")
public String updateUser(User user){
int row=userService.update(user);
return row>0?"SUCCESS":"FAILED";
}
@DeleteMapping("/user/{id}")
public String deleteUser(@PathVariable("id") int id){
return userService.delete(id)>0?"SUCCESS":"FAILED";
}
}
修改配置
在 Spring 的 Main 方法上增加以下注解,用来扫描 Mybatis 的 Mapper 文件
@MapperScan("com.example.demo.mapper")
配置 Mapper 配置文件的地址,在 application.yml 中
mybatis:
mapper-locations: classpath:*Mapper.xml
id int,
name varchar(20),
address varchar(20)
)
项目打包
-
mvn -Dmaven.test.skip -U clean install
-
java -jar xxx.jar
简单总结
这个代码,我想,大家应该写过无数遍了,而在基于 Spring Boot 集成 Mybatis 这个案例中,核心的业务逻辑并没有减少,它只减少了一些繁琐的配置,使得我们更聚焦在业务开发层面。
简单来说,基于 Spring Boot 的项目中,我们只需要写 Controlelr、Service、Dao 即可。甚至很多情况下我们 dao 都不需要管,比如使用 mybatis-plus 这个插件,就可以省去很多固定的 dao 层逻辑。
所以实际上,Spring Boot 并没有新鲜的东西,因此你看到市面上大部分讲 spring boot 的书,这些书我几乎都看过,基本上都是讲解 Spring Boot 的应用,以及 Spring Boot 的一些特性分析。因为一旦你想讲 Spring Boot 的原理,就必然会回归到 Spring 这块的内容上。比如《Spring Boot 编程思想》这本书,大篇幅的都是在讲 Spring Framework。因为 Spring Boot 的内核还是 Spring Framework。
Spring Boot 与微服务
接下来,给大家讲讲 spring boot 与微服务这块的内容。
什么是 Spring Cloud
首先,我们要简单了解一下什么是微服务,按照我的理解来说,微服务就是微粒度的服务,它是面向服务架构(SOA)的进一步优化。如果大家不是很好理解,翻译成白话就是
一个业务系统,原本是在一个独立的 war 包中。现在为了更好的维护和提高性能,把这个 war 包按照业务纬度拆分成了一个个独立的业务子系统,每个子系统提供该业务领域相关的功能,并暴露 API 接口。
这些服务彼此之间进行数据交换和通信来实现整个产品的功能。
而这些业务子系统,实际上代表的就是一个服务,那么所谓的微服务,说的是这个服务的粒度。至于服务的粒度什么样才叫微,其实没有一个固定的衡量标准。更多的还是在每个公司具体的业务粒度的把控上。
微服务化遇到的问题
在为服务化之后,会面临很多的问题,比如服务注册、服务路由、负载均衡、服务监控等等。这些问题都需要有相应的技术来解决,这个时候,Spring Cloud 就出现了。
简单来说,Spring Cloud 提供了一些可以让开发者快速构建微服务应用的工具,比如配置管理、服务发现、熔断、智能路由等,这些服务可以在任何分布式环境下很好地工作。Spring Cloud 主要 致力于解决如下问题:
-
Distributed/versioned configuration,分布式及版本化配置。
-
Service registration and discovery,服务注册与发现。
-
Routing,服务路由。
-
Service-to-service calls,服务调用。
-
Load balancing,负载均衡。
-
Circuit Breakers,断路器。
-
Global locks,全局锁。
-
Leadership election and cluster state,Leader 选举及集群状态。
-
Distributed messaging,分布式消息。
需要注意的是,Spring Cloud 并不是 Spring 团队全新研发的框架,它只是把一些比较优秀的解决微服务架构中常见问题的开源框架基于 Spring Cloud 规范进行了整合,通过 Spring Boot 这个 框架进行再次封装后屏蔽掉了复杂的配置,给开发者提供良好的开箱即用的微服务开发体验。不难看出,Spring Cloud 其实就是一套规范,而 Spring Cloud Netflix、Spring Cloud Consul、Spring CloudAlibaba 才是 Spring Cloud 规范的实现。
为什么 Spring Cloud 是基于 Spring Boot
那为什么 Spring Cloud 会采用 Spring Boot 来作为基础框架呢?原因很简单
-
Spring Cloud 它是关注服务治理领域的解决方案,而服务治理是依托于服务架构之上,所以它仍然需要一个承载框架
-
Spring Boot 可以简单认为它是一套快速配置 Spring 应用的脚手架,它可以快速开发单个微服务
在微服务架构下,微服务节点越来越多,需要一套成熟高效的脚手架,而 Spring Boot 正好可以满足这样的需求,如下图所示。
Spring Boot 的四大核心机制
如果一定要基于 Spring Boot 的特性去说,那么只能去说 Spring Boot 的四大核心机制,分别是 @EnableAutoConfiguration 、 Starter 开箱即用组件、Actuator 应用监控、Spring Boot CLI 命令行工具。
EnableAutoConfiguration
Starter
告诉 Spring Boot 需要什么功能,它就能引入需要的库。
Actuator
让你能够深入运行中的 Spring Boot 应用程序
Spring Boot CLI
Spring Boot CLI 为 Spring Cloud 提供了 Spring Boot 命令行功能。我们可以通过编写 groovy 脚本来运行 Spring Cloud 组件应用程序。步骤如下
-
下载 spring-boot-cli
-
Spring Boot CLI:https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.2.6.RELEASE/spring-boot-cli-2.2.6.RELEASE-bin.zip
-
配置环境变量
-
在控制台
spring --version
查看 CLI 版本 -
使用 CLI 运行应用。我们可以使用 run 命令编译和运行 Groovy 源代码。Spring Boot CLI 中包含所有运行 Groovy 所需要的依赖。
-
创建一个
hello.groovy
文件
@RestController
class HelloController {
@GetMapping("/hello")
String hello(){
return "Hello World";
}
}
在控制台执行spring run hello.groovy
,如果需要传递参数,比如端口,和 JVM 参数类似
spring run hello.groovy -- --server.port=9000
Spring Boot 的四大核心特性
-
EnableAutoConfiguration
-
Starter
-
Actuator
-
Spring Boot CLI
-
Spring Boot CLI 为 Spring Cloud 提供了 Spring Boot 命令行功能。我们可以通过编写 groovy 脚本来运行 Spring Cloud 组件应用程序。步骤如下、
-
下载 spring-boot-cli
-
Spring Boot CLI:https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.2.6.RELEASE/spring-boot-cli-2.2.6.RELEASE-bin.zip
-
配置环境变量
-
在控制台
spring --version
查看 CLI 版本 -
使用 CLI 运行应用。我们可以使用 run 命令编译和运行 Groovy 源代码。Spring Boot CLI 中包含所有运行 Groovy 所需要的依赖。
-
创建一个
hello.groovy
文件
@RestController
class HelloController {
@GetMapping("/hello")
String hello(){
return "Hello World";
}
}
在控制台执行spring run hello.groovy
,如果需要传递参数,比如端口,和 JVM 参数类似
spring run hello.groovy -- --server.port=9000