Spring Boot

Spring Boot

python、ruby等如何接入spring boot开发的授权服务器

Spring Bootxiaobaxi 回复了问题 • 2 人关注 • 1 个回复 • 88 次浏览 • 2 天前 • 来自相关话题

自制Mybatis插件--doge-mapper,欢迎使用和吐槽

开源项目throwable 发表了文章 • 4 个评论 • 122 次浏览 • 5 天前 • 来自相关话题

项目简介
   doge-mapper致力于打造一个轻量级、可扩展、灵活的Mybatis插件,配套一个实体、DAO代码生成器doge-mapper-generator(基于JavaFx编写)。目前此插件只支持Mysql和单表,需要Jdk1.8。
 
项目地址
Github:https://github.com/zjcscut/doge-mapper
 
项目文档
doge-mapper文档:https://github.com/zjcscut/doge-mapper/blob/master/readme/readme_zh_cn.md doge-mapper-generator文档:https://github.com/zjcscut/dog ... cn.md

项目使用快照:
    实体:




    Mapper:
 




 
    使用:




 
写在最后
   欢迎吐槽和Star,如果使用过程出现任何问题,直接发送邮件给我,第一时间回答和解决,邮箱见https://github.com/zjcscut/doge-mapper/的Contact部分。

 
  查看全部
项目简介
   doge-mapper致力于打造一个轻量级、可扩展、灵活的Mybatis插件,配套一个实体、DAO代码生成器doge-mapper-generator(基于JavaFx编写)。目前此插件只支持Mysql和单表,需要Jdk1.8。
 
项目地址

 
项目文档


项目使用快照:
    实体:
entity.png

    Mapper:
 
mapper.png

 
    使用:
usage.png

 
写在最后
   欢迎吐槽和Star,如果使用过程出现任何问题,直接发送邮件给我,第一时间回答和解决,邮箱见https://github.com/zjcscut/doge-mapper/的Contact部分。

 
 

每个微服务之间都是通过共享数据库,共享缓存进行交互的,并没采用彼此之间的接口调用。

Micro Service Arch.采蘑菇的大叔 回复了问题 • 8 人关注 • 6 个回复 • 902 次浏览 • 2017-09-12 13:49 • 来自相关话题

maven根据环境打包,属性值没有注入到xml文件中,求解,谢谢啦

Spring Boot那时年少轻狂 回复了问题 • 4 人关注 • 4 个回复 • 423 次浏览 • 2017-09-11 14:57 • 来自相关话题

SpringBoot最佳测试实践

Spring Bootstrongant 回复了问题 • 2 人关注 • 1 个回复 • 280 次浏览 • 2017-09-01 09:06 • 来自相关话题

Thymeleaf项目后台布局的问题

Spring Boothansonwang99 回复了问题 • 3 人关注 • 3 个回复 • 367 次浏览 • 2017-08-22 14:19 • 来自相关话题

springboot事务控制

Spring BootDrTrang 回复了问题 • 5 人关注 • 3 个回复 • 420 次浏览 • 2017-08-15 13:20 • 来自相关话题

新人:SpringBoot启动 访问url路径问题

Spring Bootxerxessultan 回复了问题 • 6 人关注 • 5 个回复 • 387 次浏览 • 2017-08-14 18:18 • 来自相关话题

为redis添加slave的时候,健康检查出问题

Spring Bootxiaobaxi 回复了问题 • 2 人关注 • 1 个回复 • 257 次浏览 • 2017-08-14 14:07 • 来自相关话题

springCloud 与阿里的 gts 全局分布式事务 服务 怎么集成

Spring Cloud程序猿DD 回复了问题 • 3 人关注 • 2 个回复 • 429 次浏览 • 2017-08-14 10:42 • 来自相关话题

条新动态, 点击查看
泥瓦匠BYSocket

泥瓦匠BYSocket 回答了问题 • 2017-05-31 18:44 • 14 个回复 不感兴趣

springboot前后端分离

赞同来自:

spring boot 可以发 HTTP Over JSON 服务。可以看  《 Springboot 实现 Restful 服务,基于 HTTP / JSON 传输 》

前后端分离,轻量级交互就是走 HTTP。
前后端需要通过接口来协作 
JSON 格式/... 显示全部 »
spring boot 可以发 HTTP Over JSON 服务。可以看  《 Springboot 实现 Restful 服务,基于 HTTP / JSON 传输 》

前后端分离,轻量级交互就是走 HTTP。
前后端需要通过接口来协作 
JSON 格式/ XML 的接口
后端负责数据的提供和处理地服务,而完全不处理展现
前端负责数据,组织数据并展现

直接通过 apidoc、swagger 这种 API 文档交互工作

前后端分离 https://my.oschina.net/huangyong/blog/521891

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
解压 ZooKeeper$(document).ready(function() {$('pre code').each(function(i, block) { hljs.highlightBlock( block); }); });tar 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
版权归作者所有,转载请注明出处

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中查看,大家有兴趣的可以自行查看源码。
 

SpringBoot 整合 Mybatis 的完整 Web 案例

Spring Boot泥瓦匠BYSocket 发表了文章 • 17 个评论 • 1560 次浏览 • 2017-05-28 17:28 • 来自相关话题

推荐一本书《腾讯传》。
 
新年第一篇 Springboot 技术文诞生。泥瓦匠准备写写 Springboot 相关最佳实践。一方面总结下一些 Springboot 相关,一方面和大家交流交流 Springboot 框架。
现在业界互联网流行的数据操作层框架 Mybatis,下面详解下 Springboot 如何整合 Mybatis 。
这边没有使用 Mybatis Annotation 这种,是使用 xml 配置 SQL。因为我觉得 SQL 和业务代码应该隔离,方便和 DBA 校对 SQL。二者 XML 对较长的 SQL 比较清晰。
 
一、运行 springboot-mybatis 工程
git clone 下载工程 springboot-learning-example ,项目地址见 springboot-learning-example。
 
下面开始运行工程步骤(Quick Start):

1.数据库准备
a.创建数据库 springbootdb:CREATE DATABASE springbootdb;


b.创建表 city :(因为我喜欢徒步)
DROP TABLE IF EXISTS `city`;
CREATE TABLE `city` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '城市编号',
`province_id` int(10) unsigned NOT NULL COMMENT '省份编号',
`city_name` varchar(25) DEFAULT NULL COMMENT '城市名称',
`description` varchar(25) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
 
c.插入数据
INSERT city VALUES (1 ,1,'温岭市','BYSocket 的家在温岭。');

2. 项目结构介绍
项目结构如下:
org.spring.springboot.controller – Controller 层
org.spring.springboot.dao – 数据操作层 DAO
org.spring.springboot.domain – 实体类
org.spring.springboot.service – 业务逻辑层
Application – 应用启动类
application.properties – 应用配置文件,应用启动会自动读取配置
 
3.改数据库配置
打开 application.properties 文件, 修改相应的数据源配置,比如数据源地址、账号、密码等。(如果不是用 MySQL,自行添加连接驱动 pom,然后修改驱动名配置。)

4.编译工程
在项目根目录 springboot-learning-example,运行 maven 指令:mvn clean install
 
5.运行工程
 
右键运行 Application 应用启动类的 main 函数,然后在浏览器访问:
http://localhost:8080/api/city?cityName=温岭市 
 
可以看到返回的 JSON 结果:{
"id": 1,
"provinceId": 1,
"cityName": "温岭市",
"description": "我的家在温岭。"
}
 
 
二、springboot-mybatis 工程配置详解
 
1.pom 添加 Mybatis 依赖
<!-- Spring Boot Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot}</version>
</dependency>

整个工程的 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-mybatis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-mybatis :: 整合 Mybatis Demo</name>

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

<properties>
<mybatis-spring-boot>1.2.0</mybatis-spring-boot>
<mysql-connector>5.1.39</mysql-connector>
</properties>

<dependencies>

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

<!-- Spring Boot Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot}</version>
</dependency>

<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector}</version>
</dependency>

<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
 
 
2.在 application.properties 应用配置文件,增加 Mybatis 相关配置## Mybatis 配置
mybatis.typeAliasesPackage=org.spring.springboot.domain
mybatis.mapperLocations=classpath:mapper/*.xml
 
mybatis.typeAliasesPackage 配置为 org.spring.springboot.domain,指向实体类包路径。
mybatis.mapperLocations 配置为 classpath 路径下 mapper 包下,* 代表会扫描所有 xml 文件。
 
mybatis 其他配置相关详解如下:
mybatis.config = mybatis 配置文件名称
mybatis.mapperLocations = mapper xml 文件地址
mybatis.typeAliasesPackage = 实体类包路径
mybatis.typeHandlersPackage = type handlers 处理器包路径
mybatis.check-config-location = 检查 mybatis 配置是否存在,一般命名为 mybatis-config.xml
mybatis.executorType = 执行模式。默认是 SIMPLE3.在 Application 应用启动类添加注解 MapperScan

Application.java 代码如下:/**
* Spring Boot 应用启动类
*
* Created by bysocket on 16/4/26.
*/
// Spring Boot 应用的标识
@SpringBootApplication
// mapper 接口类扫描包配置
@MapperScan("org.spring.springboot.dao")
public class Application {

public static void main(String[] args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(Application.class,args);
}
}
 
mapper 接口类扫描包配置注解 MapperScan :用这个注解可以注册 Mybatis mapper 接口类。

4.添加相应的 City domain类、CityDao mapper接口类
 
City.java:/**
* 城市实体类
*
* Created by bysocket on 07/02/2017.
*/
public class City {

/**
* 城市编号
*/
private Long id;

/**
* 省份编号
*/
private Long provinceId;

/**
* 城市名称
*/
private String cityName;

/**
* 描述
*/
private String description;

public Long getId() {
return id;
}

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

public Long getProvinceId() {
return provinceId;
}

public void setProvinceId(Long provinceId) {
this.provinceId = provinceId;
}

public String getCityName() {
return cityName;
}

public void setCityName(String cityName) {
this.cityName = cityName;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}
}
 
CityDao.java:/**
* 城市 DAO 接口类
*
* Created by bysocket on 07/02/2017.
*/
public interface CityDao {

/**
* 根据城市名称,查询城市信息
*
* @param cityName 城市名
*/
City findByName(@Param("cityName") String cityName);
}
 
其他不明白的,可以 git clone 下载工程 springboot-learning-example ,工程代码注解很详细。 https://github.com/JeffLi1993/springboot-learning-example。 
 
三、其他
 
利用 Mybatis-generator自动生成代码 http://www.cnblogs.com/yjmyzz/p/4210554.html
Mybatis 通用 Mapper3 https://github.com/abel533/Mapper
Mybatis 分页插件 PageHelper https://github.com/pagehelper/Mybatis-PageHelper 

本文作者: 泥瓦匠
原文链接: http://www.bysocket.com/?p=1610 
版权归作者所有,转载请注明出处 查看全部
推荐一本书《腾讯传》。
 
新年第一篇 Springboot 技术文诞生。泥瓦匠准备写写 Springboot 相关最佳实践。一方面总结下一些 Springboot 相关,一方面和大家交流交流 Springboot 框架。
现在业界互联网流行的数据操作层框架 Mybatis,下面详解下 Springboot 如何整合 Mybatis 。
这边没有使用 Mybatis Annotation 这种,是使用 xml 配置 SQL。因为我觉得 SQL 和业务代码应该隔离,方便和 DBA 校对 SQL。二者 XML 对较长的 SQL 比较清晰。
 
一、运行 springboot-mybatis 工程
git clone 下载工程 springboot-learning-example ,项目地址见 springboot-learning-example
 
下面开始运行工程步骤(Quick Start):

1.数据库准备
a.创建数据库 springbootdb:
CREATE DATABASE springbootdb;


b.创建表 city :(因为我喜欢徒步)
DROP TABLE IF EXISTS  `city`;
CREATE TABLE `city` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '城市编号',
`province_id` int(10) unsigned NOT NULL COMMENT '省份编号',
`city_name` varchar(25) DEFAULT NULL COMMENT '城市名称',
`description` varchar(25) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 
c.插入数据
INSERT city VALUES (1 ,1,'温岭市','BYSocket 的家在温岭。');

