关于 Spring For All

关于 Spring For All

Spring For All 的一切
最新动态

最新动态

Spring 5 会是咋样呢
Spring Boot

Spring Boot

快速构建并运行 Spring 应用程序
Spring Cloud

Spring Cloud

分布式系统的一套工具,可用于构建微服务
Spring Framework

Spring Framework

提供依赖注入、事务、Web应用、数据访问等模块
Spring Data

Spring Data

提供一致性数据访问模块
Spring Security

Spring Security

提供应用身份验证和授权支持
Spring Batch

Spring Batch

提供高容批处理操作模块
Spring AMQP

Spring AMQP

基于AMQP消息解决方案
Micro Service Arch.

Micro Service Arch.

微服务架构相关
开源项目及视频教程

开源项目及视频教程

做中国最好的 Spring 开源项目及视频教程
小马哥专栏

小马哥专栏

阿里技术布道者,专注 Spring Boot 及微服务

深入浅出 spring-data-elasticsearch - 基本案例详解(三)

Spring Data泥瓦匠BYSocket 发表了文章 • 30 个评论 • 2247 次浏览 • 2017-06-20 11:37 • 来自相关话题

『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearch$(document).ready(function() {$('pre code').each(function(i, block) { hljs.highlightBlock( block); }); });cd elasticsearch-2.3.2/
./bin/elasticsearch -d
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:mvn clean install
 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/:
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}
下面是基本查询语句的接口:
a. 普通查询,查询城市描述GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]

b. AND 语句查询GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]如果换成 score=5 ,就没有结果了。

c. OR 语句查询GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
d. NOT 语句查询GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
e. LIKE 语句查询GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}
接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
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 getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 

摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 查看全部
『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍
org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置
本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:
mvn clean install

 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}

POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}

可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:
{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}

下面是基本查询语句的接口:
a. 普通查询,查询城市描述
GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]


b. AND 语句查询
GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]
如果换成 score=5 ,就没有结果了。

c. OR 语句查询
GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

d. NOT 语句查询
GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

e. LIKE 语句查询
GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。

更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}

接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
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 getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}

注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 


摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!


跟我学 Spring Cloud 开源项目视频公开课系列

开源项目itmuch.com 发表了文章 • 28 个评论 • 9805 次浏览 • 2017-06-14 15:02 • 来自相关话题

跟我学Spring Cloud

视频主旨
基于Spring Cloud Dalston SR1讲解Spring Cloud核心组件的系统讲解帮助大家快速上手,理解Spring Cloud各组件的用途先脉络,后细节

版权声明
本视频由周立http://www.itmuch.com 贡献给Spring For All社区http://www.spring4all.com ,版权为个人及社区所有。欢迎转载。

转载协议
本视频采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在添加作者公众号二维码。

个人公众号 




社区公众号





代码&课件
https://github.com/itmuch/spring-cloud-learning-video 
http://git.oschina.net/itmuch/spring-cloud-learning-video
 答疑&勘误&建议&意见
http://www.spring4all.com 
 
具体课程在线播放如下:
 
- 跟我学SpringCloud(一)视频介绍「spring4all.com」







跟我学SpringCloud(二)SpringCloud简介「spring4all.com」





跟我学SpringCloud(三)Spring Boot快速入门





待续...
(社区小编努力上传ing ... , 先分享收藏吧 )
 
赞助社区
(微信扫一扫)




  查看全部
跟我学Spring Cloud

视频主旨
  • 基于Spring Cloud Dalston SR1讲解
  • Spring Cloud核心组件的系统讲解
  • 帮助大家快速上手,理解Spring Cloud各组件的用途
  • 先脉络,后细节


版权声明
本视频由周立http://www.itmuch.com 贡献给Spring For All社区http://www.spring4all.com ,版权为个人及社区所有。欢迎转载。

转载协议
本视频采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在添加作者公众号二维码。

个人公众号 
1.jpg

社区公众号
2.jpg


代码&课件
https://github.com/itmuch/spring-cloud-learning-video 
http://git.oschina.net/itmuch/spring-cloud-learning-video
 答疑&勘误&建议&意见
http://www.spring4all.com 
 
具体课程在线播放如下
 
- 跟我学SpringCloud(一)视频介绍「spring4all.com」








跟我学SpringCloud(二)SpringCloud简介「spring4all.com」






跟我学SpringCloud(三)Spring Boot快速入门






待续...
(社区小编努力上传ing ... , 先分享收藏吧 )
 
赞助社区
(微信扫一扫)
8888.png

 

Spring Boot 最佳实战开源项目:Any-Video

开源项目anoy 发表了文章 • 26 个评论 • 2988 次浏览 • 2017-06-09 09:42 • 来自相关话题

前言
   Any-Video 是一款 WEB 应用,基于网络爬虫,实时解析各种资源,如视频、文章等。足以媲美官网的体验效果和速度,用户可以对解析后的资源收藏并分享。演示地址:https://www.ictgu.cn
 
Github地址
https://github.com/ChinaSilence/any-video
 技术框架
核心:Spring Boot 1.5.3数据:Mybatis数据库连接池:Hikari安全 : Spring Serucity模板:Thymeleaf 3前端:Bootstrap + Jquery爬虫:Jsoup容器:Undertow
 
效果展示
 

 

  查看全部
前言
   Any-Video 是一款 WEB 应用,基于网络爬虫,实时解析各种资源,如视频、文章等。足以媲美官网的体验效果和速度,用户可以对解析后的资源收藏并分享。演示地址:https://www.ictgu.cn
 
Github地址
https://github.com/ChinaSilence/any-video
 技术框架
  • 核心:Spring Boot 1.5.3
  • 数据:Mybatis
  • 数据库连接池:Hikari
  • 安全 : Spring Serucity
  • 模板:Thymeleaf 3
  • 前端:Bootstrap + Jquery
  • 爬虫:Jsoup
  • 容器:Undertow

 
效果展示
 

 

 

(送书哦)Spring For All 第 1 期高手 QA 环节 — Spring Cloud 微服务实战

Spring Cloudkawan 回复了问题 • 48 人关注 • 46 个回复 • 4166 次浏览 • 2017-06-09 09:48 • 来自相关话题

深入浅出 spring-data-elasticsearch 之 ElasticSearch 架构初探(一)

Spring Data泥瓦匠BYSocket 发表了文章 • 2 个评论 • 2552 次浏览 • 2017-06-04 17:20 • 来自相关话题

本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比:
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node)
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:





那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:





分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:shard = hash(routing) % number_of_primary_shards
routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态GET http://127.0.0.1:9200/_cluster/stats
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}
status 字段是需要我们关心的。状态可能是下列三个值之一:green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:PUT /blogs/_settings
{
"number_of_replicas" : 2
}

三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 

本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处 查看全部
本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:
elas_node.png


那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:
elas_0201.png


分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:
shard = hash(routing) % number_of_primary_shards

routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态
GET http://127.0.0.1:9200/_cluster/stats 
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}

status 字段是需要我们关心的。状态可能是下列三个值之一:
green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:
PUT /blogs/_settings
{
"number_of_replicas" : 2
}


三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 


本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处


SpringBoot 整合 Dubbo/ZooKeeper 详解 SOA 案例

Spring Boot泥瓦匠BYSocket 发表了文章 • 3 个评论 • 1472 次浏览 • 2017-06-03 17:22 • 来自相关话题

“看看星空,会觉得自己很渺小,可能我们在宇宙中从来就是一个偶然。所以,无论什么事情,仔细想一想,都没有什么大不了的。这能帮助自己在遇到挫折时稳定心态,想得更开。”  – 《腾讯传》

本文提纲
一、为啥整合 Dubbo 实现 SOA
二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程
三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解 

一、为啥整合 Dubbo 实现 SOA

Dubbo 不单单只是高性能的 RPC 调用框架,更是 SOA 服务治理的一种方案。
核心:
1. 远程通信,向本地调用一样调用远程方法。
2. 集群容错
3. 服务自动发现和注册,可平滑添加或者删除服务提供者。
我们常常使用 Springboot 暴露 HTTP 服务,并走 JSON 模式。但慢慢量大了,一种 SOA 的治理方案。这样可以暴露出 Dubbo 服务接口,提供给 Dubbo 消费者进行 RPC 调用。下面我们详解下如何集成 Dubbo。
 

二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+、Dubbo 2.5+、ZooKeeper 3.3+
 

1.ZooKeeper 服务注册中心
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
下载 ZooKeeper ,地址 http://www.apache.org/dyn/closer.cgi/zookeeper
解压 ZooKeepertar zxvf zookeeper-3.4.8.tar.gz在 conf 目录新建 zoo.cfg ,照着该目录的 zoo_sample.cfg 配置如下。cd zookeeper-3.3.6/conf
vim zoo.cfgzoo.cfg 代码如下(自己指定 log 文件目录):tickTime=2000
dataDir=/javaee/zookeeper/data
dataLogDir=/javaee/zookeeper/log
clientPort=2181
在 bin 目录下,启动 ZooKeeper:cd zookeeper-3.3.6/bin
./zkServer.sh start
2. git clone 下载工程 springboot-learning-example
项目地址见 GitHub – https://github.com/JeffLi1993/ ... ample:git clone git@github.com:JeffLi1993/springboot-learning-example.git
然后,Maven 编译安装这个工程:cd springboot-learning-example
mvn clean install
 

3.运行 springboot-dubbo-server Dubbo 服务提供者工程
右键运行 springboot-dubbo-server 工程 ServerApplication 应用启动类的 main 函数。Console 中出现如下表示项目启动成功:






这里表示 Dubbo 服务已经启动成功,并注册到 ZK (ZooKeeper)中。
 

4.运行 springboot-dubbo-client Dubbo 服务消费者工程
右键运行 springboot-dubbo-client 工程 ClientApplication 应用启动类的 main 函数。Console 中出现如下:...
2017-03-01 16:31:38.473 INFO 9896 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-03-01 16:31:38.538 INFO 9896 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http)
2017-03-01 16:31:38.547 INFO 9896 --- [ main] org.spring.springboot.ClientApplication : Started ClientApplication in 6.055 seconds (JVM running for 7.026)
City{id=1, provinceId=2, cityName='温岭', description='是我的故乡'}最后打印的城市信息,就是通过 Dubbo 服务接口调用获取的。顺利运行成功,下面详解下各个代码及配置。
 

三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解
代码都在 GitHub 上, https://github.com/JeffLi1993/ ... ample。
1.详解 springboot-dubbo-server Dubbo 服务提供者工程
springboot-dubbo-server 工程目录结构├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── ServerApplication.java
│ ├── domain
│ │ └── City.java
│ └── dubbo
│ ├── CityDubboService.java
│ └── impl
│ └── CityDubboServiceImpl.java
└── resources
└── application.properties

a.pom.xml 配置
pom.xml 中依赖了 spring-boot-starter-dubbo 工程,该项目地址是 https://github.com/teaey/spring-boot-starter-dubbo。pom.xml 配置如下<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>

<groupId>springboot</groupId>
<artifactId>springboot-dubbo-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-dubbo 服务端:: 整合 Dubbo/ZooKeeper 详解 SOA 案例</name>

<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>

<properties>
<dubbo-spring-boot>1.0.0</dubbo-spring-boot>
</properties>

<dependencies>

<!-- Spring Boot Dubbo 依赖 -->
<dependency>
<groupId>io.dubbo.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>${dubbo-spring-boot}</version>
</dependency>

<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
b.application.properties 配置## Dubbo 服务提供者配置
spring.dubbo.application.name=provider
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
spring.dubbo.scan=org.spring.springboot.dubbo
这里 ZK 配置的地址和端口,就是上面本机搭建的 ZK 。如果有自己的 ZK 可以修改下面的配置。配置解释如下:spring.dubbo.application.name 应用名称
spring.dubbo.registry.address 注册中心地址
spring.dubbo.protocol.name 协议名称
spring.dubbo.protocol.port 协议端口
spring.dubbo.scan dubbo 服务类包目录

c.CityDubboServiceImpl.java 城市业务 Dubbo 服务层实现层类// 注册为 Dubbo 服务
@Service(version = "1.0.0")
public class CityDubboServiceImpl implements CityDubboService {

public City findCityByName(String cityName) {
return new City(1L,2L,"温岭","是我的故乡");
}
}@Service 注解标识为 Dubbo 服务,并通过 version 指定了版本号。
 
d.City.java 城市实体类
实体类通过 Dubbo 服务之间 RPC 调用,则需要实现序列化接口。最好指定下 serialVersionUID 值。
 

2.详解 springboot-dubbo-client Dubbo 服务消费者工程
springboot-dubbo-client 工程目录结构├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── ClientApplication.java
│ ├── domain
│ │ └── City.java
│ └── dubbo
│ ├── CityDubboConsumerService.java
│ └── CityDubboService.java
└── resources
└── application.propertiespom.xml 、 CityDubboService.java、City.java 没有改动。Dubbo 消费者通过引入接口实现 Dubbo 接口的调用。
 

a.application.properties 配置## 避免和 server 工程端口冲突
server.port=8081

## Dubbo 服务消费者配置
spring.dubbo.application.name=consumer
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.scan=org.spring.springboot.dubbo因为 springboot-dubbo-server 工程启动占用了 8080 端口,所以这边设置端口为 8081。
 

b.CityDubboConsumerService.java 城市 Dubbo 服务消费者@Component
public class CityDubboConsumerService {

@Reference(version = "1.0.0")
CityDubboService cityDubboService;

public void printCity() {
String cityName="温岭";
City city = cityDubboService.findCityByName(cityName);
System.out.println(city.toString());
}
}@Reference(version = “1.0.0”) 通过该注解,订阅该接口版本为 1.0.0 的 Dubbo 服务。
这里将 CityDubboConsumerService 注入 Spring 容器,是为了更方便的获取该 Bean,然后验证这个 Dubbo 调用是否成功。
 