2. 项目结构介绍
项目结构如下:
org.spring.springboot.controller – Controller 层
org.spring.springboot.dao – 数据操作层 DAO
org.spring.springboot.domain – 实体类
org.spring.springboot.service – 业务逻辑层
Application – 应用启动类
application.properties – 应用配置文件,应用启动会自动读取配置

 
3.改数据库配置
打开 application.properties 文件, 修改相应的数据源配置,比如数据源地址、账号、密码等。(如果不是用 MySQL,自行添加连接驱动 pom,然后修改驱动名配置。)

4.编译工程
在项目根目录 springboot-learning-example,运行 maven 指令:
mvn clean install

 
5.运行工程
 
右键运行 Application 应用启动类的 main 函数,然后在浏览器访问:
http://localhost:8080/api/city?cityName=温岭市 
 
可以看到返回的 JSON 结果:
{
"id": 1,
"provinceId": 1,
"cityName": "温岭市",
"description": "我的家在温岭。"
}

 
 
二、springboot-mybatis 工程配置详解
 
1.pom 添加 Mybatis 依赖

<!-- Spring Boot Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot}</version>
</dependency>


整个工程的 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-mybatis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-mybatis :: 整合 Mybatis Demo</name>

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

<properties>
<mybatis-spring-boot>1.2.0</mybatis-spring-boot>
<mysql-connector>5.1.39</mysql-connector>
</properties>

<dependencies>

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

<!-- Spring Boot Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot}</version>
</dependency>

<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector}</version>
</dependency>

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

 
 
2.在 application.properties 应用配置文件,增加 Mybatis 相关配置
## Mybatis 配置
mybatis.typeAliasesPackage=org.spring.springboot.domain
mybatis.mapperLocations=classpath:mapper/*.xml

 
mybatis.typeAliasesPackage 配置为 org.spring.springboot.domain,指向实体类包路径。
mybatis.mapperLocations 配置为 classpath 路径下 mapper 包下,* 代表会扫描所有 xml 文件。
 
mybatis 其他配置相关详解如下:
mybatis.config = mybatis 配置文件名称
mybatis.mapperLocations = mapper xml 文件地址
mybatis.typeAliasesPackage = 实体类包路径
mybatis.typeHandlersPackage = type handlers 处理器包路径
mybatis.check-config-location = 检查 mybatis 配置是否存在,一般命名为 mybatis-config.xml
mybatis.executorType = 执行模式。默认是 SIMPLE3.在 Application 应用启动类添加注解 MapperScan


Application.java 代码如下:
/**
* Spring Boot 应用启动类
*
* Created by bysocket on 16/4/26.
*/
// Spring Boot 应用的标识
@SpringBootApplication
// mapper 接口类扫描包配置
@MapperScan("org.spring.springboot.dao")
public class Application {

public static void main(String[] args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(Application.class,args);
}
}

 
mapper 接口类扫描包配置注解 MapperScan :用这个注解可以注册 Mybatis mapper 接口类。

4.添加相应的 City domain类、CityDao mapper接口类
 
City.java:
/**
* 城市实体类
*
* Created by bysocket on 07/02/2017.
*/
public class City {

/**
* 城市编号
*/
private Long id;

/**
* 省份编号
*/
private Long provinceId;

/**
* 城市名称
*/
private String cityName;

/**
* 描述
*/
private String description;

public Long getId() {
return id;
}

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

public Long getProvinceId() {
return provinceId;
}

public void setProvinceId(Long provinceId) {
this.provinceId = provinceId;
}

public String getCityName() {
return cityName;
}

public void setCityName(String cityName) {
this.cityName = cityName;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}
}

 
CityDao.java:
/**
* 城市 DAO 接口类
*
* Created by bysocket on 07/02/2017.
*/
public interface CityDao {

/**
* 根据城市名称,查询城市信息
*
* @param cityName 城市名
*/
City findByName(@Param("cityName") String cityName);
}

 
其他不明白的,可以 git clone 下载工程 springboot-learning-example ,工程代码注解很详细。 https://github.com/JeffLi1993/springboot-learning-example。 
 
三、其他
 
利用 Mybatis-generator自动生成代码 http://www.cnblogs.com/yjmyzz/p/4210554.html
Mybatis 通用 Mapper3 https://github.com/abel533/Mapper
Mybatis 分页插件 PageHelper https://github.com/pagehelper/Mybatis-PageHelper 

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

python、ruby等如何接入spring boot开发的授权服务器

回复

Spring Bootxiaobaxi 回复了问题 • 2 人关注 • 1 个回复 • 88 次浏览 • 2 天前 • 来自相关话题

每个微服务之间都是通过共享数据库,共享缓存进行交互的,并没采用彼此之间的接口调用。

回复

Micro Service Arch.采蘑菇的大叔 回复了问题 • 8 人关注 • 6 个回复 • 902 次浏览 • 2017-09-12 13:49 • 来自相关话题

maven根据环境打包,属性值没有注入到xml文件中,求解,谢谢啦

回复

Spring Boot那时年少轻狂 回复了问题 • 4 人关注 • 4 个回复 • 423 次浏览 • 2017-09-11 14:57 • 来自相关话题

SpringBoot最佳测试实践

回复

Spring Bootstrongant 回复了问题 • 2 人关注 • 1 个回复 • 280 次浏览 • 2017-09-01 09:06 • 来自相关话题

Thymeleaf项目后台布局的问题

回复

Spring Boothansonwang99 回复了问题 • 3 人关注 • 3 个回复 • 367 次浏览 • 2017-08-22 14:19 • 来自相关话题

springboot事务控制

回复

Spring BootDrTrang 回复了问题 • 5 人关注 • 3 个回复 • 420 次浏览 • 2017-08-15 13:20 • 来自相关话题

新人:SpringBoot启动 访问url路径问题

回复

Spring Bootxerxessultan 回复了问题 • 6 人关注 • 5 个回复 • 387 次浏览 • 2017-08-14 18:18 • 来自相关话题

为redis添加slave的时候,健康检查出问题

回复

Spring Bootxiaobaxi 回复了问题 • 2 人关注 • 1 个回复 • 257 次浏览 • 2017-08-14 14:07 • 来自相关话题

springCloud 与阿里的 gts 全局分布式事务 服务 怎么集成

回复

Spring Cloud程序猿DD 回复了问题 • 3 人关注 • 2 个回复 • 429 次浏览 • 2017-08-14 10:42 • 来自相关话题

spring boot业务处理抛异常问题

回复

Spring BootDrTrang 回复了问题 • 7 人关注 • 6 个回复 • 659 次浏览 • 2017-08-07 10:52 • 来自相关话题

自制Mybatis插件--doge-mapper,欢迎使用和吐槽

开源项目throwable 发表了文章 • 4 个评论 • 122 次浏览 • 5 天前 • 来自相关话题

项目简介
   doge-mapper致力于打造一个轻量级、可扩展、灵活的Mybatis插件,配套一个实体、DAO代码生成器doge-mapper-generator(基于JavaFx编写)。目前此插件只支持Mysql和单表,需要Jdk1.8。
 
项目地址
Github:https://github.com/zjcscut/doge-mapper
 
项目文档
doge-mapper文档:https://github.com/zjcscut/doge-mapper/blob/master/readme/readme_zh_cn.md doge-mapper-generator文档:https://github.com/zjcscut/dog ... cn.md

项目使用快照:
    实体:




    Mapper:
 




 
    使用:




 
写在最后
   欢迎吐槽和Star,如果使用过程出现任何问题,直接发送邮件给我,第一时间回答和解决,邮箱见https://github.com/zjcscut/doge-mapper/的Contact部分。

 
  查看全部
项目简介
   doge-mapper致力于打造一个轻量级、可扩展、灵活的Mybatis插件,配套一个实体、DAO代码生成器doge-mapper-generator(基于JavaFx编写)。目前此插件只支持Mysql和单表,需要Jdk1.8。
 
项目地址

 
项目文档


项目使用快照:
    实体:
entity.png

    Mapper:
 
mapper.png

 
    使用:
usage.png

 
写在最后
   欢迎吐槽和Star,如果使用过程出现任何问题,直接发送邮件给我,第一时间回答和解决,邮箱见https://github.com/zjcscut/doge-mapper/的Contact部分。

 
 

Spring Boot 的 Maven 项目原型

Spring BootDrTrang 发表了文章 • 8 个评论 • 594 次浏览 • 2017-08-11 16:25 • 来自相关话题

Note:本文同步发表在个人博客,点击前往 Trang's Blog
 
概要
随着微服务的流行,Spring Boot 在广大开发者中占据了越来越重要的位置,其开箱即用、自动配置的特性也给我们带来了诸多便利。

然而 Spring Boot 只是一个基础框架,我们还是需要新建工程、技术选型、参数配置等一系列步骤才能搭建出一个完整的项目,繁冗又无趣,尤其对于初学者来说,这一过程还可能会出现各种各样莫名其妙的问题。

Spring Boot Archetype 则是为了解决上述痛点而打造,借助 `maven-archetype-plugin` 插件,预置了日志、缓存、AOP、数据访问、代码生成、文档生成等模块,并提供常见技术的最佳实践,用户只需几秒即可快速构建一个可运行的 Spring Boot 项目。

地址
主页:https://github.com/drtrang/mav ... gboot
问题:https://github.com/drtrang/mav ... ssues
后续计划:https://github.com/drtrang/mav ... DO.md


特点
基于 Spring Boot 1.5.6,内嵌 Jetty增加全局异常捕获、view to json 等功能集成通用 Mapper 和 PageHelper,提供 BaseService,常用 CRUD 无需编写代码集成 MyBatis Generator,提供 MBG Plugin Extension,如自动生成 Service 插件、支持 Lombok 插件等等集成 Druid Spring Boot Starter,无需显式声明数据源(支持多数据源)集成 Swagger2,HTTP 接口自动生成接口文档提供常用工具,如 SpringContextHolder、SqlMapper


ISSUE
项目刚刚发布,许多方面还有不足,希望大家多提意见,如果有任何想法和讨论,都可以放到 ISSUE 平台,我会及时回复。有条件的用户还可以提 PR,成为该项目的 Contributor。


About Me
QQ:349096849
Email:donghao.l@hotmail.com
Blog:Trang's Blog 查看全部
Note:本文同步发表在个人博客,点击前往 Trang's Blog
 
概要
随着微服务的流行,Spring Boot 在广大开发者中占据了越来越重要的位置,其开箱即用、自动配置的特性也给我们带来了诸多便利。

然而 Spring Boot 只是一个基础框架,我们还是需要新建工程、技术选型、参数配置等一系列步骤才能搭建出一个完整的项目,繁冗又无趣,尤其对于初学者来说,这一过程还可能会出现各种各样莫名其妙的问题。

Spring Boot Archetype 则是为了解决上述痛点而打造,借助 `maven-archetype-plugin` 插件,预置了日志、缓存、AOP、数据访问、代码生成、文档生成等模块,并提供常见技术的最佳实践,用户只需几秒即可快速构建一个可运行的 Spring Boot 项目。

地址
主页:https://github.com/drtrang/mav ... gboot
问题:https://github.com/drtrang/mav ... ssues
后续计划:https://github.com/drtrang/mav ... DO.md


特点
  • 基于 Spring Boot 1.5.6,内嵌 Jetty
  • 增加全局异常捕获、view to json 等功能
  • 集成通用 Mapper 和 PageHelper,提供 BaseService,常用 CRUD 无需编写代码
  • 集成 MyBatis Generator,提供 MBG Plugin Extension,如自动生成 Service 插件、支持 Lombok 插件等等
  • 集成 Druid Spring Boot Starter,无需显式声明数据源(支持多数据源)
  • 集成 Swagger2,HTTP 接口自动生成接口文档
  • 提供常用工具,如 SpringContextHolder、SqlMapper



ISSUE
项目刚刚发布,许多方面还有不足,希望大家多提意见,如果有任何想法和讨论,都可以放到 ISSUE 平台,我会及时回复。有条件的用户还可以提 PR,成为该项目的 Contributor。


About Me
QQ:349096849
Email:donghao.l@hotmail.com
Blog:Trang's Blog

简化Swagger使用的自制Starter:spring-boot-starter-swagger,欢迎使用和吐槽

开源项目程序猿DD 发表了文章 • 3 个评论 • 1081 次浏览 • 2017-08-10 07:49 • 来自相关话题

项目简介

该项目主要利用Spring Boot的自动化配置特性来实现快速的将swagger2引入spring boot应用来生成API文档,简化原生使用swagger2的整合代码。
 
GitHub:https://github.com/dyc87112/spring-boot-starter-swagger码云:http://git.oschina.net/didispace/spring-boot-starter-swagger博客:http://blog.didispace.com

小工具一枚,欢迎使用和Star支持,如使用过程中碰到问题,可以提出Issue,我会尽力完善该Starter

版本基础
 
Spring Boot:1.5.xSwagger:2.7.x

如何使用

在该项目的帮助下,我们的Spring Boot可以轻松的引入swagger2,主需要做下面两个步骤:

在pom.xml中引入依赖:<dependency>
<groupId>com.didispace</groupId>
<artifactId>spring-boot-starter-swagger</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
在应用主类中增加@EnableSwagger2Doc注解@EnableSwagger2Doc
@SpringBootApplication
public class Bootstrap {
public static void main(String[] args) {
SpringApplication.run(Bootstrap.class, args);
}
}
默认情况下就能产生所有当前Spring MVC加载的请求映射文档。

参数配置

更细致的配置内容参考如下:

配置示例swagger.title=spring-boot-starter-swagger
swagger.description=Starter for swagger 2.x
swagger.version=1.1.0.RELEASE
swagger.license=Apache License, Version 2.0
swagger.licenseUrl=https://www.apache.org/licenses/LICENSE-2.0.html
swagger.termsOfServiceUrl=https://github.com/dyc87112/sp ... agger
swagger.contact.name=程序猿DD
swagger.contact.url=http://blog.didispace.com
swagger.contact.email=dyc87112@qq.com
swagger.base-package=com.didispace
swagger.base-path=/**
swagger.exclude-path=/error, /ops/**
配置说明

swagger.title=标题
swagger.description=描述
swagger.version=版本
swagger.license=许可证
swagger.licenseUrl=许可证URL
swagger.termsOfServiceUrl=服务条款URL
swagger.contact.name=维护人
swagger.contact.url=维护人URL
swagger.contact.email=维护人email
swagger.base-package=swagger扫描的基础包,默认:全扫描
swagger.base-path=需要处理的基础URL规则,默认:/**
swagger.exclude-path=需要排除的URL规则,默认:空


Path规则说明

swagger.base-path和swagger.exclude-path使用ANT规则配置。

我们可以使用swagger.base-path来指定所有需要生成文档的请求路径基础规则,然后再利用swagger.exclude-path来剔除部分我们不需要的。

比如,通常我们可以这样设置:management.context-path=/ops
swagger.base-path=/**
swagger.exclude-path=/ops/**, /error
上面的设置将解析所有除了/ops/开始以及spring boot自带/error请求路径。

其中,exclude-path可以配合management.context-path=/ops设置的spring boot actuator的context-path来排除所有监控端点。 查看全部

项目简介

该项目主要利用Spring Boot的自动化配置特性来实现快速的将swagger2引入spring boot应用来生成API文档,简化原生使用swagger2的整合代码。
 


小工具一枚,欢迎使用和Star支持,如使用过程中碰到问题,可以提出Issue,我会尽力完善该Starter

版本基础
 
  • Spring Boot:1.5.x
  • Swagger:2.7.x


如何使用

在该项目的帮助下,我们的Spring Boot可以轻松的引入swagger2,主需要做下面两个步骤:

在pom.xml中引入依赖:
<dependency>
<groupId>com.didispace</groupId>
<artifactId>spring-boot-starter-swagger</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>

在应用主类中增加@EnableSwagger2Doc注解
@EnableSwagger2Doc
@SpringBootApplication
public class Bootstrap {
public static void main(String[] args) {
SpringApplication.run(Bootstrap.class, args);
}
}

默认情况下就能产生所有当前Spring MVC加载的请求映射文档。

参数配置

更细致的配置内容参考如下:

配置示例
swagger.title=spring-boot-starter-swagger
swagger.description=Starter for swagger 2.x
swagger.version=1.1.0.RELEASE
swagger.license=Apache License, Version 2.0
swagger.licenseUrl=https://www.apache.org/licenses/LICENSE-2.0.html
swagger.termsOfServiceUrl=https://github.com/dyc87112/sp ... agger
swagger.contact.name=程序猿DD
swagger.contact.url=http://blog.didispace.com
swagger.contact.email=dyc87112@qq.com
swagger.base-package=com.didispace
swagger.base-path=/**
swagger.exclude-path=/error, /ops/**

配置说明


swagger.title=标题
swagger.description=描述
swagger.version=版本
swagger.license=许可证
swagger.licenseUrl=许可证URL
swagger.termsOfServiceUrl=服务条款URL
swagger.contact.name=维护人
swagger.contact.url=维护人URL
swagger.contact.email=维护人email
swagger.base-package=swagger扫描的基础包,默认:全扫描
swagger.base-path=需要处理的基础URL规则,默认:/**
swagger.exclude-path=需要排除的URL规则,默认:空



Path规则说明

swagger.base-path和swagger.exclude-path使用ANT规则配置。

我们可以使用swagger.base-path来指定所有需要生成文档的请求路径基础规则,然后再利用swagger.exclude-path来剔除部分我们不需要的。

比如,通常我们可以这样设置:
management.context-path=/ops
swagger.base-path=/**
swagger.exclude-path=/ops/**, /error

上面的设置将解析所有除了/ops/开始以及spring boot自带/error请求路径。

其中,exclude-path可以配合management.context-path=/ops设置的spring boot actuator的context-path来排除所有监控端点。

Spring Boot Web 开发注解篇

Spring Boot泥瓦匠BYSocket 发表了文章 • 0 个评论 • 405 次浏览 • 2017-08-02 13:59 • 来自相关话题

摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!
“人才是公司运作的关键所在”
本文提纲
1. spring-boot-starter-web 依赖概述
1.1 spring-boot-starter-web 职责
1.2 spring-boot-starter-web 依赖关系
2. Spring MVC on Spring Boot
2.1 Spring MVC 体系温故知新
2.2 重要的类
2.3 Spring Boot MVC
2.3.1 控制器
2.3.2 数据绑定
2.3.3 视图和视图解析 

一、spring-boot-starter-web 依赖概述
在 Spring Boot 快速入门中,只要在 pom.xml 加入了 spring-boot-starter-web 依赖,即可快速开发 web 应用。可见,Spring Boot 极大地简化了 Spring 应用从搭建到开发的过程,做到了「开箱即用」的方式。Spring Boot 已经提供很多「开箱即用」的依赖,如上面开发 web 应用使用的 spring-boot-starter-web ,都是以 spring-boot-starter-xx 进行命名的。
 
Spring Boot 「开箱即用」 的设计,对开发者非常便利。简单来说,只要往 Spring Boot 项目加入相应的 spring-boot-starter-xx 依赖,就可以使用对应依赖的功能,比如加入 spring-boot-starter-data-jpa 依赖,就可以使用数据持久层框架 Spring Data JPA 操作数据源。相比 Spring 以前需要大量的XML配置以及复杂的依赖管理,极大的减少了开发工作量和学习成本。

当开发一个特定类型的应用程序时,特定的 Starter 提供所需的依赖关系,并且将对应的 Bean 注册到 Spring 容器中。spring-boot-starter-web 依赖就是提供开发 Web 应用的。

1.1 spring-boot-starter-web 职责
spring-boot-starter-web 是一个用于构建 Web 的 Starter ,包括构建 RESTful 服务应用、Spring MVC 应用等。并且不需要额外配置容器,默认使用 Tomcat 作为嵌入式容器。

1.2 spring-boot-starter-web 依赖关系
spring-boot-starter-web 这么强大,它的组成如下表:
spring-boot-starter  核心包,包括了自动化配置支持、日志、YAML 文件解析的支持等。
spring-boot-starter-json 读写 JSON 包
spring-boot-starter-tomcat Tomcat 嵌入式 Servlet 容器包
hibernate-validator Hibernate 框架提供的验证包
spring-web Spring 框架的 Web 包
spring-webmvc Spring 框架的 Web MVC 包







spring-boot-starter-web 包含了 Tomcat 和 Spring MVC ,那启动流程是这样的。 标识 @SpringBootApplication 的应用,初始化经过 spring-boot-starter  核心包中的自动化配置,构建了 Spring 容器,并通过 Tomcat 启动 Web 应用。很多 Starters 只支持 Spring MVC,一般会将 spring-boot-starter-web 依赖加入到应用的 Classpath。

另外,spring-boot-starter-web 默认使用 Tomcat 作为嵌入式 Servlet 容器,在 pom.xml 配置 spring-boot-starter-jetty 和 spring-boot-starter-undertow 就可以替换默认容器。


二、Spring MVC on Spring Boot
Spring MVC 是 Spring Web 重要的模块。内容包括 MVC 模式的实现和 RESTful 服务的支持。

2.1 Spring MVC 体系温故知新

spring-webmvc 模块里面包:
- org.springframework.web.servlet 提供与应用程序上下文基础结构集成的 Servlet,以及 Spring web MVC 框架的核心接口和类。
- org.springframework.web.servlet.mvc Spring 附带的 Servlet MVC 框架的标准控制器实现。
- org.springframework.web.servlet.mvc.annotation 用于基于注解的 Servlet MVC 控制器的支持包。
- org.springframework.web.servlet.mvc.condition 用于根据条件匹配传入请求的公共 MVC 逻辑。
- org.springframework.web.servlet.mvc.method 用于处理程序方法处理的基于 Servlet 的基础结构,基于在 org.springframework.web.method 包上。
- org.springframework.web.servlet.view 提供标准的 View 和 ViewResolver 实现,包括自定义实现的抽象基类。
- org.springframework.web.servlet.view.freemarker 支持将 FreeMarker 集成为 Spring Web 视图技术的类。
- org.springframework.web.servlet.view.json 支持提供基于 JSON 序列化的 View 实现的类。

上面列出来核心的包。org.springframework.web.servlet.view 包中, View 视图实现有常见的:JSON 、FreeMarker 等。org.springframework.web.servlet.mvc 包中,Controller 控制层实现包括了注解、程序方法处理等封装。自然,看源码先从 org.springframework.web.servlet 包看其核心的接口和类。

2.2 重要的类
DispatcherServlet 类:调度 HTTP 请求控制器(或者处理器 Handler)。  
View 视图层 ModelAndView 类:模型和视图的持有者。
View 接口:MVC WEB 交互。该接口的实现负责呈现视图或者暴露模型。  
Controller 控制层 HandlerMapping 接口: 请求从 DispacherServlet 过来,该接口定义请求和处理程序对象之间的映射。
HandlerInterceptor 接口:处理程序的执行链接口。

Spring MVC 框架模型






2.3 Spring Boot MVC
以前 Spring MVC 开发模式是这样的:
1. 在 web.xml 配置 DispatcherServlet,用于截获并处理所有请求
2. 在 Spring MVC 配置文件中,声明预定义的控制器和视图解析器等
3. 编写预定义的处理请求控制器
4. 编写预定义的视图对象,比如 JSP、Freemarker 等

在 Spring Boot MVC 中,Web 自动化配置会帮你减少上面的两个步骤。默认使用的视图是 ThymeLeaf,在下面小节会具体讲
1. 编写预定义的处理请求控制器
2. 编写默认 ThymeLeaf 视图对象

例如下面会展示用户列表案例:
第一步:处理用户请求控制器
UserController.java/**
* 用户控制层
*
* Created by bysocket on 24/07/2017.
*/
@Controller
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /users
public class UserController {
@Autowired
UserService userService; // 用户服务层
/**
* 获取用户列表
* 处理 "/users" 的GET请求,用来获取用户列表
* 通过 @RequestParam 传递参数,进一步实现条件查询或者分页查询
*/
@RequestMapping(method = RequestMethod.GET)
public String getUserList(ModelMap map) {
map.addAttribute("userList", userService.findAll());
return "userList";
}
}

 
@Controller 注解在 UserController 类上,标识其为一个可接收 HTTP 请求的控制器
@RequestMapping(value = "/users") 注解 ,标识 UserController 类下所有接收的请求路由都是 /users 开头的。注意:类上的 @RequestMapping 注解是不必需的