c.ClientApplication.java 客户端启动类@SpringBootApplication
public class ClientApplication {

public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
ConfigurableApplicationContext run = SpringApplication.run(ClientApplication.class, args);
CityDubboConsumerService cityService = run.getBean(CityDubboConsumerService.class);
cityService.printCity();
}
}解释下这段逻辑,就是启动后从 Bean 容器中获取城市 Dubbo 服务消费者 Bean。然后调用该 Bean 方法去验证 Dubbo 调用是否成功。
 

四、小结

还有涉及到服务的监控,治理。这本质上和 SpringBoot 无关,所以这边不做一一介绍。感谢阿里 teaey 提供的 starter-dubbo 项目。
 
本文作者: 泥瓦匠
原文链接: http://www.bysocket.com/?p=1681
版权归作者所有,转载请注明出处 查看全部
“看看星空,会觉得自己很渺小,可能我们在宇宙中从来就是一个偶然。所以,无论什么事情,仔细想一想,都没有什么大不了的。这能帮助自己在遇到挫折时稳定心态,想得更开。”  – 《腾讯传》

本文提纲
一、为啥整合 Dubbo 实现 SOA
二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程
三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解 

一、为啥整合 Dubbo 实现 SOA

Dubbo 不单单只是高性能的 RPC 调用框架,更是 SOA 服务治理的一种方案。
核心
1. 远程通信,向本地调用一样调用远程方法。
2. 集群容错
3. 服务自动发现和注册,可平滑添加或者删除服务提供者。
我们常常使用 Springboot 暴露 HTTP 服务,并走 JSON 模式。但慢慢量大了,一种 SOA 的治理方案。这样可以暴露出 Dubbo 服务接口,提供给 Dubbo 消费者进行 RPC 调用。下面我们详解下如何集成 Dubbo。
 

二、运行 springboot-dubbo-server 和 springboot-dubbo-client 工程

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+、Dubbo 2.5+、ZooKeeper 3.3+
 

1.ZooKeeper 服务注册中心
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
下载 ZooKeeper ,地址 http://www.apache.org/dyn/closer.cgi/zookeeper
解压 ZooKeeper
tar zxvf zookeeper-3.4.8.tar.gz
在 conf 目录新建 zoo.cfg ,照着该目录的 zoo_sample.cfg 配置如下。
cd zookeeper-3.3.6/conf
vim zoo.cfg
zoo.cfg 代码如下(自己指定 log 文件目录):
tickTime=2000
dataDir=/javaee/zookeeper/data
dataLogDir=/javaee/zookeeper/log
clientPort=2181

在 bin 目录下,启动 ZooKeeper:
cd zookeeper-3.3.6/bin
./zkServer.sh start

2. git clone 下载工程 springboot-learning-example
项目地址见 GitHub – https://github.com/JeffLi1993/ ... ample
git clone git@github.com:JeffLi1993/springboot-learning-example.git

然后,Maven 编译安装这个工程:
cd springboot-learning-example
mvn clean install

 

3.运行 springboot-dubbo-server Dubbo 服务提供者工程
右键运行 springboot-dubbo-server 工程 ServerApplication 应用启动类的 main 函数。Console 中出现如下表示项目启动成功:

sdubbo.png


这里表示 Dubbo 服务已经启动成功,并注册到 ZK (ZooKeeper)中。
 

4.运行 springboot-dubbo-client Dubbo 服务消费者工程
右键运行 springboot-dubbo-client 工程 ClientApplication 应用启动类的 main 函数。Console 中出现如下:
...
2017-03-01 16:31:38.473 INFO 9896 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-03-01 16:31:38.538 INFO 9896 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http)
2017-03-01 16:31:38.547 INFO 9896 --- [ main] org.spring.springboot.ClientApplication : Started ClientApplication in 6.055 seconds (JVM running for 7.026)
City{id=1, provinceId=2, cityName='温岭', description='是我的故乡'}
最后打印的城市信息,就是通过 Dubbo 服务接口调用获取的。顺利运行成功,下面详解下各个代码及配置。
 

三、springboot-dubbo-server 和 springboot-dubbo-client 工程配置详解
代码都在 GitHub 上, https://github.com/JeffLi1993/ ... ample
1.详解 springboot-dubbo-server Dubbo 服务提供者工程
springboot-dubbo-server 工程目录结构
├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── ServerApplication.java
│ ├── domain
│ │ └── City.java
│ └── dubbo
│ ├── CityDubboService.java
│ └── impl
│ └── CityDubboServiceImpl.java
└── resources
└── application.properties


a.pom.xml 配置
pom.xml 中依赖了 spring-boot-starter-dubbo 工程,该项目地址是 https://github.com/teaey/spring-boot-starter-dubbo。pom.xml 配置如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>

<groupId>springboot</groupId>
<artifactId>springboot-dubbo-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-dubbo 服务端:: 整合 Dubbo/ZooKeeper 详解 SOA 案例</name>

<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>

<properties>
<dubbo-spring-boot>1.0.0</dubbo-spring-boot>
</properties>

<dependencies>

<!-- Spring Boot Dubbo 依赖 -->
<dependency>
<groupId>io.dubbo.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>${dubbo-spring-boot}</version>
</dependency>

<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

b.application.properties 配置
## Dubbo 服务提供者配置
spring.dubbo.application.name=provider
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
spring.dubbo.scan=org.spring.springboot.dubbo

这里 ZK 配置的地址和端口,就是上面本机搭建的 ZK 。如果有自己的 ZK 可以修改下面的配置。配置解释如下:
spring.dubbo.application.name 应用名称
spring.dubbo.registry.address 注册中心地址
spring.dubbo.protocol.name 协议名称
spring.dubbo.protocol.port 协议端口
spring.dubbo.scan dubbo 服务类包目录


c.CityDubboServiceImpl.java 城市业务 Dubbo 服务层实现层类
// 注册为 Dubbo 服务
@Service(version = "1.0.0")
public class CityDubboServiceImpl implements CityDubboService {

public City findCityByName(String cityName) {
return new City(1L,2L,"温岭","是我的故乡");
}
}
@Service 注解标识为 Dubbo 服务,并通过 version 指定了版本号。
 
d.City.java 城市实体类
实体类通过 Dubbo 服务之间 RPC 调用,则需要实现序列化接口。最好指定下 serialVersionUID 值。
 

2.详解 springboot-dubbo-client Dubbo 服务消费者工程
springboot-dubbo-client 工程目录结构
├── pom.xml
└── src
└── main
├── java
│ └── org
│ └── spring
│ └── springboot
│ ├── ClientApplication.java
│ ├── domain
│ │ └── City.java
│ └── dubbo
│ ├── CityDubboConsumerService.java
│ └── CityDubboService.java
└── resources
└── application.properties
pom.xml 、 CityDubboService.java、City.java 没有改动。Dubbo 消费者通过引入接口实现 Dubbo 接口的调用。
 

a.application.properties 配置
## 避免和 server 工程端口冲突
server.port=8081

## Dubbo 服务消费者配置
spring.dubbo.application.name=consumer
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.scan=org.spring.springboot.dubbo
因为 springboot-dubbo-server 工程启动占用了 8080 端口,所以这边设置端口为 8081。
 

b.CityDubboConsumerService.java 城市 Dubbo 服务消费者
@Component
public class CityDubboConsumerService {

@Reference(version = "1.0.0")
CityDubboService cityDubboService;

public void printCity() {
String cityName="温岭";
City city = cityDubboService.findCityByName(cityName);
System.out.println(city.toString());
}
}
@Reference(version = “1.0.0”) 通过该注解,订阅该接口版本为 1.0.0 的 Dubbo 服务。
这里将 CityDubboConsumerService 注入 Spring 容器,是为了更方便的获取该 Bean,然后验证这个 Dubbo 调用是否成功。
 

c.ClientApplication.java 客户端启动类
@SpringBootApplication
public class ClientApplication {

public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
ConfigurableApplicationContext run = SpringApplication.run(ClientApplication.class, args);
CityDubboConsumerService cityService = run.getBean(CityDubboConsumerService.class);
cityService.printCity();
}
}
解释下这段逻辑,就是启动后从 Bean 容器中获取城市 Dubbo 服务消费者 Bean。然后调用该 Bean 方法去验证 Dubbo 调用是否成功。
 

四、小结

还有涉及到服务的监控,治理。这本质上和 SpringBoot 无关,所以这边不做一一介绍。感谢阿里 teaey 提供的 starter-dubbo 项目。
 
本文作者: 泥瓦匠
原文链接: http://www.bysocket.com/?p=1681
版权归作者所有,转载请注明出处

跟着小程来学微服务--微服务思想

Micro Service Arch.flychao88 发表了文章 • 3 个评论 • 1016 次浏览 • 2017-06-03 11:50 • 来自相关话题

前言

一直对微服务非常感兴趣,因为公司的架构改造正好有机会能够接触微服务,买来一些书,请教了很多微服务大牛同时自己也做了很多总结,写成了80页ppt,算是我对微服务的一个认识吧,微服务本身不同的人有不同的理解,而我就从我自己的角度来谈谈微服务是什么。
目前市面上的不少书或者不少相关文章写的都是框架的使用,或者架构的介绍,其实对于刚入门不久的同学来说很容易造成微服务就是一堆框架和组件的堆砌,于是今天我将从理论和实践的角度来说说微服务。

现代互联网的方向是当企业发展到一定规模后,一定是大规模、云计算和大数据的三者的结合,从而形成平台,那么微服务就是基于此而提出的。

一、什么是微服务

1、常见的系统架构
目前我们经常接触的网络架构主要有三种,如下图:

从图中可以看到,共有三种模式,第一种是集中式架构也是单块应用最常使用的架构模式。第二种是分布式架构,最常见的应用是将一个大的任务拆分到不同的机器中进行计算,最终有一台服务器合并计算结果就是分布式架构的一个好的体现。第三种就是微服务架构。
2、现实遇到的挑战
扩容困难 我们之前开发项目用的是虚拟机,每次上线项目需要加机器总会遇到资源不足的情况,还要走非常复杂工单审批流程,还要与运维人员不断PK,才能申请下来资源,整个流程冗长,机器受限于资源申请困难。部署困难每次上线采用专门的人进行布署,上线之前需要与上线人员沟通上线的环境,防止上线出错。发布回滚困难每次上线发现问题后,需要重新从svn主干上面进行代码编译,但是有时候会因为各种问题回滚失败,而且重新编译很耗时导致回滚缓慢。适配新技术困难如果打算在不同的模块采用不同的语言开发,或者想在架构中做技术升级都很困难或者不支持。快速开发困难项目中采用单体应用,里面集成了太多功能模块,无法快速进行功能开发并且很容易牵一发动全身。测试困难测试人员没有自动化测试框架,或者Mock系统,导致只能采用简单的人工测试流程,而且还经常发生功能覆盖不全面等问题。学习困难
于是我们把项目中遇到上述问题的项目称为单体应用。
3、微服务的定义
The microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services , which may be written in different programming languages and use different data storage technologies.
– James Lewis and Martin Fowler
原文:http://martinfowler.com/articles/microservices.html
翻译文:http://blog.csdn.net/u013970991/article/details/53333921
总结了一下共有四点特性:
一些列的独立的服务共同组成系统单独部署,跑在自己的进程里每个服务为独立的业务开发分布式的管理

4、微服务和SOA的区别
微服务只是一种为经过良好架构设计的SOA解决方案,是面向服务的交付方案。微服务更趋向于以自治的方式产生价值。微服务与敏捷开发的思想高度结合在一起,服务的定义更加清晰,同时减少了企业ESB开发的复杂性。微服务是soa思想的一种提炼!SOA是重ESB,微服务是轻网关。
5、微服务定义小结


二、关于微服务的建模

我们在谈建模,首先想到的是领域驱动设计的内容,没错,微服务的建模思想也是基于建模的思想,下面我将给简单给与介绍什么样的服务才算是好服务。
1、松耦合和高内聚
松耦合:修改一个服务不需要同时修改另一个,每个微服务都可以单独修改和布署。
高内聚:把相关的事务放在一起,把不相关的排除出去,聚集在一起的事务只能干同一件事。
用如下图可以清晰的表示:

2、限界上下文
限界:就是划分、规定,界限、边界。
上下文:是业务的整个流程。

当我们检查已有的系统时,经常会发现系统中存在混杂在一起的模型,他们之间的边界是非常模糊的。此时你应该为整个系统绘制一个边界,然后将其归纳在大泥球范围之内。往往在我们所在的项目中,经常是项目版本的迭代的时候出现这样的情况,导致后期维护代码越来越困难。
3、逐步上下文
划分方法:一开始识别粗粒度的限界上下文、这些粗粒度的上下文可能包括一些套嵌的限界上下文,这些套嵌的上下文不直接对外可见。
暴露原则:使用粗粒度上下文还是套嵌上下文暴露服务,哪个更合理,应该有组织结构来决定的。
!

正如上面二个图的示例所示,图一的订单处理,货物接收和库存管理三个模块在项目研发初期被归集到了仓库服务中,财务服务获取库存管理的数据,直接访问仓库服务的库存管理接口就可以了。随着这三个模块的不断演进和壮大,单个服务已经不能满足业务和团队发展的需求,这时候将这三个模块分别拆分演变成图二的结构图,这时候订单管理,货物接收和库存管理分别以服务的形式对应不同的团队,财务服务只需请求库存管理服务就可以得到相应的数据。

三、关于微服务的集成

1、集成原则
微服务的集成做到好,可以保持自治性、可以独立发布修改和发布。
避免破坏性修改 服务的一些修改不能导致该服务的消费方发生改变。
保证API与技术的无关性
保证API的易用性
隐藏内部实现细节
2、编排与协同
编排:同步调用一组服务,等待各个服务的返回结果。优点知道业务流程中每一步跨服务调用结果,缺点容易承担太多的调用,太耗时,导致调用方的不稳定性。
协同:异步调用一组服务或服务调用加入队列中,降低服务之间的耦合度,带来的额外工作业务流程跨服务的监控,不过可通过消费方处理完成后,回调服务方告知处理结果。
(编排)
(协同)
3、版本管理
尽可能推迟破坏性修改 宽进严出的原则尽早发现破坏性的修改 按照契约,通过测试及早发现是服务方还是消费方破坏性的修改不同的接口版本共存 最好共存两个版本
4、案例分析
案例一:如何拆分单块系统结构