@RequestMapping(method = RequestMethod.GET) 注解,标识该 getUserList(ModelMap map) 方法会接收并处理 /users 请求,且请求方法是 GET
getUserList(ModelMap map) 方法返回的字符串 userList ,代表着是视图,会有视图解析器解析成为一个具体的视图对象,然后经过视图渲染展示到浏览器

第二步:用户列表 ThymeLeaf 视图对象<!DOCTYPE html>
<html lang="zh-CN">
<head>
<script type="text/javascript" th:src="@{https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js}"></script>
<link th:href="@{https://cdn.bootcss.com/bootst ... n.css}" rel="stylesheet"/>
<link th:href="@{/css/default.css}" rel="stylesheet"/>
<link rel="icon" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
<meta charset="UTF-8"/>
<title>用户列表</title>
</head>
<body>
<div class="contentDiv">
<h5> 《 Spring Boot 2.x 核心技术实战》第二章快速入门案例</h5>
<table class="table table-hover table-condensed">
<legend>
<strong>用户列表</strong>
</legend>
<thead>
<tr>
<th>用户编号</th>
<th>名称</th>
<th>年龄</th>
<th>出生时间</th>
<th>管理</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${userList}">
<th scope="row" th:text="${user.id}"></th>
<td><a th:href="@{/users/update/{userId}(userId=${user.id})}" th:text="${user.name}"></a></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthday}"></td>
<td><a class="btn btn-danger" th:href="@{/users/delete/{userId}(userId=${user.id})}">删除</a></td>
</tr>
</tbody>
</table>
<div><a class="btn btn-primary" href="/users/create" role="button">创建用户</a></div>
</div>
</body>
</html>
一个 table 展示用户列表,引入了 jquery.min.js 和 bootstrap.min.css ,更好的展示页面效果。具体 ThymeLeaf 语法下面会讲到。

代码共享在:https://github.com/JeffLi1993/spring-boot-core-book-demo  

2.3.1 控制器
什么是控制器?控制器就是控制请求接收和负责响应到视图的角色。
@Controller 注解标识一个类作为控制器。DispatcherServlet 会扫描所有控制器类,并检测 @RequestMapping 注解配置的方法。Web 自动化配置已经处理完这一步骤。

@RequestMapping 注解标识请求 URL 信息,可以映射到整个类或某个特定的方法上。该注解可以表明请求需要的。
使用 value 指定特定的 URL ,比如 @RequestMapping(value = "/users”) 和 @RequestMapping(value = "/users/create”) 等
使用 method 指定 HTTP 请求方法,比如 RequestMethod.GET 等
还有使用其他特定的参数条件,可以设置 consumes 指定请求时的请求头需要包含的 Content-Type 值、设置 produces 可确保响应的内容类型

MVC on REST ful 场景
在 HTTP over JSON (自然 JSON、XML或其他自定义的媒体类型内容等均可)场景,配合上前后端分离的开发模式,我们经常会用 @ResponseBody 或 @RestController 两种方式实现 RESTful HTTP API 。

老方式:
@ResponseBody 注解标识该方法的返回值。这样被标注的方法返回值,会直接写入 HTTP 响应体(而不会被视图解析器认为是一个视图对象)。

新方式:
@RestController 注解,和 @Controller 用法一致,整合了 @Controller 和 @ResponseBody 功能。这样不需要每个 @RequestMapping 方法上都加上 @ResponseBody 注解,这样代码更简明。

使代码更简明,还有常用便捷注解 @GetMapping、@PostMapping 和 @PutMapping 等
HTTP 协议相关知识回顾,可以看看我以前的博文《图解 HTTP 协议》http://www.bysocket.com/?p=282 

2.3.2 数据绑定
数据绑定,简单的说就是 Spring MVC 从请求中获取请求入参,赋予给处理方法相应的入参。主要流程如下:
1. DataBinder 接受带有请求入参的 ServletRequest 对象
2. 调用 ConversionService 组件,进行数据类型转换、数据格式化等工作
3. 然后调用 Validator 组件,进行数据校验等工作
4. 绑定结果到 BindingResult 对象
5. 最后赋予给处理方法相应的入参

@ModelAttribute 注解添加一个或多个属性(类对象)到 model 上。例如@RequestMapping(value = "/create", method = RequestMethod.POST)
public String postUser(@ModelAttribute User user)
@PathVariable 注解通过变量名匹配到 URI 模板中相对应的变量。例如@RequestMapping(value = "/update/{id}", method = RequestMethod.GET)
public String getUser(@PathVariable("id") Long id, ModelMap map)   
@RequestParam 注解将请求参数绑定到方法参数。
@RequestHeader 注解将请求头属性绑定到方法参数。

2.3.3 视图和视图解析
视图的职责就是渲染模型数据,将模型里面的数据展示给用户。

请求到经过处理方法处理后,最终返回的是 ModeAndView 。可以从 Spring MVC 框架模型 看出,最终经过 ViewResolver 视频解析器得到视图对象 View。可能是我们常见的 JSP ,也可能是基于 ThymLeaf 、FreeMarker 或 Velocity 模板引擎视图,当然还有可能是 JSON 、XML 或者 PDF 等各种形式。
业界流行的模板引擎有如下的 Starters 支持:
spring-boot-starter-thymeleaf Thymeleaf 模板视图依赖,官方推荐
spring-boot-starter-freemarker Freemarker 模板视图依赖
spring-boot-starter-groovy-templates Groovy 模板视图依赖
spring-boot-starter-mustache Mustache 模板视图依赖

具体,spring-boot-starter-thymeleaf 使用案例在 GitHub :https://github.com/JeffLi1993/spring-boot-core-book-demo   的 chapter-2-spring-boot-quick-start 工程。

三、小结
本文主要介绍了 Spring Boot 在 Web 开发中涉及到的 HTTP 协议,还有一些 Spring MVC 相关的知识。

推荐:
开源项目 springboot-learning-example https://github.com/JeffLi1993/ ... ample 
开源项目 spring-boot-core-book-demo https://github.com/JeffLi1993/ ... -demo 

资料:
- 官方文档 http://docs.spring.io/spring-b ... ingle 
-《精通Spring 4.x 企业应用开发实战》

欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
— http://www.bysocket.com/ —
— https://github.com/JeffLi1993 — 





  查看全部
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!
“人才是公司运作的关键所在”
本文提纲
1. spring-boot-starter-web 依赖概述
1.1 spring-boot-starter-web 职责
1.2 spring-boot-starter-web 依赖关系
2. Spring MVC on Spring Boot
2.1 Spring MVC 体系温故知新
2.2 重要的类
2.3 Spring Boot MVC
2.3.1 控制器
2.3.2 数据绑定
2.3.3 视图和视图解析 

一、spring-boot-starter-web 依赖概述
在 Spring Boot 快速入门中,只要在 pom.xml 加入了 spring-boot-starter-web 依赖,即可快速开发 web 应用。可见,Spring Boot 极大地简化了 Spring 应用从搭建到开发的过程,做到了「开箱即用」的方式。Spring Boot 已经提供很多「开箱即用」的依赖,如上面开发 web 应用使用的 spring-boot-starter-web ,都是以 spring-boot-starter-xx 进行命名的。
 
Spring Boot 「开箱即用」 的设计,对开发者非常便利。简单来说,只要往 Spring Boot 项目加入相应的 spring-boot-starter-xx 依赖,就可以使用对应依赖的功能,比如加入 spring-boot-starter-data-jpa 依赖,就可以使用数据持久层框架 Spring Data JPA 操作数据源。相比 Spring 以前需要大量的XML配置以及复杂的依赖管理,极大的减少了开发工作量和学习成本。

当开发一个特定类型的应用程序时,特定的 Starter 提供所需的依赖关系,并且将对应的 Bean 注册到 Spring 容器中。spring-boot-starter-web 依赖就是提供开发 Web 应用的。

1.1 spring-boot-starter-web 职责
spring-boot-starter-web 是一个用于构建 Web 的 Starter ,包括构建 RESTful 服务应用、Spring MVC 应用等。并且不需要额外配置容器,默认使用 Tomcat 作为嵌入式容器。

1.2 spring-boot-starter-web 依赖关系
spring-boot-starter-web 这么强大,它的组成如下表:
spring-boot-starter  核心包,包括了自动化配置支持、日志、YAML 文件解析的支持等。
spring-boot-starter-json 读写 JSON 包
spring-boot-starter-tomcat Tomcat 嵌入式 Servlet 容器包
hibernate-validator Hibernate 框架提供的验证包
spring-web Spring 框架的 Web 包
spring-webmvc Spring 框架的 Web MVC 包

WX20170727-181441.png



spring-boot-starter-web 包含了 Tomcat 和 Spring MVC ,那启动流程是这样的。 标识 @SpringBootApplication 的应用,初始化经过 spring-boot-starter  核心包中的自动化配置,构建了 Spring 容器,并通过 Tomcat 启动 Web 应用。很多 Starters 只支持 Spring MVC,一般会将 spring-boot-starter-web 依赖加入到应用的 Classpath。

另外,spring-boot-starter-web 默认使用 Tomcat 作为嵌入式 Servlet 容器,在 pom.xml 配置 spring-boot-starter-jetty 和 spring-boot-starter-undertow 就可以替换默认容器。


二、Spring MVC on Spring Boot
Spring MVC 是 Spring Web 重要的模块。内容包括 MVC 模式的实现和 RESTful 服务的支持。

2.1 Spring MVC 体系温故知新

spring-webmvc 模块里面包:
- org.springframework.web.servlet 提供与应用程序上下文基础结构集成的 Servlet,以及 Spring web MVC 框架的核心接口和类。
- org.springframework.web.servlet.mvc Spring 附带的 Servlet MVC 框架的标准控制器实现。
- org.springframework.web.servlet.mvc.annotation 用于基于注解的 Servlet MVC 控制器的支持包。
- org.springframework.web.servlet.mvc.condition 用于根据条件匹配传入请求的公共 MVC 逻辑。
- org.springframework.web.servlet.mvc.method 用于处理程序方法处理的基于 Servlet 的基础结构,基于在 org.springframework.web.method 包上。
- org.springframework.web.servlet.view 提供标准的 View 和 ViewResolver 实现,包括自定义实现的抽象基类。
- org.springframework.web.servlet.view.freemarker 支持将 FreeMarker 集成为 Spring Web 视图技术的类。
- org.springframework.web.servlet.view.json 支持提供基于 JSON 序列化的 View 实现的类。

上面列出来核心的包。org.springframework.web.servlet.view 包中, View 视图实现有常见的:JSON 、FreeMarker 等。org.springframework.web.servlet.mvc 包中,Controller 控制层实现包括了注解、程序方法处理等封装。自然,看源码先从 org.springframework.web.servlet 包看其核心的接口和类。

2.2 重要的类
DispatcherServlet 类:调度 HTTP 请求控制器(或者处理器 Handler)。  
View 视图层 ModelAndView 类:模型和视图的持有者。
View 接口:MVC WEB 交互。该接口的实现负责呈现视图或者暴露模型。  
Controller 控制层 HandlerMapping 接口: 请求从 DispacherServlet 过来,该接口定义请求和处理程序对象之间的映射。
HandlerInterceptor 接口:处理程序的执行链接口。

Spring MVC 框架模型
WechatIMG485.jpeg



2.3 Spring Boot MVC
以前 Spring MVC 开发模式是这样的:
1. 在 web.xml 配置 DispatcherServlet,用于截获并处理所有请求
2. 在 Spring MVC 配置文件中,声明预定义的控制器和视图解析器等
3. 编写预定义的处理请求控制器
4. 编写预定义的视图对象,比如 JSP、Freemarker 等

在 Spring Boot MVC 中,Web 自动化配置会帮你减少上面的两个步骤。默认使用的视图是 ThymeLeaf,在下面小节会具体讲
1. 编写预定义的处理请求控制器
2. 编写默认 ThymeLeaf 视图对象

例如下面会展示用户列表案例:
第一步:处理用户请求控制器
UserController.java
/**
* 用户控制层
*
* Created by bysocket on 24/07/2017.
*/
@Controller
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在 /users
public class UserController {
@Autowired
UserService userService; // 用户服务层
/**
* 获取用户列表
* 处理 "/users" 的GET请求,用来获取用户列表
* 通过 @RequestParam 传递参数,进一步实现条件查询或者分页查询
*/
@RequestMapping(method = RequestMethod.GET)
public String getUserList(ModelMap map) {
map.addAttribute("userList", userService.findAll());
return "userList";
}
}

 
@Controller 注解在 UserController 类上,标识其为一个可接收 HTTP 请求的控制器
@RequestMapping(value = "/users") 注解 ,标识 UserController 类下所有接收的请求路由都是 /users 开头的。注意:类上的 @RequestMapping 注解是不必需的

@RequestMapping(method = RequestMethod.GET) 注解,标识该 getUserList(ModelMap map) 方法会接收并处理 /users 请求,且请求方法是 GET
getUserList(ModelMap map) 方法返回的字符串 userList ,代表着是视图,会有视图解析器解析成为一个具体的视图对象,然后经过视图渲染展示到浏览器

第二步:用户列表 ThymeLeaf 视图对象
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<script type="text/javascript" th:src="@{https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js}"></script>
<link th:href="@{https://cdn.bootcss.com/bootst ... n.css}" rel="stylesheet"/>
<link th:href="@{/css/default.css}" rel="stylesheet"/>
<link rel="icon" th:href="@{/images/favicon.ico}" type="image/x-icon"/>
<meta charset="UTF-8"/>
<title>用户列表</title>
</head>
<body>
<div class="contentDiv">
<h5> 《 Spring Boot 2.x 核心技术实战》第二章快速入门案例</h5>
<table class="table table-hover table-condensed">
<legend>
<strong>用户列表</strong>
</legend>
<thead>
<tr>
<th>用户编号</th>
<th>名称</th>
<th>年龄</th>
<th>出生时间</th>
<th>管理</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${userList}">
<th scope="row" th:text="${user.id}"></th>
<td><a th:href="@{/users/update/{userId}(userId=${user.id})}" th:text="${user.name}"></a></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthday}"></td>
<td><a class="btn btn-danger" th:href="@{/users/delete/{userId}(userId=${user.id})}">删除</a></td>
</tr>
</tbody>
</table>
<div><a class="btn btn-primary" href="/users/create" role="button">创建用户</a></div>
</div>
</body>
</html>

一个 table 展示用户列表,引入了 jquery.min.js 和 bootstrap.min.css ,更好的展示页面效果。具体 ThymeLeaf 语法下面会讲到。

代码共享在:https://github.com/JeffLi1993/spring-boot-core-book-demo  

2.3.1 控制器
什么是控制器?控制器就是控制请求接收和负责响应到视图的角色。
@Controller 注解标识一个类作为控制器。DispatcherServlet 会扫描所有控制器类,并检测 @RequestMapping 注解配置的方法。Web 自动化配置已经处理完这一步骤。

@RequestMapping 注解标识请求 URL 信息,可以映射到整个类或某个特定的方法上。该注解可以表明请求需要的。
使用 value 指定特定的 URL ,比如 @RequestMapping(value = "/users”) 和 @RequestMapping(value = "/users/create”) 等
使用 method 指定 HTTP 请求方法,比如 RequestMethod.GET 等
还有使用其他特定的参数条件,可以设置 consumes 指定请求时的请求头需要包含的 Content-Type 值、设置 produces 可确保响应的内容类型

MVC on REST ful 场景
在 HTTP over JSON (自然 JSON、XML或其他自定义的媒体类型内容等均可)场景,配合上前后端分离的开发模式,我们经常会用 @ResponseBody 或 @RestController 两种方式实现 RESTful HTTP API 。

老方式:
@ResponseBody 注解标识该方法的返回值。这样被标注的方法返回值,会直接写入 HTTP 响应体(而不会被视图解析器认为是一个视图对象)。

新方式:
@RestController 注解,和 @Controller 用法一致,整合了 @Controller 和 @ResponseBody 功能。这样不需要每个 @RequestMapping 方法上都加上 @ResponseBody 注解,这样代码更简明。

使代码更简明,还有常用便捷注解 @GetMapping、@PostMapping 和 @PutMapping 等
HTTP 协议相关知识回顾,可以看看我以前的博文《图解 HTTP 协议》http://www.bysocket.com/?p=282 

2.3.2 数据绑定
数据绑定,简单的说就是 Spring MVC 从请求中获取请求入参,赋予给处理方法相应的入参。主要流程如下:
1. DataBinder 接受带有请求入参的 ServletRequest 对象
2. 调用 ConversionService 组件,进行数据类型转换、数据格式化等工作
3. 然后调用 Validator 组件,进行数据校验等工作
4. 绑定结果到 BindingResult 对象
5. 最后赋予给处理方法相应的入参

@ModelAttribute 注解添加一个或多个属性(类对象)到 model 上。例如
@RequestMapping(value = "/create", method = RequestMethod.POST) 
public String postUser(@ModelAttribute User user)

@PathVariable 注解通过变量名匹配到 URI 模板中相对应的变量。例如
@RequestMapping(value = "/update/{id}", method = RequestMethod.GET) 
public String getUser(@PathVariable("id") Long id, ModelMap map)
   
@RequestParam 注解将请求参数绑定到方法参数。
@RequestHeader 注解将请求头属性绑定到方法参数。

2.3.3 视图和视图解析
视图的职责就是渲染模型数据,将模型里面的数据展示给用户。

请求到经过处理方法处理后,最终返回的是 ModeAndView 。可以从 Spring MVC 框架模型 看出,最终经过 ViewResolver 视频解析器得到视图对象 View。可能是我们常见的 JSP ,也可能是基于 ThymLeaf 、FreeMarker 或 Velocity 模板引擎视图,当然还有可能是 JSON 、XML 或者 PDF 等各种形式。
业界流行的模板引擎有如下的 Starters 支持:
spring-boot-starter-thymeleaf Thymeleaf 模板视图依赖,官方推荐
spring-boot-starter-freemarker Freemarker 模板视图依赖
spring-boot-starter-groovy-templates Groovy 模板视图依赖
spring-boot-starter-mustache Mustache 模板视图依赖

具体,spring-boot-starter-thymeleaf 使用案例在 GitHub :https://github.com/JeffLi1993/spring-boot-core-book-demo   的 chapter-2-spring-boot-quick-start 工程。

三、小结
本文主要介绍了 Spring Boot 在 Web 开发中涉及到的 HTTP 协议,还有一些 Spring MVC 相关的知识。

推荐:
开源项目 springboot-learning-example https://github.com/JeffLi1993/ ... ample 
开源项目 spring-boot-core-book-demo https://github.com/JeffLi1993/ ... -demo 

资料:
- 官方文档 http://docs.spring.io/spring-b ... ingle 
-《精通Spring 4.x 企业应用开发实战》

欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
http://www.bysocket.com/
https://github.com/JeffLi1993 — 

qrcode_for_gh_cd421e7eb7d6_430.jpg

 

Spring Boot HTTP over JSON 的错误码异常处理

Spring Boot泥瓦匠BYSocket 发表了文章 • 9 个评论 • 267 次浏览 • 2017-08-01 14:39 • 来自相关话题

摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!

“年轻人不要怕表现,要敢于出来表现,但还是那句话,要有正确的度,你的表现是分析问题和解决问题的能力。”

– 《你凭什么做好互联网》

本文提纲
一、异常统一处理的使用场景
二、运行 springboot-validation-over-json 工程
三、springboot-validation-over-json 工程代码详解

一、异常统一处理的使用场景

在前后端分离开发中,经常用 HTTP over JSON 作为服务进行前后端联调对接。这里简单介绍下为啥前后端分离开发?我想到如下:

1.低耦合,责权分离,模块化。前后端之间利用轻量级协议对接耦合。
2.便于敏捷开发:后端给出 api 文档 -> 前端根据文档,mock出数据开发 ;同时,后端实现业务逻辑。
3.微服务尤其适用

这时候 HTTP over JSON 形式中很多涉及到返回码,错误码相关的处理。比如xxx参数不完整,权限不足,用户不存在等。

怎么统一处理认为是异常的场景呢?
利用的是 Spring 4.x 提供的 RestControllerAdvice。这里做下说明,也可以根据 ControllerAdvice 去实现。这里案例是 HTTP over JSON 模式,所以直接利用
RestControllerAdvice ,控制层通知器,这里用于统一拦截异常,进行响应处理。工作模式,如图:






二、运行 springboot-validation-over-json 工程

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+(内涵 Spring 4.x)

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

2.运行 springboot-validation-over-json 工程
右键运行 springboot-validation-over-json 工程 Application 应用启动类的 main 函数。默认端口 8080

3.访问案例
a. 参数不完整案例:
访问浏览器打开下面链接,可得到以下 JSON 返回
http://localhost:8080/api/city?cityName=
{
    "code": "000001",
    "message": "params no complete",
    "result": null
}

b. 成功案例:
访问浏览器打开下面链接,可得到以下 JSON 返回
http://localhost:8080/api/city ... %2582
{
    "code": "0",
    "message": "success",
    "result": {
        "id": 1,
        "provinceId": 2,
        "cityName": "温岭",
        "description": "是我的故乡"
    }
}

三、springboot-validation-over-json 工程代码详解

代码详解提纲:
a.控制层通知器
b.响应码设计

同样,代码共享在我的 GitHub 上:
https://github.com/JeffLi1993/ ... -json

首先,工程代码目录如下:
├── pom.xml
└── src
    └── main
        └── java
            └── org
                └── spring
                    └── springboot
                        ├── Application.java
                        ├── constant
                        │   └── CityErrorInfoEnum.java
                        ├── result
                        │   ├── ErrorInfoInterface.java
                        │   ├── GlobalErrorInfoEnum.java
                        │   ├── GlobalErrorInfoException.java
                        │   ├── GlobalErrorInfoHandler.java
                        │   └── ResultBody.java
                        └── web
                            ├── City.java
                            └── ErrorJsonController.java

a.控制层通知器
GlobalErrorInfoHandler.java 代码如下:@RestControllerAdvice
public class GlobalErrorInfoHandler {

@ExceptionHandler(value = GlobalErrorInfoException.class)
public ResultBody errorHandlerOverJson(HttpServletRequest request,
GlobalErrorInfoException exception) {
ErrorInfoInterface errorInfo = exception.getErrorInfo();
ResultBody result = new ResultBody(errorInfo);
return result;
}
}
@ExceptionHandler 注解,标记了使用 errorHandlerOverJson() 方法来处理 GlobalErrorInfoException 异常。
@RestControllerAdvice 是 @ControllerAdvice 和 @ResponseBody 的语义结合。是控制器增强,直接返回对象。这里用于统一拦截异常,然后返回错误码对象体。
@ResponseBody 作用: 该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区。

b.响应码设计
简单讲讲,这里定义了一个错误码接口,全局错误码枚举和各个业务错误码枚举去实现接口,并用枚举值枚举出错误码及错误码消息列表。如图:






四、小结

如果实战中,大家遇到什么,或者建议《Spring boot 那些事》还需要一起交流的。请点击留言。

推荐书《腾讯传》,其中几章写的很不错。

欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
— http://www.bysocket.com/ —
— https://github.com/JeffLi1993 —





  查看全部
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢!

“年轻人不要怕表现,要敢于出来表现,但还是那句话,要有正确的度,你的表现是分析问题和解决问题的能力。”

– 《你凭什么做好互联网》

本文提纲
一、异常统一处理的使用场景
二、运行 springboot-validation-over-json 工程
三、springboot-validation-over-json 工程代码详解

一、异常统一处理的使用场景

在前后端分离开发中,经常用 HTTP over JSON 作为服务进行前后端联调对接。这里简单介绍下为啥前后端分离开发?我想到如下:

1.低耦合,责权分离,模块化。前后端之间利用轻量级协议对接耦合。
2.便于敏捷开发:后端给出 api 文档 -> 前端根据文档,mock出数据开发 ;同时,后端实现业务逻辑。
3.微服务尤其适用

这时候 HTTP over JSON 形式中很多涉及到返回码,错误码相关的处理。比如xxx参数不完整,权限不足,用户不存在等。

怎么统一处理认为是异常的场景呢?
利用的是 Spring 4.x 提供的 RestControllerAdvice。这里做下说明,也可以根据 ControllerAdvice 去实现。这里案例是 HTTP over JSON 模式,所以直接利用
RestControllerAdvice ,控制层通知器,这里用于统一拦截异常,进行响应处理。工作模式,如图:

1.png


二、运行 springboot-validation-over-json 工程

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+(内涵 Spring 4.x)

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

2.运行 springboot-validation-over-json 工程
右键运行 springboot-validation-over-json 工程 Application 应用启动类的 main 函数。默认端口 8080

3.访问案例
a. 参数不完整案例:
访问浏览器打开下面链接,可得到以下 JSON 返回
http://localhost:8080/api/city?cityName=
{
    "code": "000001",
    "message": "params no complete",
    "result": null
}

b. 成功案例:
访问浏览器打开下面链接,可得到以下 JSON 返回
http://localhost:8080/api/city ... %2582
{
    "code": "0",
    "message": "success",
    "result": {
        "id": 1,
        "provinceId": 2,
        "cityName": "温岭",
        "description": "是我的故乡"
    }
}

三、springboot-validation-over-json 工程代码详解

代码详解提纲:
a.控制层通知器
b.响应码设计

同样,代码共享在我的 GitHub 上:
https://github.com/JeffLi1993/ ... -json

首先,工程代码目录如下:
├── pom.xml
└── src
    └── main
        └── java
            └── org
                └── spring
                    └── springboot
                        ├── Application.java
                        ├── constant
                        │   └── CityErrorInfoEnum.java
                        ├── result
                        │   ├── ErrorInfoInterface.java
                        │   ├── GlobalErrorInfoEnum.java
                        │   ├── GlobalErrorInfoException.java
                        │   ├── GlobalErrorInfoHandler.java
                        │   └── ResultBody.java
                        └── web
                            ├── City.java
                            └── ErrorJsonController.java

a.控制层通知器
GlobalErrorInfoHandler.java 代码如下:
@RestControllerAdvice
public class GlobalErrorInfoHandler {

@ExceptionHandler(value = GlobalErrorInfoException.class)
public ResultBody errorHandlerOverJson(HttpServletRequest request,
GlobalErrorInfoException exception) {
ErrorInfoInterface errorInfo = exception.getErrorInfo();
ResultBody result = new ResultBody(errorInfo);
return result;
}
}

@ExceptionHandler 注解,标记了使用 errorHandlerOverJson() 方法来处理 GlobalErrorInfoException 异常。
@RestControllerAdvice 是 @ControllerAdvice 和 @ResponseBody 的语义结合。是控制器增强,直接返回对象。这里用于统一拦截异常,然后返回错误码对象体。
@ResponseBody 作用: 该注解用于将 Controller 的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到 Response 对象的 body 数据区。

b.响应码设计
简单讲讲,这里定义了一个错误码接口,全局错误码枚举和各个业务错误码枚举去实现接口,并用枚举值枚举出错误码及错误码消息列表。如图:

2.png


四、小结

如果实战中,大家遇到什么,或者建议《Spring boot 那些事》还需要一起交流的。请点击留言。

推荐书《腾讯传》,其中几章写的很不错。

欢迎扫一扫我的公众号关注 — 及时得到博客订阅哦!
http://www.bysocket.com/
https://github.com/JeffLi1993

3.jpg

 

Spring boot 使用AbstractRoutingDataSource实现多数据源管理

Spring Bootzjh527 发表了文章 • 1 个评论 • 722 次浏览 • 2017-08-01 08:43 • 来自相关话题

    老司机们都知道,Spring提供了一个 AbstractRoutingDataSource 可以实现数据源路由功能。但是就目前能找到的关于 Spring boot 多数据源配置大都与翟永超的《Spring boot 多数据源的配置与使用》一文介绍的方式相同。
     这里我们介绍如何使用 AbstractRoutingDataSource 实现 Spring boot 多数据源。因为个人认为相较于前面提到的实现方式,基于 AbstractRoutingDataSource 的方式更优雅。
     我们知道 Spring boot 引入了自动化配置,这样我们在配置数据源时,只需要引入相关jar,并在特定的.properties(.yaml)中配置相关内容,Spring boot 就能创建出我们需要的数据源。这里我们希望继续以这种方式实现我们的多数据源。
    如何在 Spring boot 自动化配置的过程中植入我们需要的 AbstractRoutingDataSource,这就不得不提 
BeanPostProcessor 。它是一个 factory hook,通过它我们可以实现 Bean 实例的重定义、属性设置、包装等操作。既然在 BeanPostProcessor 中能重新定义 Bean 实例,那么我们就利用这个特性在 BeanPostProcessor 中实例化一个 AbstractRoutingDataSource 实现类对象替换掉 Spring boot 自动化配置的数据源。
 
下面我们直接上代码,注意这里使用的 Spring boot 版本为 1.5.5.RELEASE。
 
spring.datasource.url=jdbc:mysql://localhost:3306/test1
spring.datasource.username=root
spring.datasource.password=123

spring.datasource.names=ds1
spring.datasource.ds1.url=jdbc:mysql://localhost:3306/test2
spring.datasource.ds1.username=root
spring.datasource.ds1.password=123 
public class DataSourceContext {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

public static void setDatasourceName(String datasourceName){
contextHolder.set(datasourceName);
}

public static String getDatasourceName(){
return contextHolder.get();
}

public static void clean(){
contextHolder.remove();
}
} public class RoutingDataSource extends AbstractRoutingDataSource{
private static final Logger logger = LoggerFactory.getLogger(RoutingDataSource.class);
private static final String DATASOURCE_PROPERTY_PREFIX = "spring.datasource.";

private Environment environment;
private Map<Object, Object> targetDataSources;

@Override
protected Object determineCurrentLookupKey() {
String dataSourceName = DataSourceContext.getDatasourceName();
logger.info("datasource [{}].", StringUtils.hasText(dataSourceName)? dataSourceName : "master");
return dataSourceName;
}

@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = new HashMap<>(targetDataSources);
}

@Override
public void afterPropertiesSet() {
buildTargetDataSources();
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}


public void setEnvironment(Environment environment) {
this.environment = environment;
}

private void buildTargetDataSources(){
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(this.environment, DATASOURCE_PROPERTY_PREFIX);
String targetDatasourceNames = propertyResolver.getProperty("names");
logger.info("target datasource names: {}", targetDatasourceNames);

if(!StringUtils.hasText(targetDatasourceNames)){
return;
}


for (String name : targetDatasourceNames.split(",")){
Map<String, Object> subProperties = propertyResolver.getSubProperties(name + '.');
logger.info("sub properties: {}", subProperties);
targetDataSources.put(name, buildDatasource(subProperties));
}
}

private DataSource buildDatasource(Map<String, Object> properties){
if(properties.containsKey("jndi-name")){
return buildJndiDatasource(properties.get("jndi-name").toString());
}else{
return buildJdbcDatasource(properties);
}
}

private DataSource buildJdbcDatasource(Map<String, Object> properties){
DataSourceBuilder factory = DataSourceBuilder.create()
.url(properties.get("url").toString())
.username(properties.get("username").toString())
.password(properties.get("password").toString());

return factory.build();
}

private DataSource buildJndiDatasource(String datasourceName){
JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup();
return jndiDataSourceLookup.getDataSource(datasourceName);
}
} @Component
public class DatasourceProxyBeanProcessor implements BeanPostProcessor, EnvironmentAware {
private Environment environment;

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
if(bean instanceof DataSource){
DataSource dataSourceBean = (DataSource) bean;
AbstractRoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setDefaultTargetDataSource(dataSourceBean);
((RoutingDataSource)routingDataSource).setEnvironment(this.environment);

Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", dataSourceBean);
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.afterPropertiesSet();
return routingDataSource;
}
return bean;
}
}

通过上面三个类,我们已经可以在编码时使用DataSourceContext.setDatasourceName("ds1")和DataSourceContext.clean()实现数据源的路由。当然它现在使用起来还比较丑陋,我们可以通过切面来使得调用更优雅些。
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TargetDataSource {
String value();
}
 @Component
@Aspect
public class DataSourceAspect {

@Around("@annotation(targetDataSource)")
public Object changeDatasource(ProceedingJoinPoint pjp, TargetDataSource targetDataSource) throws Throwable {

try {
DataSourceContext.setDatasourceName(targetDataSource.value());
Object rtnValue = pjp.proceed();
DataSourceContext.clean();
return rtnValue;
} catch (Throwable throwable) {
DataSourceContext.clean();
throw throwable;
}
}
}
 下面我们通过一个测试用例,测试下是否达到了我们的要求。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
private static final Logger logger = LoggerFactory.getLogger(ApplicationTests.class);

@Autowired
private UserService userService;

@Before
public void setup(){
userService.clean();
userService.clean2();
}

@Test
public void contextLoads() {

}

@Test
public void userServiceTest(){
userService.save(new User(1L, "aaa", 20));
logger.info("{}", userService.findAll());
}

@Test
public void changeDatasourceTest(){
userService.save(new User(1L, "aaa", 20));

DataSourceContext.setDatasourceName("ds1");
userService.save(new User(2L, "bbb", 26));
DataSourceContext.clean();

Assert.assertEquals(1, userService.findAll().size());

DataSourceContext.setDatasourceName("ds1");
Assert.assertEquals(1, userService.findAll().size());
DataSourceContext.clean();
}

@Test
public void datasourceAspectTest(){
userService.save(new User(1L, "aaa", 20));
userService.save2(new User(2L, "bbb", 26));

Assert.assertEquals(1, userService.findAll().size());
Assert.assertEquals(1, userService.findAll2().size());
}

}
 整个代码很粗,离生产还有很大的距离,需要你去完善。完整工程代码https://github.com/loafer/spring-boot-tutorials/tree/master/springboot-multidatasource
  查看全部
    老司机们都知道,Spring提供了一个 AbstractRoutingDataSource 可以实现数据源路由功能。但是就目前能找到的关于 Spring boot 多数据源配置大都与翟永超的《Spring boot 多数据源的配置与使用》一文介绍的方式相同。
     这里我们介绍如何使用 AbstractRoutingDataSource 实现 Spring boot 多数据源。因为个人认为相较于前面提到的实现方式,基于 AbstractRoutingDataSource 的方式更优雅。
     我们知道 Spring boot 引入了自动化配置,这样我们在配置数据源时,只需要引入相关jar,并在特定的.properties(.yaml)中配置相关内容,Spring boot 就能创建出我们需要的数据源。这里我们希望继续以这种方式实现我们的多数据源。
    如何在 Spring boot 自动化配置的过程中植入我们需要的 AbstractRoutingDataSource,这就不得不提 
BeanPostProcessor 。它是一个 factory hook,通过它我们可以实现 Bean 实例的重定义、属性设置、包装等操作。既然在 BeanPostProcessor 中能重新定义 Bean 实例,那么我们就利用这个特性在 BeanPostProcessor 中实例化一个 AbstractRoutingDataSource 实现类对象替换掉 Spring boot 自动化配置的数据源。
 
下面我们直接上代码,注意这里使用的 Spring boot 版本为 1.5.5.RELEASE。
 
spring.datasource.url=jdbc:mysql://localhost:3306/test1
spring.datasource.username=root
spring.datasource.password=123

spring.datasource.names=ds1
spring.datasource.ds1.url=jdbc:mysql://localhost:3306/test2
spring.datasource.ds1.username=root
spring.datasource.ds1.password=123
 
public class DataSourceContext {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

public static void setDatasourceName(String datasourceName){
contextHolder.set(datasourceName);
}

public static String getDatasourceName(){
return contextHolder.get();
}

public static void clean(){
contextHolder.remove();
}
}
 
public class RoutingDataSource extends AbstractRoutingDataSource{
private static final Logger logger = LoggerFactory.getLogger(RoutingDataSource.class);
private static final String DATASOURCE_PROPERTY_PREFIX = "spring.datasource.";

private Environment environment;
private Map<Object, Object> targetDataSources;

@Override
protected Object determineCurrentLookupKey() {
String dataSourceName = DataSourceContext.getDatasourceName();
logger.info("datasource [{}].", StringUtils.hasText(dataSourceName)? dataSourceName : "master");
return dataSourceName;
}

@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = new HashMap<>(targetDataSources);
}

@Override
public void afterPropertiesSet() {
buildTargetDataSources();
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}


public void setEnvironment(Environment environment) {
this.environment = environment;
}