当我们看到一个单块系统时,往往首先要从数据库入手进行拆分,规划好哪些是财务代码的表,哪些是客户代码的表,将二者进行分离,这时候单块系统的应用结构并没有拆分,这还需要我们在进行设计单块系统的时候,客户代表和财务代码的表字段不能混在一起,还是要设计成不同的表才能方便我们将来拆分,虽然系统是在一起的,但是却为未来做了拆分准备,最后将应用系统拆分独立布署,这个过程就结束了。
案例二:如何跨系统访问数据表

有二个服务,分别是产品目录和财务,左图的场景是财务服务直接调用产品目录的数据表进行数据获取,这种跨服务的数据获取方式是有问题的,首先我们无法把控财务服务是如何获取数据的,是否对我们的数据表造成影响,其次从设计的角度来说无疑又增长了系统之间调用的耦合度,系统之间的依赖又增强了。于是演变成右图这样,左图只需提供服务接口给右图调用即可。
案例三:服务设计中的坏味道

在这样的系统中,ABCD四个系统进行了串联,这样也就要求这四个系统分别都是高可用,如果其中任何一个系统挂了或者发生问题,都会直接影响其他所有系统,所以我们设计微服务架构的时候要尽量避免这种集中式的架构。

四、如何大规模的使用微服务

我们真正使用微服务的时候,有很多需要注意和关注的点:
1、故障无所不在
网络是不可靠,只能尽力限制引起故障的因数,达到一定规模后,故障不可避免。
2、跨功能需求
服务吞吐量、可用性和数据持久性等这些需求需要持续测量,并保证服务满足可接受的目标。
3、功能降级
构建弹性系统,因微服务功能分散,在有可能down机的微服务上,能够安全的降级以保证弹性
4、反服务脆弱
为了不会引起严重级联影响,需要正确的设置超时、实现舱壁隔离或断路层等以避免在第一时间调用一个不健康的服务。
超时设置超时时间对于调用下游服务十分重要,超时时间设置太长有可能把下游系统拖慢,设置太短可能下游服务未处理完成。最好设置一个默认的超时时间,当超时发生时后,记录到日志里看看发生了什么,并且做响应的调整。断路器使用断路器,当请求下游服务发生一定数量的失败后,短路器打开,接下来的请求快速失败。一断时间后,查看下游服务是否已服务,重置断路器。舱壁为每个下游服务建立单独的连接池。超时和断路器资源受限时释放资源,舱壁第一时间确保它不成为限制。还有一个拒绝请求的舱壁,用以避免资源饱和,称之为减载。隔离当下游服务离线,上游服务不受影响。设置成为服务间隔离。
5、幂等
幂等操作,多次执行所产生的影响,均与一次执行影响相同。可以把某些特定业务操作设计成幂等的,比如客户下单送积分。
6、扩展
增加负载、减少延迟。
更强大的主机:垂直扩展,更好的机器。拆分负载:按业务拆分成不同的微服务分散风险:数据跨机房,异地备份等负载均衡:避免服务单点故障作业分离:Job独立服务执行重新设计:一般设计系统需要考虑10倍容量增长。重新设计系统应对规模化,是成功的标志。
7、扩展数据库
服务的可用性服务的持久性:多副本读取数据扩展:读写分离写操作扩展:分表分库共享数据库设施:容易形成单点故障CQRS:命令查询职责分离
8、缓存的使用
通过存储之前的操作结果,以便后续请求使用这个结果,而无需花重新计算或查询。
客户端缓存客户端缓存获取的结果,客户端决定何时获取新副本。一般是有下游服务提供缓冲的过期时间。客户端缓存可以减少网络调用次数,并且减少下游服务负载的最快方法之一,客户端缓存数据,让数据失效需要做额外的工作。服务端缓存服务端来负责处理缓存,容易跟踪和优化缓存的命中率。代理服务器缓存缓存在服务的和客户端之间,比如方向代理或CDN等。对一切客户端和服务端不透明HTTP缓存为写使用缓存先写入本地缓存,之后某个时刻将数据写入下游的,可能更规范化的数据源中。为弹性使用缓存下游服务不可用,客户端可以缓存可能失效的数据。隐藏源服务保护源服务,不直接暴露源服务。如果缓存不命中,立即失败,异步重建缓存。保持简单避免太多地方使用缓存,缓存越多,数据越可能失效,就越难保证数据的新鲜程度。
9、自动伸缩
响应型伸缩、预测型伸缩
10、CAP定理
在分布式系统中有三方面需要彼此权衡:一致性、可用性和分区容忍性。这个定理告之我们最多只能能保证三个中的两个。CA系统在分布式系统中根本不存在。

六、阶段总结:

在第一部分我们重点介绍了涉及微服务的一些思想,总结了如何设计一个相对好的服务,并且也介绍了一些微服务和领域驱动的相关概念帮助大家学习掌握。
那么在第二部分介绍中,我将在如何在微服务中使用事务,自动化测试怎么做,Devops是什么,如何利用康威定律管理团队,以及重点介绍实战项目,如何基于Spring boot/netflix来构建微服务项目。
查看全部


前言


一直对微服务非常感兴趣,因为公司的架构改造正好有机会能够接触微服务,买来一些书,请教了很多微服务大牛同时自己也做了很多总结,写成了80页ppt,算是我对微服务的一个认识吧,微服务本身不同的人有不同的理解,而我就从我自己的角度来谈谈微服务是什么。
目前市面上的不少书或者不少相关文章写的都是框架的使用,或者架构的介绍,其实对于刚入门不久的同学来说很容易造成微服务就是一堆框架和组件的堆砌,于是今天我将从理论和实践的角度来说说微服务。

现代互联网的方向是当企业发展到一定规模后,一定是大规模、云计算和大数据的三者的结合,从而形成平台,那么微服务就是基于此而提出的。


一、什么是微服务


1、常见的系统架构
目前我们经常接触的网络架构主要有三种,如下图:

从图中可以看到,共有三种模式,第一种是集中式架构也是单块应用最常使用的架构模式。第二种是分布式架构,最常见的应用是将一个大的任务拆分到不同的机器中进行计算,最终有一台服务器合并计算结果就是分布式架构的一个好的体现。第三种就是微服务架构。
2、现实遇到的挑战
  • 扩容困难 我们之前开发项目用的是虚拟机,每次上线项目需要加机器总会遇到资源不足的情况,还要走非常复杂工单审批流程,还要与运维人员不断PK,才能申请下来资源,整个流程冗长,机器受限于资源申请困难。
  • 部署困难每次上线采用专门的人进行布署,上线之前需要与上线人员沟通上线的环境,防止上线出错。
  • 发布回滚困难每次上线发现问题后,需要重新从svn主干上面进行代码编译,但是有时候会因为各种问题回滚失败,而且重新编译很耗时导致回滚缓慢。
  • 适配新技术困难如果打算在不同的模块采用不同的语言开发,或者想在架构中做技术升级都很困难或者不支持。
  • 快速开发困难项目中采用单体应用,里面集成了太多功能模块,无法快速进行功能开发并且很容易牵一发动全身。
  • 测试困难测试人员没有自动化测试框架,或者Mock系统,导致只能采用简单的人工测试流程,而且还经常发生功能覆盖不全面等问题。
  • 学习困难

于是我们把项目中遇到上述问题的项目称为单体应用。
3、微服务的定义
The microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services , which may be written in different programming languages and use different data storage technologies.
– James Lewis and Martin Fowler
原文:http://martinfowler.com/articles/microservices.html
翻译文:http://blog.csdn.net/u013970991/article/details/53333921
总结了一下共有四点特性:
  • 一些列的独立的服务共同组成系统
  • 单独部署,跑在自己的进程里
  • 每个服务为独立的业务开发
  • 分布式的管理


4、微服务和SOA的区别
  • 微服务只是一种为经过良好架构设计的SOA解决方案,是面向服务的交付方案。
  • 微服务更趋向于以自治的方式产生价值。
  • 微服务与敏捷开发的思想高度结合在一起,服务的定义更加清晰,同时减少了企业ESB开发的复杂性。
  • 微服务是soa思想的一种提炼!
  • SOA是重ESB,微服务是轻网关。

5、微服务定义小结


二、关于微服务的建模


我们在谈建模,首先想到的是领域驱动设计的内容,没错,微服务的建模思想也是基于建模的思想,下面我将给简单给与介绍什么样的服务才算是好服务。
1、松耦合和高内聚
松耦合:修改一个服务不需要同时修改另一个,每个微服务都可以单独修改和布署。
高内聚:把相关的事务放在一起,把不相关的排除出去,聚集在一起的事务只能干同一件事。
用如下图可以清晰的表示:

2、限界上下文
限界:就是划分、规定,界限、边界。
上下文:是业务的整个流程。

当我们检查已有的系统时,经常会发现系统中存在混杂在一起的模型,他们之间的边界是非常模糊的。此时你应该为整个系统绘制一个边界,然后将其归纳在大泥球范围之内。往往在我们所在的项目中,经常是项目版本的迭代的时候出现这样的情况,导致后期维护代码越来越困难。
3、逐步上下文
划分方法:一开始识别粗粒度的限界上下文、这些粗粒度的上下文可能包括一些套嵌的限界上下文,这些套嵌的上下文不直接对外可见。
暴露原则:使用粗粒度上下文还是套嵌上下文暴露服务,哪个更合理,应该有组织结构来决定的。
!

正如上面二个图的示例所示,图一的订单处理,货物接收和库存管理三个模块在项目研发初期被归集到了仓库服务中,财务服务获取库存管理的数据,直接访问仓库服务的库存管理接口就可以了。随着这三个模块的不断演进和壮大,单个服务已经不能满足业务和团队发展的需求,这时候将这三个模块分别拆分演变成图二的结构图,这时候订单管理,货物接收和库存管理分别以服务的形式对应不同的团队,财务服务只需请求库存管理服务就可以得到相应的数据。


三、关于微服务的集成


1、集成原则
微服务的集成做到好,可以保持自治性、可以独立发布修改和发布。
避免破坏性修改 服务的一些修改不能导致该服务的消费方发生改变。
保证API与技术的无关性
保证API的易用性
隐藏内部实现细节
2、编排与协同
编排:同步调用一组服务,等待各个服务的返回结果。优点知道业务流程中每一步跨服务调用结果,缺点容易承担太多的调用,太耗时,导致调用方的不稳定性。
协同:异步调用一组服务或服务调用加入队列中,降低服务之间的耦合度,带来的额外工作业务流程跨服务的监控,不过可通过消费方处理完成后,回调服务方告知处理结果。
(编排)
(协同)
3、版本管理
  • 尽可能推迟破坏性修改 宽进严出的原则
  • 尽早发现破坏性的修改 按照契约,通过测试及早发现是服务方还是消费方破坏性的修改
  • 不同的接口版本共存 最好共存两个版本

4、案例分析
案例一:如何拆分单块系统结构

当我们看到一个单块系统时,往往首先要从数据库入手进行拆分,规划好哪些是财务代码的表,哪些是客户代码的表,将二者进行分离,这时候单块系统的应用结构并没有拆分,这还需要我们在进行设计单块系统的时候,客户代表和财务代码的表字段不能混在一起,还是要设计成不同的表才能方便我们将来拆分,虽然系统是在一起的,但是却为未来做了拆分准备,最后将应用系统拆分独立布署,这个过程就结束了。
案例二:如何跨系统访问数据表

有二个服务,分别是产品目录和财务,左图的场景是财务服务直接调用产品目录的数据表进行数据获取,这种跨服务的数据获取方式是有问题的,首先我们无法把控财务服务是如何获取数据的,是否对我们的数据表造成影响,其次从设计的角度来说无疑又增长了系统之间调用的耦合度,系统之间的依赖又增强了。于是演变成右图这样,左图只需提供服务接口给右图调用即可。
案例三:服务设计中的坏味道

在这样的系统中,ABCD四个系统进行了串联,这样也就要求这四个系统分别都是高可用,如果其中任何一个系统挂了或者发生问题,都会直接影响其他所有系统,所以我们设计微服务架构的时候要尽量避免这种集中式的架构。


四、如何大规模的使用微服务


我们真正使用微服务的时候,有很多需要注意和关注的点:
1、故障无所不在
网络是不可靠,只能尽力限制引起故障的因数,达到一定规模后,故障不可避免。
2、跨功能需求
服务吞吐量、可用性和数据持久性等这些需求需要持续测量,并保证服务满足可接受的目标。
3、功能降级
构建弹性系统,因微服务功能分散,在有可能down机的微服务上,能够安全的降级以保证弹性
4、反服务脆弱
为了不会引起严重级联影响,需要正确的设置超时、实现舱壁隔离或断路层等以避免在第一时间调用一个不健康的服务。
  • 超时设置超时时间对于调用下游服务十分重要,超时时间设置太长有可能把下游系统拖慢,设置太短可能下游服务未处理完成。最好设置一个默认的超时时间,当超时发生时后,记录到日志里看看发生了什么,并且做响应的调整。
  • 断路器使用断路器,当请求下游服务发生一定数量的失败后,短路器打开,接下来的请求快速失败。一断时间后,查看下游服务是否已服务,重置断路器。
  • 舱壁为每个下游服务建立单独的连接池。超时和断路器资源受限时释放资源,舱壁第一时间确保它不成为限制。还有一个拒绝请求的舱壁,用以避免资源饱和,称之为减载。
  • 隔离当下游服务离线,上游服务不受影响。设置成为服务间隔离。

5、幂等
幂等操作,多次执行所产生的影响,均与一次执行影响相同。可以把某些特定业务操作设计成幂等的,比如客户下单送积分。
6、扩展
增加负载、减少延迟。
  • 更强大的主机:垂直扩展,更好的机器。
  • 拆分负载:按业务拆分成不同的微服务
  • 分散风险:数据跨机房,异地备份等
  • 负载均衡:避免服务单点故障
  • 作业分离:Job独立服务执行
  • 重新设计:一般设计系统需要考虑10倍容量增长。重新设计系统应对规模化,是成功的标志。

7、扩展数据库
  • 服务的可用性
  • 服务的持久性:多副本
  • 读取数据扩展:读写分离
  • 写操作扩展:分表分库
  • 共享数据库设施:容易形成单点故障
  • CQRS:命令查询职责分离

8、缓存的使用
通过存储之前的操作结果,以便后续请求使用这个结果,而无需花重新计算或查询。
  • 客户端缓存客户端缓存获取的结果,客户端决定何时获取新副本。一般是有下游服务提供缓冲的过期时间。客户端缓存可以减少网络调用次数,并且减少下游服务负载的最快方法之一,客户端缓存数据,让数据失效需要做额外的工作。
  • 服务端缓存服务端来负责处理缓存,容易跟踪和优化缓存的命中率。
  • 代理服务器缓存缓存在服务的和客户端之间,比如方向代理或CDN等。对一切客户端和服务端不透明
  • HTTP缓存
  • 为写使用缓存先写入本地缓存,之后某个时刻将数据写入下游的,可能更规范化的数据源中。
  • 为弹性使用缓存下游服务不可用,客户端可以缓存可能失效的数据。
  • 隐藏源服务保护源服务,不直接暴露源服务。如果缓存不命中,立即失败,异步重建缓存。
  • 保持简单避免太多地方使用缓存,缓存越多,数据越可能失效,就越难保证数据的新鲜程度。

9、自动伸缩
响应型伸缩、预测型伸缩
10、CAP定理
在分布式系统中有三方面需要彼此权衡:一致性、可用性和分区容忍性。这个定理告之我们最多只能能保证三个中的两个。CA系统在分布式系统中根本不存在。


六、阶段总结:


在第一部分我们重点介绍了涉及微服务的一些思想,总结了如何设计一个相对好的服务,并且也介绍了一些微服务和领域驱动的相关概念帮助大家学习掌握。
那么在第二部分介绍中,我将在如何在微服务中使用事务,自动化测试怎么做,Devops是什么,如何利用康威定律管理团队,以及重点介绍实战项目,如何基于Spring boot/netflix来构建微服务项目。

SpringData ES中一些底层原理的分析

Spring Dataformat_coder 发表了文章 • 7 个评论 • 974 次浏览 • 2017-06-02 18:31 • 来自相关话题

之前写过一篇SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题,顺便深入学习下Spring Data Elasticsearch。
 
Spring Data Elasticsearch是Spring Data针对Elasticsearch的实现。

它跟Spring Data一样,提供了Repository接口,我们只需要定义一个新的接口并继承这个Repository接口,然后就可以注入这个新的接口使用了。
 
定义接口:
 
@Repository
public interface TaskRepository extends ElasticsearchRepository<Task, String> { }
注入接口进行使用:
 
@Autowired
private TaskRepository taskRepository;

....
taskRepository.save(task);
Repository接口的代理生成
 
上面的例子中TaskRepository是个接口,而我们却直接注入了这个接口并调用方法;很明显,这是错误的。

其实SpringData ES内部基于这个TaskRepository接口构造一个SimpleElasticsearchRepository,真正被注入的是这个SimpleElasticsearchRepository。

这个过程是如何实现的呢?  来分析一下。

ElasticsearchRepositoriesAutoConfiguration自动化配置类会导入ElasticsearchRepositoriesRegistrar这个ImportBeanDefinitionRegistrar。

ElasticsearchRepositoriesRegistrar继承自AbstractRepositoryConfigurationSourceSupport,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器调用registerBeanDefinitions进行自定义bean的注册。

ElasticsearchRepositoriesRegistrar委托给RepositoryConfigurationDelegate完成bean的解析。

整个解析过程可以分3个步骤:
 
找出模块中的org.springframework.data.repository.Repository接口的实现类或者org.springframework.data.repository.RepositoryDefinition注解的修饰类,并会过滤掉org.springframework.data.repository.NoRepositoryBean注解的修饰类。找出后封装到RepositoryConfiguration中遍历这些RepositoryConfiguration,然后构造成BeanDefinition并注册到Spring容器中。需要注意的是这些RepositoryConfiguration会以beanClass为ElasticsearchRepositoryFactoryBean这个类的方式被注册,并把对应的Repository接口当做构造参数传递给ElasticsearchRepositoryFactoryBean,还会设置相应的属性比如elasticsearchOperations、evaluationContextProvider、namedQueries、repositoryBaseClass、lazyInitqueryLookupStrategyKeyElasticsearchRepositoryFactoryBean被实例化的时候设置对应的构造参数和属性。设置完毕以后调用afterPropertiesSet方法(实现了InitializingBean接口)。在afterPropertiesSet方法内部会去创建RepositoryFactorySupport类,并进行一些初始化,比如namedQueries、repositoryBaseClass等。然后通过这个RepositoryFactorySupport的getRepository方法基于Repository接口创建出代理类,并使用AOP添加了几个MethodInterceptor
 
// 遍历基于第1步条件得到的RepositoryConfiguration集合
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
// 构造出BeanDefinitionBuilder
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

extension.postProcess(definitionBuilder, configurationSource);

if (isXml) {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
} else {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}

// 使用命名策略生成bean的名字
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName,
configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName());
}

beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// 注册到Spring容器中
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}

// build方法
public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {

Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
// 得到factoryBeanName,这里会使用extension.getRepositoryFactoryClassName()去获得
// extension.getRepositoryFactoryClassName()返回的正是ElasticsearchRepositoryFactoryBean
String factoryBeanName = configuration.getRepositoryFactoryBeanName();
factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName
: extension.getRepositoryFactoryClassName();
// 基于factoryBeanName构造BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName);

builder.getRawBeanDefinition().setSource(configuration.getSource());
// 设置ElasticsearchRepositoryFactoryBean的构造参数,这里是对应的Repository接口
// 设置一些的属性值
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());

NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(
extension.getDefaultNamedQueryLocation());

if (StringUtils.hasText(configuration.getNamedQueriesLocation())) {
definitionBuilder.setLocations(configuration.getNamedQueriesLocation());
}

builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource()));
// 查找是否有对应Repository接口的自定义实现类
String customImplementationBeanName = registerCustomImplementation(configuration);
// 存在自定义实现类的话,设置到属性中
if (customImplementationBeanName != null) {
builder.addPropertyReference("customImplementation", customImplementationBeanName);
builder.addDependsOn(customImplementationBeanName);
}

RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition(
ExtensionAwareEvaluationContextProvider.class);
evaluationContextProviderDefinition.setSource(configuration.getSource());
// 设置一些的属性值
builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition);

return builder;
}

// RepositoryFactorySupport的getRepository方法,获得Repository接口的代理类
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {

// 获取Repository的元数据
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
// 获取Repository的自定义实现类
Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass();
// 根据元数据和自定义实现类得到Repository的RepositoryInformation信息类
// 获取信息类的时候如果发现repositoryBaseClass是空的话会根据meta中的信息去自动匹配
// 具体匹配过程在下面的getRepositoryBaseClass方法中说明
RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);
// 验证
validate(information, customImplementation);
// 得到最终的目标类实例,会通过repositoryBaseClass去查找
Object target = getTargetRepository(information);

// 创建代理工厂
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
// 进行aop相关的设置
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
}
// 使用RepositoryProxyPostProcessor处理
for (RepositoryProxyPostProcessor processor : postProcessors) {
processor.postProcess(result, information);
}

if (IS_JAVA_8) {
// 如果是JDK8的话,添加DefaultMethodInvokingMethodInterceptor
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}

// 添加QueryExecutorMethodInterceptor
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
// 使用代理工厂创建出代理类,这里是使用jdk内置的代理模式
return (T) result.getProxy(classLoader);
}

// 目标类的获取
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// 如果Repository接口属于QueryDsl,抛出异常。目前还不支持
if (isQueryDslRepository(metadata.getRepositoryInterface())) {
throw new IllegalArgumentException("QueryDsl Support has not been implemented yet.");
}
// 如果主键是数值类型的话,repositoryBaseClass为NumberKeyedRepository
if (Integer.class.isAssignableFrom(metadata.getIdType())
|| Long.class.isAssignableFrom(metadata.getIdType())
|| Double.class.isAssignableFrom(metadata.getIdType())) {
return NumberKeyedRepository.class;
} else if (metadata.getIdType() == String.class) {
// 如果主键是String类型的话,repositoryBaseClass为SimpleElasticsearchRepository
return SimpleElasticsearchRepository.class;
} else if (metadata.getIdType() == UUID.class) {
// 如果主键是UUID类型的话,repositoryBaseClass为UUIDElasticsearchRepository
return UUIDElasticsearchRepository.class;
} else {
// 否则报错
throw new IllegalArgumentException("Unsupported ID type " + metadata.getIdType());
}
}
ElasticsearchRepositoryFactoryBean是一个FactoryBean接口的实现类,getObject方法返回的上面提到的getRepository方法返回的代理对象;getObjectType方法返回的是对应Repository接口类型。

我们文章一开始提到的注入TaskRepository的时候,实际上这个对象是ElasticsearchRepositoryFactoryBean类型的实例,只不过ElasticsearchRepositoryFactoryBean实现了FactoryBean接口,所以注入的时候会得到一个代理对象,这个代理对象是由jdk内置的代理生成的,并且它的target对象是SimpleElasticsearchRepository(主键是String类型)。
 
 
SpringData ES中ElasticsearchOperations的介绍
 
ElasticsearchTemplate实现了ElasticsearchOperations接口。

ElasticsearchOperations接口是SpringData对Elasticsearch操作的一层封装,比如有创建索引createIndex方法、获取索引的设置信息getSetting方法、查询对象queryForObject方法、分页查询方法queryForPage、删除文档delete方法、更新文档update方法等等。

ElasticsearchTemplate是具体的实现类,它有这些属性:
 
// elasticsearch提供的基于java的客户端连接接口。java对es集群的操作使用这个接口完成
private Client client;
// 一个转换器接口,定义了2个方法,分别可以获得MappingContext和ConversionService
// MappingContext接口用于获取所有的持久化实体和这些实体的属性
// ConversionService目前在SpringData ES中没有被使用
private ElasticsearchConverter elasticsearchConverter;
// 内部使用EntityMapper完成对象到json字符串和json字符串到对象的映射。默认使用jackson完成映射,可自定义
private ResultsMapper resultsMapper;
// 查询超时时间
private String searchTimeout;
Client接口在ElasticsearchAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public Client elasticsearchClient() {
try {
return createClient();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
ElasticsearchTemplate、ElasticsearchConverter以及SimpleElasticsearchMappingContext在ElasticsearchDataAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public ElasticsearchTemplate elasticsearchTemplate(Client client,
ElasticsearchConverter converter) {
try {
return new ElasticsearchTemplate(client, converter);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Bean
@ConditionalOnMissingBean
public ElasticsearchConverter elasticsearchConverter(
SimpleElasticsearchMappingContext mappingContext) {
return new MappingElasticsearchConverter(mappingContext);
}

@Bean
@ConditionalOnMissingBean
public SimpleElasticsearchMappingContext mappingContext() {
return new SimpleElasticsearchMappingContext();
}
 需要注意的是这个bean被自动化配置类构造的前提是它们在Spring容器中并不存在。
 
Repository的调用过程
 
以自定义的TaskRepository的save方法为例,大致的执行流程如下所示:



SimpleElasticsearchRepository的save方法具体的分析在SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题中分析过。

像自定义的Repository查询方法,或者Repository接口的自定义实现类的操作这些底层,可以去QueryExecutorMethodInterceptor中查看,大家有兴趣的可以自行查看源码。
  查看全部
之前写过一篇SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题,顺便深入学习下Spring Data Elasticsearch。
 
Spring Data ElasticsearchSpring Data针对Elasticsearch的实现。

它跟Spring Data一样,提供了Repository接口,我们只需要定义一个新的接口并继承这个Repository接口,然后就可以注入这个新的接口使用了。
 
定义接口:
 
@Repository
public interface TaskRepository extends ElasticsearchRepository<Task, String> { }

注入接口进行使用:
 
@Autowired
private TaskRepository taskRepository;

....
taskRepository.save(task);

Repository接口的代理生成
 
上面的例子中TaskRepository是个接口,而我们却直接注入了这个接口并调用方法;很明显,这是错误的。

其实SpringData ES内部基于这个TaskRepository接口构造一个SimpleElasticsearchRepository,真正被注入的是这个SimpleElasticsearchRepository。

这个过程是如何实现的呢?  来分析一下。

ElasticsearchRepositoriesAutoConfiguration自动化配置类会导入ElasticsearchRepositoriesRegistrar这个ImportBeanDefinitionRegistrar。

ElasticsearchRepositoriesRegistrar继承自AbstractRepositoryConfigurationSourceSupport,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器调用registerBeanDefinitions进行自定义bean的注册。

ElasticsearchRepositoriesRegistrar委托给RepositoryConfigurationDelegate完成bean的解析。

整个解析过程可以分3个步骤:
 
  1. 找出模块中的org.springframework.data.repository.Repository接口的实现类或者org.springframework.data.repository.RepositoryDefinition注解的修饰类,并会过滤掉org.springframework.data.repository.NoRepositoryBean注解的修饰类。找出后封装到RepositoryConfiguration中
  2. 遍历这些RepositoryConfiguration,然后构造成BeanDefinition并注册到Spring容器中。需要注意的是这些RepositoryConfiguration会以beanClass为ElasticsearchRepositoryFactoryBean这个类的方式被注册,并把对应的Repository接口当做构造参数传递给ElasticsearchRepositoryFactoryBean,还会设置相应的属性比如elasticsearchOperations、evaluationContextProvider、namedQueries、repositoryBaseClass、lazyInitqueryLookupStrategyKey
  3. ElasticsearchRepositoryFactoryBean被实例化的时候设置对应的构造参数和属性。设置完毕以后调用afterPropertiesSet方法(实现了InitializingBean接口)。在afterPropertiesSet方法内部会去创建RepositoryFactorySupport类,并进行一些初始化,比如namedQueries、repositoryBaseClass等。然后通过这个RepositoryFactorySupport的getRepository方法基于Repository接口创建出代理类,并使用AOP添加了几个MethodInterceptor

 
// 遍历基于第1步条件得到的RepositoryConfiguration集合
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
// 构造出BeanDefinitionBuilder
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

extension.postProcess(definitionBuilder, configurationSource);

if (isXml) {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
} else {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}

// 使用命名策略生成bean的名字
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName,
configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName());
}

beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// 注册到Spring容器中
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}

// build方法
public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {

Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
// 得到factoryBeanName,这里会使用extension.getRepositoryFactoryClassName()去获得
// extension.getRepositoryFactoryClassName()返回的正是ElasticsearchRepositoryFactoryBean
String factoryBeanName = configuration.getRepositoryFactoryBeanName();
factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName
: extension.getRepositoryFactoryClassName();
// 基于factoryBeanName构造BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName);

builder.getRawBeanDefinition().setSource(configuration.getSource());
// 设置ElasticsearchRepositoryFactoryBean的构造参数,这里是对应的Repository接口
// 设置一些的属性值
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());

NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(
extension.getDefaultNamedQueryLocation());

if (StringUtils.hasText(configuration.getNamedQueriesLocation())) {
definitionBuilder.setLocations(configuration.getNamedQueriesLocation());
}

builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource()));
// 查找是否有对应Repository接口的自定义实现类
String customImplementationBeanName = registerCustomImplementation(configuration);
// 存在自定义实现类的话,设置到属性中
if (customImplementationBeanName != null) {
builder.addPropertyReference("customImplementation", customImplementationBeanName);
builder.addDependsOn(customImplementationBeanName);
}

RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition(
ExtensionAwareEvaluationContextProvider.class);
evaluationContextProviderDefinition.setSource(configuration.getSource());
// 设置一些的属性值
builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition);

return builder;
}

// RepositoryFactorySupport的getRepository方法,获得Repository接口的代理类
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {

// 获取Repository的元数据
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
// 获取Repository的自定义实现类
Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass();
// 根据元数据和自定义实现类得到Repository的RepositoryInformation信息类
// 获取信息类的时候如果发现repositoryBaseClass是空的话会根据meta中的信息去自动匹配
// 具体匹配过程在下面的getRepositoryBaseClass方法中说明
RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);
// 验证
validate(information, customImplementation);
// 得到最终的目标类实例,会通过repositoryBaseClass去查找
Object target = getTargetRepository(information);

// 创建代理工厂
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
// 进行aop相关的设置
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
}
// 使用RepositoryProxyPostProcessor处理
for (RepositoryProxyPostProcessor processor : postProcessors) {
processor.postProcess(result, information);
}

if (IS_JAVA_8) {
// 如果是JDK8的话,添加DefaultMethodInvokingMethodInterceptor
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}

// 添加QueryExecutorMethodInterceptor
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
// 使用代理工厂创建出代理类,这里是使用jdk内置的代理模式
return (T) result.getProxy(classLoader);
}

// 目标类的获取
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// 如果Repository接口属于QueryDsl,抛出异常。目前还不支持
if (isQueryDslRepository(metadata.getRepositoryInterface())) {
throw new IllegalArgumentException("QueryDsl Support has not been implemented yet.");
}
// 如果主键是数值类型的话,repositoryBaseClass为NumberKeyedRepository
if (Integer.class.isAssignableFrom(metadata.getIdType())
|| Long.class.isAssignableFrom(metadata.getIdType())
|| Double.class.isAssignableFrom(metadata.getIdType())) {
return NumberKeyedRepository.class;
} else if (metadata.getIdType() == String.class) {
// 如果主键是String类型的话,repositoryBaseClass为SimpleElasticsearchRepository
return SimpleElasticsearchRepository.class;
} else if (metadata.getIdType() == UUID.class) {
// 如果主键是UUID类型的话,repositoryBaseClass为UUIDElasticsearchRepository
return UUIDElasticsearchRepository.class;
} else {
// 否则报错
throw new IllegalArgumentException("Unsupported ID type " + metadata.getIdType());
}
}

ElasticsearchRepositoryFactoryBean是一个FactoryBean接口的实现类,getObject方法返回的上面提到的getRepository方法返回的代理对象;getObjectType方法返回的是对应Repository接口类型。

我们文章一开始提到的注入TaskRepository的时候,实际上这个对象是ElasticsearchRepositoryFactoryBean类型的实例,只不过ElasticsearchRepositoryFactoryBean实现了FactoryBean接口,所以注入的时候会得到一个代理对象,这个代理对象是由jdk内置的代理生成的,并且它的target对象是SimpleElasticsearchRepository(主键是String类型)。
 
 
SpringData ES中ElasticsearchOperations的介绍
 
ElasticsearchTemplate实现了ElasticsearchOperations接口。

ElasticsearchOperations接口是SpringData对Elasticsearch操作的一层封装,比如有创建索引createIndex方法、获取索引的设置信息getSetting方法、查询对象queryForObject方法、分页查询方法queryForPage、删除文档delete方法、更新文档update方法等等。

ElasticsearchTemplate是具体的实现类,它有这些属性:
 
// elasticsearch提供的基于java的客户端连接接口。java对es集群的操作使用这个接口完成
private Client client;
// 一个转换器接口,定义了2个方法,分别可以获得MappingContext和ConversionService
// MappingContext接口用于获取所有的持久化实体和这些实体的属性
// ConversionService目前在SpringData ES中没有被使用
private ElasticsearchConverter elasticsearchConverter;
// 内部使用EntityMapper完成对象到json字符串和json字符串到对象的映射。默认使用jackson完成映射,可自定义
private ResultsMapper resultsMapper;
// 查询超时时间
private String searchTimeout;