private void buildTargetDataSources(){
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(this.environment, DATASOURCE_PROPERTY_PREFIX);
String targetDatasourceNames = propertyResolver.getProperty("names");
logger.info("target datasource names: {}", targetDatasourceNames);

if(!StringUtils.hasText(targetDatasourceNames)){
return;
}


for (String name : targetDatasourceNames.split(",")){
Map<String, Object> subProperties = propertyResolver.getSubProperties(name + '.');
logger.info("sub properties: {}", subProperties);
targetDataSources.put(name, buildDatasource(subProperties));
}
}

private DataSource buildDatasource(Map<String, Object> properties){
if(properties.containsKey("jndi-name")){
return buildJndiDatasource(properties.get("jndi-name").toString());
}else{
return buildJdbcDatasource(properties);
}
}

private DataSource buildJdbcDatasource(Map<String, Object> properties){
DataSourceBuilder factory = DataSourceBuilder.create()
.url(properties.get("url").toString())
.username(properties.get("username").toString())
.password(properties.get("password").toString());

return factory.build();
}

private DataSource buildJndiDatasource(String datasourceName){
JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup();
return jndiDataSourceLookup.getDataSource(datasourceName);
}
}
 
@Component
public class DatasourceProxyBeanProcessor implements BeanPostProcessor, EnvironmentAware {
private Environment environment;

@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
if(bean instanceof DataSource){
DataSource dataSourceBean = (DataSource) bean;
AbstractRoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setDefaultTargetDataSource(dataSourceBean);
((RoutingDataSource)routingDataSource).setEnvironment(this.environment);

Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", dataSourceBean);
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.afterPropertiesSet();
return routingDataSource;
}
return bean;
}
}

通过上面三个类,我们已经可以在编码时使用DataSourceContext.setDatasourceName("ds1")和DataSourceContext.clean()实现数据源的路由。当然它现在使用起来还比较丑陋,我们可以通过切面来使得调用更优雅些。
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TargetDataSource {
String value();
}

 
@Component
@Aspect
public class DataSourceAspect {

@Around("@annotation(targetDataSource)")
public Object changeDatasource(ProceedingJoinPoint pjp, TargetDataSource targetDataSource) throws Throwable {

try {
DataSourceContext.setDatasourceName(targetDataSource.value());
Object rtnValue = pjp.proceed();
DataSourceContext.clean();
return rtnValue;
} catch (Throwable throwable) {
DataSourceContext.clean();
throw throwable;
}
}
}

 下面我们通过一个测试用例,测试下是否达到了我们的要求。
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
private static final Logger logger = LoggerFactory.getLogger(ApplicationTests.class);

@Autowired
private UserService userService;

@Before
public void setup(){
userService.clean();
userService.clean2();
}

@Test
public void contextLoads() {

}

@Test
public void userServiceTest(){
userService.save(new User(1L, "aaa", 20));
logger.info("{}", userService.findAll());
}

@Test
public void changeDatasourceTest(){
userService.save(new User(1L, "aaa", 20));

DataSourceContext.setDatasourceName("ds1");
userService.save(new User(2L, "bbb", 26));
DataSourceContext.clean();

Assert.assertEquals(1, userService.findAll().size());

DataSourceContext.setDatasourceName("ds1");
Assert.assertEquals(1, userService.findAll().size());
DataSourceContext.clean();
}

@Test
public void datasourceAspectTest(){
userService.save(new User(1L, "aaa", 20));
userService.save2(new User(2L, "bbb", 26));

Assert.assertEquals(1, userService.findAll().size());
Assert.assertEquals(1, userService.findAll2().size());
}

}

 整个代码很粗,离生产还有很大的距离,需要你去完善。完整工程代码https://github.com/loafer/spring-boot-tutorials/tree/master/springboot-multidatasource
 

SpringBoot利用注解的方式解决AJAX请求跨域(2中方式)

Spring BootLevin 发表了文章 • 2 个评论 • 390 次浏览 • 2017-07-24 19:04 • 来自相关话题

 
 - 第一种方式(存在BUG)
  1.编写一个支持跨域请求的 Configurationimport org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
* 处理AJAX请求跨域的问题
* @author Levin
* @time 2017-07-13
*/
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {
static final String ORIGINS = new String { "GET", "POST", "PUT", "DELETE" };
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowCredentials(true).allowedMethods(ORIGINS)
.maxAge(3600);
}
}2.HTTP请求接口@RestController
public class HelloController {

@Autowired
HelloService helloService;


@GetMapping(value = "/test", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String query() {
return "hello";
}
}
 - 第二种方式(推荐)
 
PS:第一种存在一个问题,当服务器抛出 500 的时候依旧存在跨域问题@SpringBootApplication
@ComponentScan
@EnableDiscoveryClient
public class ManagementApplication {

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

private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addExposedHeader(HttpHeaderConStant.X_TOTAL_COUNT);
return corsConfiguration;
}

/**
* 跨域过滤器
*
* @return
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 4
return new CorsFilter(source);
}
}

- index.html<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>跨域请求</title>
<script src="https://cdn.bootcss.com/jquery ... gt%3B
<script>
$(document).ready(function(){
$("button").click(function(){
$.ajax({url:"http://localhost:8080/test",success:function(result){
$("#p1").html(result);
}});
});
});
</script>
</head>
<body>

<p width="500px" height="100px" id="p1"></p>
<button>获取其他内容</button>
</body>
</html>





 
原文链接:http://blog.battcn.com/2017/07/13/springboot-cors/ 查看全部
 
 - 第一种方式(存在BUG)
  1.编写一个支持跨域请求的 Configuration
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
* 处理AJAX请求跨域的问题
* @author Levin
* @time 2017-07-13
*/
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {
static final String ORIGINS = new String { "GET", "POST", "PUT", "DELETE" };
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowCredentials(true).allowedMethods(ORIGINS)
.maxAge(3600);
}
}
2.HTTP请求接口
@RestController
public class HelloController {

@Autowired
HelloService helloService;


@GetMapping(value = "/test", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String query() {
return "hello";
}
}

 - 第二种方式(推荐)
 
PS:第一种存在一个问题,当服务器抛出 500 的时候依旧存在跨域问题
@SpringBootApplication
@ComponentScan
@EnableDiscoveryClient
public class ManagementApplication {

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

private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addExposedHeader(HttpHeaderConStant.X_TOTAL_COUNT);
return corsConfiguration;
}

/**
* 跨域过滤器
*
* @return
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 4
return new CorsFilter(source);
}
}

- index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>跨域请求</title>
<script src="https://cdn.bootcss.com/jquery ... gt%3B
<script>
$(document).ready(function(){
$("button").click(function(){
$.ajax({url:"http://localhost:8080/test",success:function(result){
$("#p1").html(result);
}});
});
});
</script>
</head>
<body>

<p width="500px" height="100px" id="p1"></p>
<button>获取其他内容</button>
</body>
</html>





 
原文链接:http://blog.battcn.com/2017/07/13/springboot-cors/

可能是东半球最好用的 Druid Spring Boot Starter

开源项目DrTrang 发表了文章 • 10 个评论 • 2273 次浏览 • 2017-07-13 18:10 • 来自相关话题

Druid Spring Boot Starter 将帮助你在 Spring Boot 中使用 Druid,Github 地址:https://github.com/drtrang/druid-spring-boot


## 依赖<dependency>
<groupId>com.github.drtrang</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.0.1</version>
</dependency>
## 配置
### 简单配置
在引入依赖的情况下,只需如下配置即可使用 Druid:spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:file:./samples
username: root
password: 123456
### Druid 连接池
Druid Spring Boot Starter 会将以 `spring.datasource.druid` 为前缀的配置注入到 DruidDataSource,且 DruidDataSource 中的所有参数均可自定义。spring:
datasource:
druid:
initial-size: 1
min-idle: 1
max-active: 10
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-open-prepared-statements: 20
use-global-data-source-stat: true

### Druid 高级特性
Druid Spring Boot Starter 添加了 Druid 的大部分特性,如 StatFilter、WallFilter、ConfigFilter、WebStatFilter 等,其中 StatFilter 默认打开,其它特性默认关闭,需要手动开启。

同样,每个特性的参数均可自定义,具体参数可以用 IDE 的自动提示功能或者阅读 Druid 的 Wiki 查看。spring:
datasource:
druid:
slf4j:
# 开启 Slf4jFilter
enabled: true
wall:
# 开启 WallFilter
enabled: true
web-stat:
# 开启 Web 监控
enabled: true
stat-view-servlet:
# 开启监控展示
enabled: true

### 配置示例
application.yml


## 自动提示
Druid Spring Boot Starter 基于 `spring-boot-configuration-processor` 模块,支持 IDE 的自动提示。
该功能会持续优化,致力打造最方便、最友好的 Starter。

自定义参数:
)

参数说明:


参数枚举值:



## 演示
druid-spring-boot-samples 演示了 Druid Spring Boot Starter 的使用方式,可以作为参考。   


## 更新记录
Changelog.md


## TODO
任何意见和建议可以提 Issue,我会酌情加到 Todo List,一般情况一周内迭代完毕。


## 作者信息
QQ:349096849
Email:donghao.l@hotmail.com 查看全部
Druid Spring Boot Starter 将帮助你在 Spring Boot 中使用 Druid,Github 地址:https://github.com/drtrang/druid-spring-boot


## 依赖
<dependency>
<groupId>com.github.drtrang</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.0.1</version>
</dependency>

## 配置
### 简单配置
在引入依赖的情况下,只需如下配置即可使用 Druid:
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:file:./samples
username: root
password: 123456

### Druid 连接池
Druid Spring Boot Starter 会将以 `spring.datasource.druid` 为前缀的配置注入到 DruidDataSource,且 DruidDataSource 中的所有参数均可自定义。
spring:
datasource:
druid:
initial-size: 1
min-idle: 1
max-active: 10
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-open-prepared-statements: 20
use-global-data-source-stat: true


### Druid 高级特性
Druid Spring Boot Starter 添加了 Druid 的大部分特性,如 StatFilter、WallFilter、ConfigFilter、WebStatFilter 等,其中 StatFilter 默认打开,其它特性默认关闭,需要手动开启。

同样,每个特性的参数均可自定义,具体参数可以用 IDE 的自动提示功能或者阅读 Druid 的 Wiki 查看。
spring:
datasource:
druid:
slf4j:
# 开启 Slf4jFilter
enabled: true
wall:
# 开启 WallFilter
enabled: true
web-stat:
# 开启 Web 监控
enabled: true
stat-view-servlet:
# 开启监控展示
enabled: true


### 配置示例
application.yml


## 自动提示
Druid Spring Boot Starter 基于 `spring-boot-configuration-processor` 模块,支持 IDE 的自动提示。
该功能会持续优化,致力打造最方便、最友好的 Starter。

自定义参数:
)

参数说明:


参数枚举值:



## 演示
druid-spring-boot-samples 演示了 Druid Spring Boot Starter 的使用方式,可以作为参考。   


## 更新记录
Changelog.md


## TODO
任何意见和建议可以提 Issue,我会酌情加到 Todo List,一般情况一周内迭代完毕。


## 作者信息
QQ:349096849
Email:donghao.l@hotmail.com

一个基于Spring Boot的API、RESTful API项目种子(骨架)

Spring Boot简单的土豆 发表了文章 • 32 个评论 • 2056 次浏览 • 2017-07-04 17:48 • 来自相关话题

最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。

在开发一个API项目之前,搭建项目、引入依赖、配置框架这些基础活自然不用多说,通常为了加快项目的开发进度(早点回家)还需要封装一些常用的类和工具,比如统一的响应结果封装、统一的异常处理、接口签名认证、基础的增删改差方法封装、基础代码生成工具等等,有了这些项目才能开工。

然而,下次再做类似的项目上述那些步骤可能还要搞一遍,虽然通常是拿过来改改,但是还是比较浪费时间。所以,可以利用面向对象抽象、封装的思想,抽取这类项目的共同之处封装成了一个种子项目(估计大部分公司都会有很多类似的种子项目),这样的话下次再开发类似的项目直接在该种子项目上迭代就可以了,减少无意义的重复工作。

在相关项目上线之后,我花了点时间对该种子项目做了一些精简,并且已经把该项目分享到GitHub上面了,如果你正准备做类似项目的话,可以去克隆下来试试,项目地址&使用文档:https://github.com/lihengming/ ... -seed  。如果在使用中发现问题或者有什么好建议的话欢迎提issue或pr一起来完善它。
特征&提供
- 最佳实践的项目结构、配置文件、精简的POM

                           (新标签打开图片,查看大图)
注:使用代码生成器生成代码后会创建model、dao、service、web等包。

- 统一响应结果封装及生成工具/**
* 统一API响应结果封装
*/
public class Result {
private int code;
private String message;
private Object data;
public Result setCode(ResultCode resultCode) {
this.code = resultCode.code;
return this;
}
//省略getter、setter方法
}/**
* 响应码枚举,参考HTTP状态码的语义
*/
public enum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失败
UNAUTHORIZED(401),//未认证(签名错误)
NOT_FOUND(404),//接口不存在
INTERNAL_SERVER_ERROR(500);//服务器内部错误

public int code;

ResultCode(int code) {
this.code = code;
}
}/**
* 响应结果生成工具
*/
public class ResultGenerator {
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

public static Result genSuccessResult() {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE);
}