Client接口在ElasticsearchAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public Client elasticsearchClient() {
try {
return createClient();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

ElasticsearchTemplate、ElasticsearchConverter以及SimpleElasticsearchMappingContext在ElasticsearchDataAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public ElasticsearchTemplate elasticsearchTemplate(Client client,
ElasticsearchConverter converter) {
try {
return new ElasticsearchTemplate(client, converter);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Bean
@ConditionalOnMissingBean
public ElasticsearchConverter elasticsearchConverter(
SimpleElasticsearchMappingContext mappingContext) {
return new MappingElasticsearchConverter(mappingContext);
}

@Bean
@ConditionalOnMissingBean
public SimpleElasticsearchMappingContext mappingContext() {
return new SimpleElasticsearchMappingContext();
}

 需要注意的是这个bean被自动化配置类构造的前提是它们在Spring容器中并不存在。
 
Repository的调用过程
 
以自定义的TaskRepository的save方法为例,大致的执行流程如下所示:



SimpleElasticsearchRepository的save方法具体的分析在SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题中分析过。

像自定义的Repository查询方法,或者Repository接口的自定义实现类的操作这些底层,可以去QueryExecutorMethodInterceptor中查看,大家有兴趣的可以自行查看源码。
 

spring cloud 实践-降级、限流、滚动、灰度、AB、金丝雀等等等等

回复

Spring CloudCharlesHe 发起了问题 • 1 人关注 • 0 个回复 • 574 次浏览 • 2017-06-01 23:06 • 来自相关话题

《Spring 5 官方文档》1. Spring入门指南

Spring Framework泥瓦匠BYSocket 发表了文章 • 2 个评论 • 2105 次浏览 • 2017-05-31 16:04 • 来自相关话题

1.Spring入门指南

本参考指南提供了有关Spring Framework的详细信息。它全面的介绍了Spring的所有的功能,以及Spring涉及的基础概念(如“依赖注入” “Dependency Injection”)。

如果你是刚开始使用Spring,你可能需要首先创建一个Spring Boot应用程序来开始Spring框架之旅。Spring Boot提供了一个快速(和自治的)的方式来创建一个基于Spring的生产环境。它是基于Spring框架,支持约定优于配置,并且被设计成尽可能快地让你启动和运行程序。

您可以使用start.spring.io生成一个基本项目或按照新手入门指南里的任意一个指南构建项目,例如构建一个RESTful Web服务入门指南 。为了更容易帮助你理解,这些指南都是面向任务的,其中大部分都是基于Spring Boot的。 他们还涵盖了很多Spring原型工程,在您需要解决特定问题时可以考虑使用他们。

2.介绍Spring框架

Spring 框架是一个Java平台,它为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此您可以专注于应用程序的开发。

Spring可以让您从“plain old Java objects”(POJO)中构建应用程序和通过非侵入性的POJO实现企业应用服务。此功能适用于Java SE的编程模型,全部的或部分的适应Java EE模型。

这些例子告诉你,作为一个应用程序开发人员,如何从Spring平台中受益:

写一个Java方法执行数据库事务,而无需处理具体事务的APIs。
写一个本地Java方法去远程调用,而不必处理远程调用的APIs。
写一个本地Java方法实现管理操作,而不必处理JMX APIs。
写一个本地Java方法实现消息处理,而不必处理JMS APIs。

2.1依赖注入和控制反转

Java应用程序-这是一个宽松的术语,它包括的范围从受限的嵌入式应用程序到n层的服务器端企业应用程序-通常组成程序的对象互相协作而构成正确的应用程序。因此,在一个应用程序中的对象彼此具有依赖关系(dependencies)。

虽然Java平台提供了丰富的应用程序开发功能,但它缺乏将基本的模块组织成一个整体的方法,而将该任务留给了架构师和开发人员。虽然你可以使用如工厂,抽象工厂,Builder,装饰器和Service Locator等 设计模式来构建各种类和对象实例,使他们组合成应用程序,但这些模式无非只是:最佳实践赋予的一个名字,以及这是什么样的模式,应用于哪里,它能解决的问题等等。 模式是您必须在应用程序中自己实现的形式化的最佳实践。

Spring框架控制反转(IOC)组件通过提供一系列的标准化的方法把完全不同的组件组合成一个能够使用的应用程序来解决这个问题。Spring框架把形式化的设计模式编写为优秀的对象,你可以容易的集成到自己的应用程序中。许多组织和机构使用Spring框架,以这种方式(使用Spring的模式对象)来设计健壮的,可维护的应用程序。

背景

“ 现在的问题是,什么方面的控制被(他们)反转了? ”马丁·福勒2004年在他的网站提出了这个有关控制反转(IOC)的问题 ,福勒建议重命名,使之能够自我描述,并提出了依赖注入( Dependency Injection)。

2.2模块

Spring框架的功能被有组织的分散到约20个模块中。这些模块分布在核心容器,数据访问/集成,Web,AOP(面向切面​​的编程),植入(Instrumentation),消息传输和测试,如下面的图所示。

图2.1 Spring框架概述








以下部分列出了每个可用模块,以及它们的工件名称和它们支持的主要功能。工件的名字对应的是工件标识符,使用在依赖管理工具中。

2.2.1核心容器

核心容器由以下模块组成,spring-core, spring-beans,spring-context,spring-context-support,和spring-expression (Spring表达式语言)。

spring-core和spring-beans模块提供了框架的基础功能,包括IOC和依赖注入功能。 BeanFactory是一个成熟的工厂模式的实现。你不再需要编程去实现单例模式,允许你把依赖关系的配置和描述从程序逻辑中解耦。

上下文(spring-context)模块建立在由Core和Beans模块提供的坚实的基础上:它提供一个框架式的对象访问方式,类似于一个JNDI注册表。上下文模块从Beans模块继承其功能,并添加支持国际化(使用,例如,资源集合),事件传播,资源负载,并且透明创建上下文,例如,Servlet容器。Context模块还支持Java EE的功能,如EJB,JMX和基本的远程处理。ApplicationContext接口是Context模块的焦点。 spring-context-support支持整合普通第三方库到Spring应用程序上下文,特别是用于高速缓存(ehcache,JCache)和调度(CommonJ,Quartz)的支持。

spring-expression模块提供了强大的表达式语言去支持查询和操作运行时对象图。这是对JSP 2.1规范中规定的统一表达式语言(unified EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器的内容,逻辑和算术运算,变量命名以及从Spring的IoC容器中以名称检索对象。 它还支持列表投影和选择以及常见的列表聚合。

2.2.2 AOP和Instrumentation

spring-aop模块提供了一个符合AOP联盟(要求)的面向方面的编程实现,例如,允许您定义方法拦截器和切入点(pointcuts),以便干净地解耦应该被分离的功能实现。 使用源级元数据(source-level metadata)功能,您还可以以类似于.NET属性的方式将行为信息合并到代码中。

单独的spring-aspects模块,提供了与AspectJ的集成。

spring-instrument模块提供了类植入(instrumentation)支持和类加载器的实现,可以应用在特定的应用服务器中。该spring-instrument-tomcat 模块包含了支持Tomcat的植入代理。

2.2.3消息

Spring框架4包括spring-messaging(消息传递模块),其中包含来自Spring Integration的项目,例如,Message,MessageChannel,MessageHandler,和其他用来传输消息的基础应用。该模块还包括一组用于将消息映射到方法的注释(annotations),类似于基于Spring MVC注释的编程模型。

2.2.4数据访问/集成

数据访问/集成层由JDBC,ORM,OXM,JMS和事务模块组成。

spring-jdbc模块提供了一个JDBC –抽象层,消除了需要的繁琐的JDBC编码和数据库厂商特有的错误代码解析。

spring-tx模块支持用于实现特殊接口和所有POJO(普通Java对象)的类的编程和声明式事务 管理。

spring-orm模块为流行的对象关系映射(object-relational mapping )API提供集成层,包括JPA和Hibernate。使用spring-orm模块,您可以将这些O / R映射框架与Spring提供的所有其他功能结合使用,例如前面提到的简单声明性事务管理功能。

spring-oxm模块提供了一个支持对象/ XML映射实现的抽象层,如JAXB,Castor,JiBX和XStream。

spring-jms模块(Java Messaging Service) 包含用于生产和消费消息的功能。自Spring Framework 4.1以来,它提供了与 spring-messaging模块的集成。

2.2.5 Web

Web层由spring-web,spring-webmvc和spring-websocket 模块组成。

spring-web模块提供基本的面向Web的集成功能,例如多部分文件上传功能,以及初始化一个使用了Servlet侦听器和面向Web的应用程序上下文的IoC容器。它还包含一个HTTP客户端和Spring的远程支持的Web相关部分。

spring-webmvc模块(也称为Web-Servlet模块)包含用于Web应用程序的Spring的模型-视图-控制器(MVC)和REST Web Services实现。 Spring的MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。

2.2.6测试

spring-test模块支持使用JUnit或TestNG对Spring组件进行单元测试和 集成测试。它提供了Spring ApplicationContexts的一致加载和这些上下文的缓存。它还提供可用于独立测试代码的模仿(mock)对象。

2.3使用场景

之前描述的构建模块使Spring成为许多应用场景的理性选择,从在资源受限设备上运行的嵌入式应用程序到使用Spring的事务管理功能和Web框架集成的全面的企业应用程序。

图2.2 典型的成熟完整的Spring Web应用程序







Spring的声明式事务管理功能使Web应用程序完全事务性,就像使用EJB容器管理的事务一样。所有您的定制业务逻辑都可以使用简单的POJO实现,并由Spring的IoC容器进行管理。附加服务包括支持发送电子邮件和独立于Web层的验证,可让您选择执行验证规则的位置。 Spring的ORM支持与JPA和Hibernate集成;例如,当使用Hibernate时,可以继续使用现有的映射文件和标准的Hibernate SessionFactory配置。表单控制器将Web层与域模型无缝集成,从而无需ActionForms或将HTTP参数转换为域模型的值的其他类。

图2.3使用第三方web框架的Spring中间层








有时情况不允许你完全切换到不同的框架。 Spring框架并不强制您使用其中的一切;这不是一个全有或全无的解决方案。使用Struts,Tapestry,JSF或其他UI框架构建的现有前端可以与基于Spring的中间层集成,从而允许您使用Spring事务功能。您只需要使用ApplicationContext连接您的业务逻辑,并使用WebApplicationContext来集成您的Web层。

图2.4 远程使用场景








当您需要通过Web服务访问现有代码时,你可以使用Spring的 Hessian-,Rmi-或HttpInvokerProxyFactoryBean类。启用对现有应用程序的远程访问并不困难。

图2.5  EJBs – 包装现有的POJOs








Spring Framework还为Enterprise JavaBeans提供了一个访问和抽象层,使您能够重用现有的POJO,并将其包装在无状态会话bean中,以便在可能需要声明式安全性的, 可扩展的,故障安全的Web应用程序中使用。

2.3.1依赖管理和命名约定

依赖关系管理和依赖注入是不同的。为了将Spring的这些不错的功能(如依赖注入)集成到应用程序中,您需要组装所有需要的库(jar文件),并在运行时导入到类路径(classpath)中,也有可能在编译时就需要加入类路径。这些依赖关系不是注入的虚拟组件,而是文件系统中的物理资源(通常是这样)。依赖关系管理的过程包括定位这些资源,存储它们并将其添加到类路径中。依赖关系可以是直接的(例如,我的应用程序在运行时依赖于Spring)或间接的(例如我的应用程序依赖于commons-dbcp ,而commons-dbcp 又依赖于commons-pool)。间接依赖关系具有“传递性”,它们是最难识别和管理的依赖关系。

如果你要使用Spring,你需要获得一个包含你所需要的Spring模块的jar库的副本。为了使这更容易,Spring被打包为一组尽可能分离依赖关系的模块,例如,如果您不想编写Web应用程序,则不需要spring-web模块。要引用本指南中的Spring库模块,我们使用一个简写命名约定spring- *或spring – *.jar,其中*表示模块的简称(例如spring-core,spring-webmvc,spring-jms等) )。您实际使用的jar文件名通常是与版本号连接的模块名称(例如spring-core-5.0.0.M5.jar)。

Spring框架的每个版本都会发布到以下几个地方:

Maven Central,它是Maven查询的默认存储库,不需要任何特殊配置。 Spring的许多常见的库也可以从Maven Central获得,Spring社区的大部分使用Maven进行依赖关系管理,所以这对他们来说很方便。这里的jar的名字是spring – * – <version> .jar,Maven groupId是org.springframework。
在专门用于Spring的公共Maven存储库中。除了最终的GA版本,该存储库还承载开发快照和里程碑版本。 jar文件名与Maven Central格式相同,因此这是一个有用的地方,可以将开发中的版本的Spring与在Maven Central中部署的其他库配合使用。该存储库还包含集中分发的zip文件,其中包含所有Spring jar,捆绑在一起以便于下载。

所以你需要决定的第一件事是如何管理你的依赖关系:我们通常建议使用像Maven,Gradle或Ivy这样的自动化系统,但你也可以通过自己下载所有的jar来手动执行。

您将在下面找到Spring artifacts列表。有关每个模块的更完整的描述,第2.2节“模块”.

表2.1  Spring框架的Artifacts

GroupId                   ArtifactId                                Description(描述)
org.springframework spring-aop                             Proxy-based AOP support
org.springframework spring-aspects                       AspectJ based aspects
org.springframework spring-beans                          Beans support, including Groovy
org.springframework spring-context                        Application context runtime, including scheduling and remoting abstractions
org.springframework spring-context-support           Support classes for integrating common third-party libraries into a Spring application context
org.springframework spring-core                            Core utilities, used by many other Spring modules
org.springframework spring-expression                    Spring Expression Language (SpEL)
org.springframework spring-instrument                     Instrumentation agent for JVM bootstrapping
org.springframework spring-instrument-tomcat         Instrumentation agent for Tomcat
org.springframework spring-jdbc                             JDBC support package, including DataSource setup and JDBC access support
org.springframework spring-jms                              JMS support package, including helper classes to send and receive JMS messages
org.springframework spring-messaging                    Support for messaging architectures and protocols
org.springframework spring-orm                             Object/Relational Mapping, including JPA and Hibernate support
org.springframework spring-oxm                             Object/XML Mapping
org.springframework spring-test                             Support for unit testing and integration testing Spring components
org.springframework spring-tx                                Transaction infrastructure, including DAO support and JCA integration
org.springframework spring-web                             Web support packages, including client and web remoting
org.springframework spring-webmvc                       REST Web Services and model-view-controller implementation for web applications
org.springframework spring-websocket                   WebSocket and SockJS implementations, including STOMP support

Spring的依赖和依赖于Spring

虽然Spring为大量企业和其他外部工具提供集成和支持,但它有意将其强制性的依赖保持在最低限度:在使用Spring用于简单的用例时,您不必定位和下载(甚至自动的去做)大量的jar库。对于基本依赖注入功能,只有一个强制性的外部依赖关系,也就是用于日志记录的依赖(有关日志记录选项的更详细描述,请参阅下文)。

接下来,我们概述了配置依赖于Spring的应用程序所需的基本步骤,首先是使用Maven,然后使用Gradle,最后使用Ivy。在任何情况下,如果不清楚,请参阅依赖关系管理系统的文档,或查看一些示例代码 – Spring本身在构建时使用Gradle来管理依赖关系,我们的示例主要使用Gradle或Maven。

Maven的依赖管理

如果您使用Maven进行依赖关系管理,则甚至不需要显式提供依赖关系。 例如,要创建应用程序上下文并使用依赖注入来配置应用程序,您的Maven依赖配置如下所示:<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.0.M5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
(依赖配置)就是这样。注意,如果您不需要针对Spring API进行编译,那么范围(scope)可以被声明为运行时,通常情况下这是基本的依赖注入用例。

以上示例适用于Maven Central存储库。要使用Spring Maven仓库(例如,使用里程碑或开发中的快照版本),您需要在Maven配置中指定仓库位置。完整版本:<repositories>
<repository>
<id>io.spring.repo.maven.release</id>
<url>http://repo.spring.io/release/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
对于里程碑(milestones):<repositories>
<repository>
<id>io.spring.repo.maven.milestone</id>
<url>http://repo.spring.io/milestone/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
而对于快照(snapshots):<repositories>
<repository>
<id>io.spring.repo.maven.snapshot</id>
<url>http://repo.spring.io/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
Maven的“材料清单”依赖

使用Maven时,可能会意外混合不同版本的Spring JAR。例如,您可能会发现第三方库或另一个Spring项目会传递依赖于旧版本的Spring JARs。如果您忘记自己明确声明直接依赖,可能会出现各种意外问题。

为了克服这些问题,Maven支持“材料清单(bill of materials)”(BOM)依赖的概念。您可以在dependencyManagement部分中导入spring-framework-bom,以确保所有Spring依赖(直接和传递)都是相同的版本<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.0.0.M5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
使用BOM的另外一个好处是,您不再需要在依赖于Spring Framework artifacts时指定<version>属性:<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependencies>
 
Gradle 依赖管理

要使用Gradle 构建系统的Spring仓库,请在仓库部分中包含适当的URL:repositories {
mavenCentral()
// and optionally...
maven { url "http://repo.spring.io/release" }
}
 
您可以根据需要将repositoriesURL从/release更改为/milestone或/snapshot。一旦repositories被配置,你可以通常使用Gradle方式来声明依赖:dependencies {
compile("org.springframework:spring-context:5.0.0.M5")
testCompile("org.springframework:spring-test:5.0.0.M5")
}
Ivy依赖管理

如果您喜欢使用 Ivy 来管理依赖关系,那么还有类似的配置选项。 要配置Ivy指向Spring仓库,请将以下解析器添加到您的ivysettings.xml中:<resolvers>
<ibiblio name="io.spring.repo.maven.release"
m2compatible="true"
root="http://repo.spring.io/release/"/>
</resolvers>
您可以更改root从URL /release/到/milestone/或/snapshot/适当。

配置完成后,您可以在通常的方式添加依赖。例如(在ivy.xml):

您可以根据需要将rootURL从 /release/更改为/milestone/或/snapshot/。 配置完成后,您可以按通常的方式添加依赖项。例如(在ivy.xml中):<dependency org="org.springframework"
name="spring-core" rev="5.0.0.M5" conf="compile->runtime"/>
 
Zip文件发行

虽然使用依赖关系管理的构建系统是推荐的获取Spring框架的方法,但仍然可以下载发布的zip文件。

Zip文件发布到Spring Maven存储库(这仅仅是为了我们的方便,您不需要使用Maven或任何其他构建系统才能下载它们)。

要下载发布的zip文件,打开Web浏览器到http://repo.spring.io/release/ ... pring,并为所需的版本选择相应的子文件夹。zip文件以-dist.zip结尾,例如spring-framework- {spring-version} -RELEASE-dist.zip。里程碑和 快照也会发布在这里。

2.3.2 日志

日志是Spring非常重要的依赖,因为a)它是唯一的强制性外部依赖关系,b)每个人都喜欢看到他们使用的工具的一些输出,以及c)Spring集成了许多其他工具,都会具有日志依赖关系。应用程序开发人员的目标之一通常是将统一的日志配置放在整个应用程序的中央位置,包括所有外部组件。这比以前有更多的困难,因为有这么多的日志框架可以选择。

Spring中的强制性日志依赖关系是Jakarta Commons Logging API(JCL)。我们针对JCL进行编译,我们还使JCL Log 对象对于扩展了Spring Framework的类可见。对于用户来说,所有版本的Spring都使用相同的日志库很重要:迁移很简单,因为即使扩展了Spring的应用程序,但仍然保留了向后兼容性。我们这样做的方式是使Spring中的一个模块显式地依赖于commons-logging(遵循JCL规范的实现),然后在编译时使所有其他模块依赖于它。例如,如果您使用Maven,并且想知道在哪里可以获取对commons-logging的依赖,那么它来自Spring,特别是来自名为spring-core的中央模块。

commons-logging 的好处在于,您不需要任何其他操作来使您的应用程序正常工作。它具有运行时发现算法,可以在类路径中查找其他日志框架,并使用它认为合适的日志框架(或者您可以告诉它需要哪一个)。如果没有其他可用的,您可以从JDK(简称java.util.logging或JUL)获得好用的日志框架。您应该发现,在大多数情况下,您的Spring应用程序可以快乐地把日志输出到控制台,这很重要。

不使用Commons Logging

不幸的是,commons-logging中的运行时发现算法虽然对最终用户很方便,但是也有很多问题。如果我们可以把时空倒回,现在开始使用Spring去开始一个新的项目,我们会使用不同的日志依赖关系。第一选择可能是Simple Logging Facade for Java(SLF4J),它也被许多其他使用Spring的应用工具所使用。

基本上有两种方法来关闭commons-logging:

排除spring-core模块的依赖关系(因为它是明确依赖于commons-logging的唯一模块)
依赖于一个特殊的commons-logging依赖关系,用空的jar代替库(更多的细节可以在SLF4J FAQ中找到)

要排除commons-logging,请将以下内容添加到dependencyManagement部分:<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.M5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
 
现在这个应用程序可能已经被破坏了,因为在类路径中没有实现JCL API,所以要修复它,必须提供一个新的JCL API。在下一节中,我们向您展示如何使用SLF4J提供JCL的替代实现。

使用SLF4J

SLF4J是一个更清洁的依赖关系,在运行时比commons-logging更有效率,因为它使用编译时绑定,而不是其集成的其他日志框架的运行时发现。这也意味着你必须更加明确地说明你在运行时想要发生什么,并声明它或相应地进行配置。 SLF4J提供对许多常见的日志框架的绑定,因此通常可以选择一个已经使用的日志框架,并绑定到该框架进行配置和管理。

SLF4J提供了绑定到许多常见的日志框架的方法,包括JCL,它也是可以反转的:是其他日志框架和自身(Spring)之间的桥梁。所以要使用SLF4J与Spring,您需要使用SLF4J-JCL bridge替换commons-logging依赖关系。一旦完成,那么在Spring中日志调用将被转换为对SLF4J API的日志调用,因此如果应用程序中的其他库使用该API,那么您有一个统一的地方来配置和管理日志记录。

常见的选择可能是将Spring链接到SLF4J,然后提供从SLF4J到Log4j的显式绑定。您需要提供多个依赖关系(并排除现有的commons-logging):the bridge,Log4j的SLF4J实现和Log4j实现本身。在Maven你会这样做:<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.M5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.22</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
这可能看起来像很多依赖只是为了获得一些日志。是的,但它是可选的,它应该比具有类加载器问题的commons-logging更好,特别是如果你在一个要求严格的容器,如OSGi平台。而且还有一个性能优势,因为绑定在编译时不是运行时。

使用较少步骤并生成较少依赖关系的SLF4J用户更为常见的选择是直接绑定到Logback。这将删除额外的绑定步骤,因为Logback直接实现SLF4J,因此您只需要依赖于两个库(jcl-over-slf4j和logback)而不是四个。如果这样做,您可能还需要从其他外部依赖关系(不是Spring)中排除slf4j-api依赖关系,因为您只需要在类路径中使用该API的一个版本。

使用的Log4j

  [[Note]]
Log4j的1.x版本已经寿终正寝,以下的内容特指Log4j 2

许多人使用Log4j 作为配置和管理日志的日志框架。它是高效和成熟的,当我们构建和测试Spring,实际上它是在运行时使用的。 Spring还提供了一些用于配置和初始化Log4j的实用功能,因此它在某些模块中对Log4j具有可选的编译时依赖性。

要使用JCL和Log4j,所有你需要做的就是把Log4j加到类路径,并为其提供一个配置文件(log4j2.xml,log4j2.properties或其他 支持的配置格式)。对于Maven用户,所需的最少依赖关系是:<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
 
如果你也想使用SLF4J,还需要以下依赖关系:<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>
 
下面是一个例子log4j2.xml用来把日志输出到控制台:<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework.beans.factory" level="DEBUG"/>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
运行时容器和原生JCL

许多人在那些本身提供JCL实现的容器中运行他们的Spring应用程序。IBM WebSphere应用服务器(WAS)为例。这往往会导致问题,遗憾的是没有一劳永逸的解决方案; 在大多数情况下,简单地从您的应用程序排除commons-logging是不够的。

要清楚这一点:报告的问题通常不是JCL本身,甚至commons-logging:而是将commons-logging绑定到另一个框架(通常是Log4j)。这可能会失败,因为commons-logging更改了在一些容器中发现的旧版本(1.0)和大多数人现在使用的版本(1.1)之间执行运行时发现的方式。 Spring不使用JCL API的任何不寻常的部分,所以没有什么破坏,但是一旦Spring或您的应用程序尝试输出日志,您可以发现与Log4j的绑定不起作用

在这种情况下,使用WAS最简单的方法是反转类加载器层次结构(IBM将其称为”parent last”),以便应用程序控制JCL依赖关系,而不是容器。该选项并不总是开放的,但是在公共领域还有许多其他建议可供选择,您的里程(集成程度)可能因容器的确切版本和功能集而异。
 
 本文作者: 胡永
原文链接: http://ifeve.com/overview-gett ... ring/
版权归作者所有,转载请注明出处 查看全部
1.Spring入门指南

本参考指南提供了有关Spring Framework的详细信息。它全面的介绍了Spring的所有的功能,以及Spring涉及的基础概念(如“依赖注入” “Dependency Injection”)。

如果你是刚开始使用Spring,你可能需要首先创建一个Spring Boot应用程序来开始Spring框架之旅。Spring Boot提供了一个快速(和自治的)的方式来创建一个基于Spring的生产环境。它是基于Spring框架,支持约定优于配置,并且被设计成尽可能快地让你启动和运行程序。

您可以使用start.spring.io生成一个基本项目或按照新手入门指南里的任意一个指南构建项目,例如构建一个RESTful Web服务入门指南 。为了更容易帮助你理解,这些指南都是面向任务的,其中大部分都是基于Spring Boot的。 他们还涵盖了很多Spring原型工程,在您需要解决特定问题时可以考虑使用他们。

2.介绍Spring框架

Spring 框架是一个Java平台,它为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此您可以专注于应用程序的开发。

Spring可以让您从“plain old Java objects”(POJO)中构建应用程序和通过非侵入性的POJO实现企业应用服务。此功能适用于Java SE的编程模型,全部的或部分的适应Java EE模型。

这些例子告诉你,作为一个应用程序开发人员,如何从Spring平台中受益:

写一个Java方法执行数据库事务,而无需处理具体事务的APIs。
写一个本地Java方法去远程调用,而不必处理远程调用的APIs。
写一个本地Java方法实现管理操作,而不必处理JMX APIs。
写一个本地Java方法实现消息处理,而不必处理JMS APIs。

2.1依赖注入和控制反转

Java应用程序-这是一个宽松的术语,它包括的范围从受限的嵌入式应用程序到n层的服务器端企业应用程序-通常组成程序的对象互相协作而构成正确的应用程序。因此,在一个应用程序中的对象彼此具有依赖关系(dependencies)。

虽然Java平台提供了丰富的应用程序开发功能,但它缺乏将基本的模块组织成一个整体的方法,而将该任务留给了架构师和开发人员。虽然你可以使用如工厂,抽象工厂,Builder,装饰器和Service Locator等 设计模式来构建各种类和对象实例,使他们组合成应用程序,但这些模式无非只是:最佳实践赋予的一个名字,以及这是什么样的模式,应用于哪里,它能解决的问题等等。 模式是您必须在应用程序中自己实现的形式化的最佳实践。

Spring框架控制反转(IOC)组件通过提供一系列的标准化的方法把完全不同的组件组合成一个能够使用的应用程序来解决这个问题。Spring框架把形式化的设计模式编写为优秀的对象,你可以容易的集成到自己的应用程序中。许多组织和机构使用Spring框架,以这种方式(使用Spring的模式对象)来设计健壮的,可维护的应用程序。

背景

“ 现在的问题是,什么方面的控制被(他们)反转了? ”马丁·福勒2004年在他的网站提出了这个有关控制反转(IOC)的问题 ,福勒建议重命名,使之能够自我描述,并提出了依赖注入( Dependency Injection)。

2.2模块

Spring框架的功能被有组织的分散到约20个模块中。这些模块分布在核心容器,数据访问/集成,Web,AOP(面向切面​​的编程),植入(Instrumentation),消息传输和测试,如下面的图所示。

图2.1 Spring框架概述

s1.png




以下部分列出了每个可用模块,以及它们的工件名称和它们支持的主要功能。工件的名字对应的是工件标识符,使用在依赖管理工具中。

2.2.1核心容器

核心容器由以下模块组成,spring-core, spring-beans,spring-context,spring-context-support,和spring-expression (Spring表达式语言)。

spring-core和spring-beans模块提供了框架的基础功能,包括IOC和依赖注入功能。 BeanFactory是一个成熟的工厂模式的实现。你不再需要编程去实现单例模式,允许你把依赖关系的配置和描述从程序逻辑中解耦。

上下文(spring-context)模块建立在由Core和Beans模块提供的坚实的基础上:它提供一个框架式的对象访问方式,类似于一个JNDI注册表。上下文模块从Beans模块继承其功能,并添加支持国际化(使用,例如,资源集合),事件传播,资源负载,并且透明创建上下文,例如,Servlet容器。Context模块还支持Java EE的功能,如EJB,JMX和基本的远程处理。ApplicationContext接口是Context模块的焦点。 spring-context-support支持整合普通第三方库到Spring应用程序上下文,特别是用于高速缓存(ehcache,JCache)和调度(CommonJ,Quartz)的支持。

spring-expression模块提供了强大的表达式语言去支持查询和操作运行时对象图。这是对JSP 2.1规范中规定的统一表达式语言(unified EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器的内容,逻辑和算术运算,变量命名以及从Spring的IoC容器中以名称检索对象。 它还支持列表投影和选择以及常见的列表聚合。

2.2.2 AOP和Instrumentation

spring-aop模块提供了一个符合AOP联盟(要求)的面向方面的编程实现,例如,允许您定义方法拦截器和切入点(pointcuts),以便干净地解耦应该被分离的功能实现。 使用源级元数据(source-level metadata)功能,您还可以以类似于.NET属性的方式将行为信息合并到代码中。

单独的spring-aspects模块,提供了与AspectJ的集成。

spring-instrument模块提供了类植入(instrumentation)支持和类加载器的实现,可以应用在特定的应用服务器中。该spring-instrument-tomcat 模块包含了支持Tomcat的植入代理。

2.2.3消息

Spring框架4包括spring-messaging(消息传递模块),其中包含来自Spring Integration的项目,例如,Message,MessageChannel,MessageHandler,和其他用来传输消息的基础应用。该模块还包括一组用于将消息映射到方法的注释(annotations),类似于基于Spring MVC注释的编程模型。

2.2.4数据访问/集成

数据访问/集成层由JDBC,ORM,OXM,JMS和事务模块组成。

spring-jdbc模块提供了一个JDBC –抽象层,消除了需要的繁琐的JDBC编码和数据库厂商特有的错误代码解析。

spring-tx模块支持用于实现特殊接口和所有POJO(普通Java对象)的类的编程和声明式事务 管理。

spring-orm模块为流行的对象关系映射(object-relational mapping )API提供集成层,包括JPA和Hibernate。使用spring-orm模块,您可以将这些O / R映射框架与Spring提供的所有其他功能结合使用,例如前面提到的简单声明性事务管理功能。

spring-oxm模块提供了一个支持对象/ XML映射实现的抽象层,如JAXB,Castor,JiBX和XStream。

spring-jms模块(Java Messaging Service) 包含用于生产和消费消息的功能。自Spring Framework 4.1以来,它提供了与 spring-messaging模块的集成。

2.2.5 Web

Web层由spring-web,spring-webmvc和spring-websocket 模块组成。

spring-web模块提供基本的面向Web的集成功能,例如多部分文件上传功能,以及初始化一个使用了Servlet侦听器和面向Web的应用程序上下文的IoC容器。它还包含一个HTTP客户端和Spring的远程支持的Web相关部分。

spring-webmvc模块(也称为Web-Servlet模块)包含用于Web应用程序的Spring的模型-视图-控制器(MVC)和REST Web Services实现。 Spring的MVC框架提供了领域模型代码和Web表单之间的清晰分离,并与Spring Framework的所有其他功能集成。

2.2.6测试

spring-test模块支持使用JUnit或TestNG对Spring组件进行单元测试和 集成测试。它提供了Spring ApplicationContexts的一致加载和这些上下文的缓存。它还提供可用于独立测试代码的模仿(mock)对象。

2.3使用场景

之前描述的构建模块使Spring成为许多应用场景的理性选择,从在资源受限设备上运行的嵌入式应用程序到使用Spring的事务管理功能和Web框架集成的全面的企业应用程序。

图2.2 典型的成熟完整的Spring Web应用程序

s3.png



Spring的声明式事务管理功能使Web应用程序完全事务性,就像使用EJB容器管理的事务一样。所有您的定制业务逻辑都可以使用简单的POJO实现,并由Spring的IoC容器进行管理。附加服务包括支持发送电子邮件和独立于Web层的验证,可让您选择执行验证规则的位置。 Spring的ORM支持与JPA和Hibernate集成;例如,当使用Hibernate时,可以继续使用现有的映射文件和标准的Hibernate SessionFactory配置。表单控制器将Web层与域模型无缝集成,从而无需ActionForms或将HTTP参数转换为域模型的值的其他类。

图2.3使用第三方web框架的Spring中间层

2.3_.png




有时情况不允许你完全切换到不同的框架。 Spring框架并不强制您使用其中的一切;这不是一个全有或全无的解决方案。使用Struts,Tapestry,JSF或其他UI框架构建的现有前端可以与基于Spring的中间层集成,从而允许您使用Spring事务功能。您只需要使用ApplicationContext连接您的业务逻辑,并使用WebApplicationContext来集成您的Web层。

图2.4 远程使用场景


2.4_.png



当您需要通过Web服务访问现有代码时,你可以使用Spring的 Hessian-,Rmi-或HttpInvokerProxyFactoryBean类。启用对现有应用程序的远程访问并不困难。

图2.5  EJBs – 包装现有的POJOs

2.5_.png




Spring Framework还为Enterprise JavaBeans提供了一个访问和抽象层,使您能够重用现有的POJO,并将其包装在无状态会话bean中,以便在可能需要声明式安全性的, 可扩展的,故障安全的Web应用程序中使用。

2.3.1依赖管理和命名约定

依赖关系管理和依赖注入是不同的。为了将Spring的这些不错的功能(如依赖注入)集成到应用程序中,您需要组装所有需要的库(jar文件),并在运行时导入到类路径(classpath)中,也有可能在编译时就需要加入类路径。这些依赖关系不是注入的虚拟组件,而是文件系统中的物理资源(通常是这样)。依赖关系管理的过程包括定位这些资源,存储它们并将其添加到类路径中。依赖关系可以是直接的(例如,我的应用程序在运行时依赖于Spring)或间接的(例如我的应用程序依赖于commons-dbcp ,而commons-dbcp 又依赖于commons-pool)。间接依赖关系具有“传递性”,它们是最难识别和管理的依赖关系。

如果你要使用Spring,你需要获得一个包含你所需要的Spring模块的jar库的副本。为了使这更容易,Spring被打包为一组尽可能分离依赖关系的模块,例如,如果您不想编写Web应用程序,则不需要spring-web模块。要引用本指南中的Spring库模块,我们使用一个简写命名约定spring- *或spring – *.jar,其中*表示模块的简称(例如spring-core,spring-webmvc,spring-jms等) )。您实际使用的jar文件名通常是与版本号连接的模块名称(例如spring-core-5.0.0.M5.jar)。

Spring框架的每个版本都会发布到以下几个地方:

Maven Central,它是Maven查询的默认存储库,不需要任何特殊配置。 Spring的许多常见的库也可以从Maven Central获得,Spring社区的大部分使用Maven进行依赖关系管理,所以这对他们来说很方便。这里的jar的名字是spring – * – <version> .jar,Maven groupId是org.springframework。
在专门用于Spring的公共Maven存储库中。除了最终的GA版本,该存储库还承载开发快照和里程碑版本。 jar文件名与Maven Central格式相同,因此这是一个有用的地方,可以将开发中的版本的Spring与在Maven Central中部署的其他库配合使用。该存储库还包含集中分发的zip文件,其中包含所有Spring jar,捆绑在一起以便于下载。

所以你需要决定的第一件事是如何管理你的依赖关系:我们通常建议使用像Maven,Gradle或Ivy这样的自动化系统,但你也可以通过自己下载所有的jar来手动执行。

您将在下面找到Spring artifacts列表。有关每个模块的更完整的描述,第2.2节“模块”.

表2.1  Spring框架的Artifacts

GroupId                   ArtifactId                                Description(描述)
org.springframework spring-aop                             Proxy-based AOP support
org.springframework spring-aspects                       AspectJ based aspects
org.springframework spring-beans                          Beans support, including Groovy
org.springframework spring-context                        Application context runtime, including scheduling and remoting abstractions
org.springframework spring-context-support           Support classes for integrating common third-party libraries into a Spring application context
org.springframework spring-core                            Core utilities, used by many other Spring modules
org.springframework spring-expression                    Spring Expression Language (SpEL)
org.springframework spring-instrument                     Instrumentation agent for JVM bootstrapping
org.springframework spring-instrument-tomcat         Instrumentation agent for Tomcat
org.springframework spring-jdbc                             JDBC support package, including DataSource setup and JDBC access support
org.springframework spring-jms                              JMS support package, including helper classes to send and receive JMS messages
org.springframework spring-messaging                    Support for messaging architectures and protocols
org.springframework spring-orm                             Object/Relational Mapping, including JPA and Hibernate support
org.springframework spring-oxm                             Object/XML Mapping
org.springframework spring-test                             Support for unit testing and integration testing Spring components
org.springframework spring-tx                                Transaction infrastructure, including DAO support and JCA integration
org.springframework spring-web                             Web support packages, including client and web remoting
org.springframework spring-webmvc                       REST Web Services and model-view-controller implementation for web applications
org.springframework spring-websocket                   WebSocket and SockJS implementations, including STOMP support

Spring的依赖和依赖于Spring

虽然Spring为大量企业和其他外部工具提供集成和支持,但它有意将其强制性的依赖保持在最低限度:在使用Spring用于简单的用例时,您不必定位和下载(甚至自动的去做)大量的jar库。对于基本依赖注入功能,只有一个强制性的外部依赖关系,也就是用于日志记录的依赖(有关日志记录选项的更详细描述,请参阅下文)。

接下来,我们概述了配置依赖于Spring的应用程序所需的基本步骤,首先是使用Maven,然后使用Gradle,最后使用Ivy。在任何情况下,如果不清楚,请参阅依赖关系管理系统的文档,或查看一些示例代码 – Spring本身在构建时使用Gradle来管理依赖关系,我们的示例主要使用Gradle或Maven。

Maven的依赖管理

如果您使用Maven进行依赖关系管理,则甚至不需要显式提供依赖关系。 例如,要创建应用程序上下文并使用依赖注入来配置应用程序,您的Maven依赖配置如下所示:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.0.M5</version>
<scope>runtime</scope>
</dependency>
</dependencies>

(依赖配置)就是这样。注意,如果您不需要针对Spring API进行编译,那么范围(scope)可以被声明为运行时,通常情况下这是基本的依赖注入用例。

以上示例适用于Maven Central存储库。要使用Spring Maven仓库(例如,使用里程碑或开发中的快照版本),您需要在Maven配置中指定仓库位置。完整版本:
<repositories>
<repository>
<id>io.spring.repo.maven.release</id>
<url>http://repo.spring.io/release/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>

对于里程碑(milestones):
<repositories>
<repository>
<id>io.spring.repo.maven.milestone</id>
<url>http://repo.spring.io/milestone/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>

而对于快照(snapshots):
<repositories>
<repository>
<id>io.spring.repo.maven.snapshot</id>
<url>http://repo.spring.io/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>

Maven的“材料清单”依赖

使用Maven时,可能会意外混合不同版本的Spring JAR。例如,您可能会发现第三方库或另一个Spring项目会传递依赖于旧版本的Spring JARs。如果您忘记自己明确声明直接依赖,可能会出现各种意外问题。

为了克服这些问题,Maven支持“材料清单(bill of materials)”(BOM)依赖的概念。您可以在dependencyManagement部分中导入spring-framework-bom,以确保所有Spring依赖(直接和传递)都是相同的版本
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.0.0.M5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

使用BOM的另外一个好处是,您不再需要在依赖于Spring Framework artifacts时指定<version>属性:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependencies>

 
Gradle 依赖管理

要使用Gradle 构建系统的Spring仓库,请在仓库部分中包含适当的URL:
repositories {
mavenCentral()
// and optionally...
maven { url "http://repo.spring.io/release" }
}

 
您可以根据需要将repositoriesURL从/release更改为/milestone或/snapshot。一旦repositories被配置,你可以通常使用Gradle方式来声明依赖:
dependencies {
compile("org.springframework:spring-context:5.0.0.M5")
testCompile("org.springframework:spring-test:5.0.0.M5")
}

Ivy依赖管理

如果您喜欢使用 Ivy 来管理依赖关系,那么还有类似的配置选项。 要配置Ivy指向Spring仓库,请将以下解析器添加到您的ivysettings.xml中:
<resolvers>
<ibiblio name="io.spring.repo.maven.release"
m2compatible="true"
root="http://repo.spring.io/release/"/>
</resolvers>

您可以更改root从URL /release/到/milestone/或/snapshot/适当。

配置完成后,您可以在通常的方式添加依赖。例如(在ivy.xml):

您可以根据需要将rootURL从 /release/更改为/milestone/或/snapshot/。 配置完成后,您可以按通常的方式添加依赖项。例如(在ivy.xml中):
<dependency org="org.springframework"
name="spring-core" rev="5.0.0.M5" conf="compile->runtime"/>

 
Zip文件发行

虽然使用依赖关系管理的构建系统是推荐的获取Spring框架的方法,但仍然可以下载发布的zip文件。

Zip文件发布到Spring Maven存储库(这仅仅是为了我们的方便,您不需要使用Maven或任何其他构建系统才能下载它们)。

要下载发布的zip文件,打开Web浏览器到http://repo.spring.io/release/ ... pring,并为所需的版本选择相应的子文件夹。zip文件以-dist.zip结尾,例如spring-framework- {spring-version} -RELEASE-dist.zip。里程碑和 快照也会发布在这里。

2.3.2 日志

日志是Spring非常重要的依赖,因为a)它是唯一的强制性外部依赖关系,b)每个人都喜欢看到他们使用的工具的一些输出,以及c)Spring集成了许多其他工具,都会具有日志依赖关系。应用程序开发人员的目标之一通常是将统一的日志配置放在整个应用程序的中央位置,包括所有外部组件。这比以前有更多的困难,因为有这么多的日志框架可以选择。

Spring中的强制性日志依赖关系是Jakarta Commons Logging API(JCL)。我们针对JCL进行编译,我们还使JCL Log 对象对于扩展了Spring Framework的类可见。对于用户来说,所有版本的Spring都使用相同的日志库很重要:迁移很简单,因为即使扩展了Spring的应用程序,但仍然保留了向后兼容性。我们这样做的方式是使Spring中的一个模块显式地依赖于commons-logging(遵循JCL规范的实现),然后在编译时使所有其他模块依赖于它。例如,如果您使用Maven,并且想知道在哪里可以获取对commons-logging的依赖,那么它来自Spring,特别是来自名为spring-core的中央模块。

commons-logging 的好处在于,您不需要任何其他操作来使您的应用程序正常工作。它具有运行时发现算法,可以在类路径中查找其他日志框架,并使用它认为合适的日志框架(或者您可以告诉它需要哪一个)。如果没有其他可用的,您可以从JDK(简称java.util.logging或JUL)获得好用的日志框架。您应该发现,在大多数情况下,您的Spring应用程序可以快乐地把日志输出到控制台,这很重要。

不使用Commons Logging

不幸的是,commons-logging中的运行时发现算法虽然对最终用户很方便,但是也有很多问题。如果我们可以把时空倒回,现在开始使用Spring去开始一个新的项目,我们会使用不同的日志依赖关系。第一选择可能是Simple Logging Facade for Java(SLF4J),它也被许多其他使用Spring的应用工具所使用。