public static Result genSuccessResult(Object data) {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE)
.setData(data);
}

public static Result genFailResult(String message) {
return new Result()
.setCode(ResultCode.FAIL)
.setMessage(message);
}
}

- 统一异常处理public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
    exceptionResolvers.add(new HandlerExceptionResolver() {
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
            Result result = new Result();
            if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误”
                result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
                logger.info(e.getMessage());
            } else if (e instanceof NoHandlerFoundException) {
                result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
            } else if (e instanceof ServletException) {
                result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
            } else {
                result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
                String message;
                if (handler instanceof HandlerMethod) {
                    HandlerMethod handlerMethod = (HandlerMethod) handler;
                    message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
                            request.getRequestURI(),
                            handlerMethod.getBean().getClass().getName(),
                            handlerMethod.getMethod().getName(),
                            e.getMessage());
                } else {
                    message = e.getMessage();
                }
                logger.error(message, e);
            }
            responseResult(response, result);
            return new ModelAndView();
        }

    });
}


- 常用基础方法抽象封装public interface Service<T> {
void save(T model);//持久化
void save(List<T> models);//批量持久化
void deleteById(Integer id);//通过主鍵刪除
void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4”
void update(T model);//更新
T findById(Integer id);//通过ID查找
T findBy(String fieldName, Object value) throws TooManyResultsException; //通过Model中某个成员变量名称(非数据表中column的名称)查找,value需符合unique约束
List<T> findByIds(String ids);//通过多个ID查找//eg:ids -> “1,2,3,4”
List<T> findByCondition(Condition condition);//根据条件查找
List<T> findAll();//获取所有
}

- 提供代码生成器来生成基础代码public abstract class CodeGenerator {
...
public static void main(String args) {
genCode("输入表名");
}
public static void genCode(String... tableNames) {
for (String tableName : tableNames) {
//根据需求生成,不需要的注掉,模板有问题的话可以自己修改。
genModelAndMapper(tableName);
genService(tableName);
genController(tableName);
}
}
...
}CodeGenerator 可根据表名生成对应的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默认提供POST和RESTful两套Controller模板,根据需要在 genController(tableName) 方法中自己选择,默认是纯POST的),代码模板可根据实际项目的需求来定制,以便渐少重复劳动。由于每个公司业务都不太一样,所以只提供了一些简单的通用方法模板,主要是提供一个思路来减少重复代码的编写。在我们公司的实际使用中,其实根据业务的抽象编写了大量的代码模板。


- 提供简单的接口签名认证public void addInterceptors(InterceptorRegistry registry) {
//接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。
if (!"dev".equals(env)) { //开发环境忽略签名认证
registry.addInterceptor(new HandlerInterceptorAdapter() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//验证签名
boolean pass = validateSign(request);
if (pass) {
return true;
} else {
logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}",
request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

Result result = new Result();
result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败");
responseResult(response, result);
return false;
}
}
});
}
}/**
* 一个简单的签名认证,规则:
* 1. 将请求参数按ascii码排序
* 2. 拼接为a=value&b=value...这样的字符串(不包含sign)
* 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较
*/
private boolean validateSign(HttpServletRequest request) {
String requestSign = request.getParameter("sign");//获得请求签名,如sign=19e907700db7ad91318424a97c54ed57
if (StringUtils.isEmpty(requestSign)) {
return false;
}
List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
keys.remove("sign");//排除sign参数
Collections.sort(keys);//排序

StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串
}
String linkString = sb.toString();
linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最后一个'&'

String secret = "Potato";//密钥,自己修改
String sign = DigestUtils.md5Hex(linkString + secret);//混合密钥md5

return StringUtils.equals(sign, requestSign);//比较
}
- 集成MyBatis、通用Mapper插件、PageHelper分页插件,实现单表业务零SQL

- 使用Druid Spring Boot Starter 集成Druid数据库连接池与监控

- 使用FastJsonHttpMessageConverter,提高JSON序列化速度

技术选型&文档
- Spring Boot(查看Spring Boot学习&使用指南)
- MyBatis(查看官方中文文档)
- MyBatisb通用Mapper插件(查看官方中文文档)
- MyBatis PageHelper分页插件(查看官方中文文档)
- Druid Spring Boot Starter(查看官方中文文档)
- Fastjson(查看官方中文文档)

注:代码贴上去有点乱,可以查看我的简书原文 查看全部

最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。

在开发一个API项目之前,搭建项目、引入依赖、配置框架这些基础活自然不用多说,通常为了加快项目的开发进度(早点回家)还需要封装一些常用的类和工具,比如统一的响应结果封装、统一的异常处理、接口签名认证、基础的增删改差方法封装、基础代码生成工具等等,有了这些项目才能开工。

然而,下次再做类似的项目上述那些步骤可能还要搞一遍,虽然通常是拿过来改改,但是还是比较浪费时间。所以,可以利用面向对象抽象、封装的思想,抽取这类项目的共同之处封装成了一个种子项目(估计大部分公司都会有很多类似的种子项目),这样的话下次再开发类似的项目直接在该种子项目上迭代就可以了,减少无意义的重复工作。

在相关项目上线之后,我花了点时间对该种子项目做了一些精简,并且已经把该项目分享到GitHub上面了,如果你正准备做类似项目的话,可以去克隆下来试试,项目地址&使用文档:https://github.com/lihengming/ ... -seed  。如果在使用中发现问题或者有什么好建议的话欢迎提issue或pr一起来完善它。
特征&提供
- 最佳实践的项目结构、配置文件、精简的POM

                           (新标签打开图片,查看大图)
注:使用代码生成器生成代码后会创建model、dao、service、web等包。

- 统一响应结果封装及生成工具
/**
* 统一API响应结果封装
*/
public class Result {
private int code;
private String message;
private Object data;
public Result setCode(ResultCode resultCode) {
this.code = resultCode.code;
return this;
}
//省略getter、setter方法
}
/**
* 响应码枚举,参考HTTP状态码的语义
*/
public enum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失败
UNAUTHORIZED(401),//未认证(签名错误)
NOT_FOUND(404),//接口不存在
INTERNAL_SERVER_ERROR(500);//服务器内部错误

public int code;

ResultCode(int code) {
this.code = code;
}
}
/**
* 响应结果生成工具
*/
public class ResultGenerator {
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

public static Result genSuccessResult() {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE);
}

public static Result genSuccessResult(Object data) {
return new Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE)
.setData(data);
}

public static Result genFailResult(String message) {
return new Result()
.setCode(ResultCode.FAIL)
.setMessage(message);
}
}


- 统一异常处理
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
    exceptionResolvers.add(new HandlerExceptionResolver() {
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
            Result result = new Result();
            if (e instanceof ServiceException) {//业务失败的异常,如“账号或密码错误”
                result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
                logger.info(e.getMessage());
            } else if (e instanceof NoHandlerFoundException) {
                result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
            } else if (e instanceof ServletException) {
                result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
            } else {
                result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
                String message;
                if (handler instanceof HandlerMethod) {
                    HandlerMethod handlerMethod = (HandlerMethod) handler;
                    message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
                            request.getRequestURI(),
                            handlerMethod.getBean().getClass().getName(),
                            handlerMethod.getMethod().getName(),
                            e.getMessage());
                } else {
                    message = e.getMessage();
                }
                logger.error(message, e);
            }
            responseResult(response, result);
            return new ModelAndView();
        }

    });
}


- 常用基础方法抽象封装
public interface Service<T> {
void save(T model);//持久化
void save(List<T> models);//批量持久化
void deleteById(Integer id);//通过主鍵刪除
void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4”
void update(T model);//更新
T findById(Integer id);//通过ID查找
T findBy(String fieldName, Object value) throws TooManyResultsException; //通过Model中某个成员变量名称(非数据表中column的名称)查找,value需符合unique约束
List<T> findByIds(String ids);//通过多个ID查找//eg:ids -> “1,2,3,4”
List<T> findByCondition(Condition condition);//根据条件查找
List<T> findAll();//获取所有
}


- 提供代码生成器来生成基础代码
public abstract class CodeGenerator {
...
public static void main(String args) {
genCode("输入表名");
}
public static void genCode(String... tableNames) {
for (String tableName : tableNames) {
//根据需求生成,不需要的注掉,模板有问题的话可以自己修改。
genModelAndMapper(tableName);
genService(tableName);
genController(tableName);
}
}
...
}
CodeGenerator 可根据表名生成对应的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默认提供POST和RESTful两套Controller模板,根据需要在 genController(tableName) 方法中自己选择,默认是纯POST的),代码模板可根据实际项目的需求来定制,以便渐少重复劳动。由于每个公司业务都不太一样,所以只提供了一些简单的通用方法模板,主要是提供一个思路来减少重复代码的编写。在我们公司的实际使用中,其实根据业务的抽象编写了大量的代码模板。


- 提供简单的接口签名认证
public void addInterceptors(InterceptorRegistry registry) {
//接口签名认证拦截器,该签名认证比较简单,实际项目中可以使用Json Web Token或其他更好的方式替代。
if (!"dev".equals(env)) { //开发环境忽略签名认证
registry.addInterceptor(new HandlerInterceptorAdapter() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//验证签名
boolean pass = validateSign(request);
if (pass) {
return true;
} else {
logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}",
request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

Result result = new Result();
result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败");
responseResult(response, result);
return false;
}
}
});
}
}
/**
* 一个简单的签名认证,规则:
* 1. 将请求参数按ascii码排序
* 2. 拼接为a=value&b=value...这样的字符串(不包含sign)
* 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较
*/
private boolean validateSign(HttpServletRequest request) {
String requestSign = request.getParameter("sign");//获得请求签名,如sign=19e907700db7ad91318424a97c54ed57
if (StringUtils.isEmpty(requestSign)) {
return false;
}
List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
keys.remove("sign");//排除sign参数
Collections.sort(keys);//排序

StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串
}
String linkString = sb.toString();
linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最后一个'&'

String secret = "Potato";//密钥,自己修改
String sign = DigestUtils.md5Hex(linkString + secret);//混合密钥md5

return StringUtils.equals(sign, requestSign);//比较
}

- 集成MyBatis、通用Mapper插件、PageHelper分页插件,实现单表业务零SQL

- 使用Druid Spring Boot Starter 集成Druid数据库连接池与监控

- 使用FastJsonHttpMessageConverter,提高JSON序列化速度

技术选型&文档
- Spring Boot(查看Spring Boot学习&使用指南
- MyBatis(查看官方中文文档
- MyBatisb通用Mapper插件(查看官方中文文档
- MyBatis PageHelper分页插件(查看官方中文文档
- Druid Spring Boot Starter(查看官方中文文档
- Fastjson(查看官方中文文档

注:代码贴上去有点乱,可以查看我的简书原文

开源spring-boot-starter-dubbox

Spring BootJThink 发表了文章 • 0 个评论 • 217 次浏览 • 2017-06-26 19:31 • 来自相关话题

自定义的spring-boot的dubbox starter,为spring-boot相关的项目使用dubbox提供简易的方式并集成spring-boot的auto configuration。见github:https://github.com/JThink/spring-boot-starter-dubbox





  查看全部
自定义的spring-boot的dubbox starter,为spring-boot相关的项目使用dubbox提供简易的方式并集成spring-boot的auto configuration。见github:https://github.com/JThink/spring-boot-starter-dubbox

项目介绍.png

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