基本上有两种方法来关闭commons-logging:

排除spring-core模块的依赖关系(因为它是明确依赖于commons-logging的唯一模块)
依赖于一个特殊的commons-logging依赖关系,用空的jar代替库(更多的细节可以在SLF4J FAQ中找到)

要排除commons-logging,请将以下内容添加到dependencyManagement部分:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.M5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

 
现在这个应用程序可能已经被破坏了,因为在类路径中没有实现JCL API,所以要修复它,必须提供一个新的JCL API。在下一节中,我们向您展示如何使用SLF4J提供JCL的替代实现。

使用SLF4J

SLF4J是一个更清洁的依赖关系,在运行时比commons-logging更有效率,因为它使用编译时绑定,而不是其集成的其他日志框架的运行时发现。这也意味着你必须更加明确地说明你在运行时想要发生什么,并声明它或相应地进行配置。 SLF4J提供对许多常见的日志框架的绑定,因此通常可以选择一个已经使用的日志框架,并绑定到该框架进行配置和管理。

SLF4J提供了绑定到许多常见的日志框架的方法,包括JCL,它也是可以反转的:是其他日志框架和自身(Spring)之间的桥梁。所以要使用SLF4J与Spring,您需要使用SLF4J-JCL bridge替换commons-logging依赖关系。一旦完成,那么在Spring中日志调用将被转换为对SLF4J API的日志调用,因此如果应用程序中的其他库使用该API,那么您有一个统一的地方来配置和管理日志记录。

常见的选择可能是将Spring链接到SLF4J,然后提供从SLF4J到Log4j的显式绑定。您需要提供多个依赖关系(并排除现有的commons-logging):the bridge,Log4j的SLF4J实现和Log4j实现本身。在Maven你会这样做:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.0.M5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.22</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
</dependencies>

这可能看起来像很多依赖只是为了获得一些日志。是的,但它是可选的,它应该比具有类加载器问题的commons-logging更好,特别是如果你在一个要求严格的容器,如OSGi平台。而且还有一个性能优势,因为绑定在编译时不是运行时。

使用较少步骤并生成较少依赖关系的SLF4J用户更为常见的选择是直接绑定到Logback。这将删除额外的绑定步骤,因为Logback直接实现SLF4J,因此您只需要依赖于两个库(jcl-over-slf4j和logback)而不是四个。如果这样做,您可能还需要从其他外部依赖关系(不是Spring)中排除slf4j-api依赖关系,因为您只需要在类路径中使用该API的一个版本。

使用的Log4j

  [[Note]]
Log4j的1.x版本已经寿终正寝,以下的内容特指Log4j 2

许多人使用Log4j 作为配置和管理日志的日志框架。它是高效和成熟的,当我们构建和测试Spring,实际上它是在运行时使用的。 Spring还提供了一些用于配置和初始化Log4j的实用功能,因此它在某些模块中对Log4j具有可选的编译时依赖性。

要使用JCL和Log4j,所有你需要做的就是把Log4j加到类路径,并为其提供一个配置文件(log4j2.xml,log4j2.properties或其他 支持的配置格式)。对于Maven用户,所需的最少依赖关系是:
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>

 
如果你也想使用SLF4J,还需要以下依赖关系:
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.7</version>
</dependency>
</dependencies>

 
下面是一个例子log4j2.xml用来把日志输出到控制台:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework.beans.factory" level="DEBUG"/>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

运行时容器和原生JCL

许多人在那些本身提供JCL实现的容器中运行他们的Spring应用程序。IBM WebSphere应用服务器(WAS)为例。这往往会导致问题,遗憾的是没有一劳永逸的解决方案; 在大多数情况下,简单地从您的应用程序排除commons-logging是不够的。

要清楚这一点:报告的问题通常不是JCL本身,甚至commons-logging:而是将commons-logging绑定到另一个框架(通常是Log4j)。这可能会失败,因为commons-logging更改了在一些容器中发现的旧版本(1.0)和大多数人现在使用的版本(1.1)之间执行运行时发现的方式。 Spring不使用JCL API的任何不寻常的部分,所以没有什么破坏,但是一旦Spring或您的应用程序尝试输出日志,您可以发现与Log4j的绑定不起作用

在这种情况下,使用WAS最简单的方法是反转类加载器层次结构(IBM将其称为”parent last”),以便应用程序控制JCL依赖关系,而不是容器。该选项并不总是开放的,但是在公共领域还有许多其他建议可供选择,您的里程(集成程度)可能因容器的确切版本和功能集而异。
 
 本文作者: 胡永
原文链接: http://ifeve.com/overview-gett ... ring/
版权归作者所有,转载请注明出处