优化阅读格式
This commit is contained in:
32
README.md
32
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
    
|
||||
|
||||
本项目仓库提供spring、spring-boot、spring-cloud 的常用整合用例。**每个用例都提供详细的图文说明**,并给出官方文档的具体链接作为参考。随着spring的迭代,本仓库会持续更新,升级版本和丰富用例。
|
||||
本项目仓库提供 spring、spring-boot、spring-cloud 的常用整合用例。**每个用例都提供详细的图文说明**,并给出官方文档的具体链接作为参考。随着 spring 的迭代,本仓库会持续更新,升级版本和丰富用例。
|
||||
|
||||
**版本说明**:
|
||||
|
||||
@@ -17,21 +17,21 @@ spring-cloud:Finchley.SR2
|
||||
|
||||
## 1. spring samples
|
||||
|
||||
所有spring的项目我都会提供两个版本的sample:
|
||||
所有 spring 的项目我都会提供两个版本的 sample:
|
||||
|
||||
- 一个版本是基于xml配置,也就是最为常见的配置方式;
|
||||
- 另一个版本完全基于代码配置(项目以**annotation**结尾),这也是目前spring官方推荐的更为灵活配置方法,也方便更好的衔接spring boot 的配置。
|
||||
- 一个版本是基于 xml 配置,也就是最为常见的配置方式;
|
||||
- 另一个版本完全基于代码配置(项目以**annotation**结尾),这也是目前 spring 官方推荐的更为灵活配置方法,也方便更好的衔接 spring boot 的配置。
|
||||
|
||||
| samples | 描述 | 官方文档 |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| [springmvc-base](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/springmvc-base)<br/>[springmvc-base-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/springmvc-base-annotation) | springmvc基础、参数绑定、格式转换、数据校验、<br/>异常处理、 文件上传下载、视图渲染 | [Spring Mvc ](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/web.html#mvc) |
|
||||
| [spring-aop](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-aop)<br/>[spring-aop-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-aop-annotation) | spring切面编程 | [Spring AOP](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#aop) |
|
||||
| [springmvc-base](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/springmvc-base)<br/>[springmvc-base-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/springmvc-base-annotation) | springmvc 基础、参数绑定、格式转换、数据校验、<br/>异常处理、 文件上传下载、视图渲染 | [Spring Mvc ](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/web.html#mvc) |
|
||||
| [spring-aop](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-aop)<br/>[spring-aop-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-aop-annotation) | spring 切面编程 | [Spring AOP](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#aop) |
|
||||
| [spring-jdbc](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-jdbc)<br/>[spring-jdbc-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-jdbc-annotation) | spring jdbc-template 的使用 | [Using JdbcTemplate](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#jdbc-JdbcTemplate) |
|
||||
| [spring-mybatis](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-mybatis)<br/>[spring-mybatis-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-mybatis-annotation) | spring 整合 mybatis | [Mybatis-Spring](http://www.mybatis.org/spring/zh/index.html) |
|
||||
| [spring-druid-mybatis](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-druid-mybatis)<br/>[spring-druid-mybatis-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-druid-mybatis-annotation) | spring 整合druid、mybatis | [Alibaba druid](https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98) |
|
||||
| [spring-redis](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-redis)<br/>[spring-redis-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-redis-annotation) | spring 整合 redis 单机+集群(jedis客户端)<br/>spring 整合 redis 单机+集群(redisson客户端) | [Redisson](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) |
|
||||
| [spring-druid-mybatis](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-druid-mybatis)<br/>[spring-druid-mybatis-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-druid-mybatis-annotation) | spring 整合 druid、mybatis | [Alibaba druid](https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98) |
|
||||
| [spring-redis](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-redis)<br/>[spring-redis-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-redis-annotation) | spring 整合 redis 单机 + 集群(jedis 客户端)<br/>spring 整合 redis 单机 + 集群(redisson 客户端) | [Redisson](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95) |
|
||||
| [spring-mongodb](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-mongodb)<br/>[spring-mongodb-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-mongodb-annotation) | spring 整合 mongodb | [Spring Data MongoDB](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongo.mongo-db-factory-java) |
|
||||
| [spring-memcached](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-memcached)<br/>[spring-memcached-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-memcached-annotation) | spring 整合 memcached(单机+集群) | [Xmemcached](https://github.com/killme2008/xmemcached/wiki/Xmemcached%20%E4%B8%AD%E6%96%87%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97) |
|
||||
| [spring-memcached](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-memcached)<br/>[spring-memcached-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-memcached-annotation) | spring 整合 memcached(单机 + 集群) | [Xmemcached](https://github.com/killme2008/xmemcached/wiki/Xmemcached%20%E4%B8%AD%E6%96%87%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97) |
|
||||
| [spring-rabbitmq](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-rabbitmq)<br/>[spring-rabbitmq-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-rabbitmq-annotation) | spring 整合 rabbitmq、消息序列化与反序列化 | [Rabbitmq](http://www.rabbitmq.com/getstarted.html)<br>[Spring AMQP](https://docs.spring.io/spring-amqp/docs/2.1.3.BUILD-SNAPSHOT/reference/html/) |
|
||||
| [spring-dubbo](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-dubbo)<br/>[spring-dubbo-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-dubbo-annotation) | spring 整合 dubbo | [Dubbo ](http://dubbo.apache.org/zh-cn/docs/user/quick-start.html) |
|
||||
| [spring-websocket](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-websocket)<br/>[spring-websocket-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-websocket-annotation) | spring 整合 websocket | [Spring Websocket](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/web.html#websocket) |
|
||||
@@ -47,11 +47,11 @@ spring-cloud:Finchley.SR2
|
||||
| [spring-boot-base](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-base) | spring-boot 基础 | [spring boot 官方文档](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/)<br>[spring boot 中文官方文档](https://www.breakyizhan.com/springboot/3028.html) |
|
||||
| [spring-boot-yml-profile](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-yml-profile) | yml 语法和多配置切换 | [Using YAML Instead of Properties](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-external-config-yaml) |
|
||||
| [spring-boot-tomcat](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-tomcat) | spring-boot 整合外部容器(tomcat) | [Use Another Web Server](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#howto-use-another-web-server) |
|
||||
| [spring-boot-servlet](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-servlet) | spring boot 整合servlet 3.0 | [Embedded Servlet Container Support](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-embedded-container) |
|
||||
| [spring-boot-servlet](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-servlet) | spring boot 整合 servlet 3.0 | [Embedded Servlet Container Support](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-embedded-container) |
|
||||
| [spring-boot-jsp](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-jsp) | spring-boot 整合 jsp(内置容器) | [JSP Limitations](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-jsp-limitations) |
|
||||
| [spring-boot-data-jpa](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-data-jpa) | spring-boot data jpa 的使用 | [Spring Data JPA](https://docs.spring.io/spring-data/jpa/docs/2.1.3.RELEASE/reference/html/) |
|
||||
| [spring-boot-mybatis](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mybatis) | spring-boot+HikariDataSources 整合 mybatis | [Mybatis-Spring](http://www.mybatis.org/spring/zh/index.html)<br/>[Mybatis-Spring-Boot-Autoconfigure](http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/) |
|
||||
| [spring-boot-druid-mybtais](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-druid-mybatis) | spring-boot 整合druid、mybatis | [Alibaba druid](https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)<br/>[druid-spring-boot-starter](https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter) |
|
||||
| [spring-boot-druid-mybtais](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-druid-mybatis) | spring-boot 整合 druid、mybatis | [Alibaba druid](https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)<br/>[druid-spring-boot-starter](https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter) |
|
||||
| [spring-boot-redis](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-redis) | spring-boot 整合 redis | [Working with NoSQL Technologies](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-nosql) |
|
||||
| [spring-boot-mongodb](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mongodb) | spring-boot 整合 mongodb | [Working with NoSQL Technologies](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-nosql) |
|
||||
| [spring-boot-memcached](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-memcached) | spring-boot 整合 memcached | [Xmemcached](https://github.com/killme2008/xmemcached/wiki/Xmemcached%20%E4%B8%AD%E6%96%87%E7%94%A8%E6%88%B7%E6%8C%87%E5%8D%97) |
|
||||
@@ -75,7 +75,7 @@ spring-cloud:Finchley.SR2
|
||||
| [spring-cloud-Hystrix](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-hystrix) | Hystix 服务容错保护<br/>hystrix dashboard 断路器监控<br>Turbine 断路器聚合监控 | [Circuit Breaker: Hystrix Clients](https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi__circuit_breaker_hystrix_clients.html)<br/>[Hystrix metrics aggregation with Turbine](https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi_spring-cloud-consul-turbine.html) |
|
||||
| [spring-cloud-Zuul](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-zuul) | Zuul 网关服务 | [Router and Filter: Zuul](https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi__router_and_filter_zuul.html) |
|
||||
| [spring-cloud-Sleuth-Zipkin](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-sleuth-zipkin) | Sleuth + Zipkin 服务链路追踪 | [Spring Cloud Sleuth](https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi__introduction.html#sleuth-adding-project) |
|
||||
| [spring-cloud-Config-Bus](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-config) | Config 分布式配置中心 <br>集成Bus消息总线 实现配置热更新 | [Spring Cloud Config Client](https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi__spring_cloud_config_client.html) |
|
||||
| [spring-cloud-Config-Bus](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-config) | Config 分布式配置中心 <br>集成 Bus 消息总线 实现配置热更新 | [Spring Cloud Config Client](https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi__spring_cloud_config_client.html) |
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -85,13 +85,13 @@ spring-cloud:Finchley.SR2
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| [spring-session](https://github.com/heibaiying/spring-samples-for-all/tree/master/distributed-solution/spring-session) | spring 实现分布式 session | [spring session](https://spring.io/projects/spring-session#learn) |
|
||||
| [spring boot + spring session](https://github.com/heibaiying/spring-samples-for-all/tree/master/distributed-solution/spring-boot-session) | spring boot + spring session 实现分布式 session | [spring session](https://spring.io/projects/spring-session#learn) |
|
||||
| [springboot-druid-mybatis-atomikos](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/springboot-druid-mybatis-multi) | spring boot + druid + mybatis + atomikos<BR> 配置多数据源、支持分布式事务( JTA 方式实现) | [Distributed Transactions with JTA](https://docs.spring.io/spring-boot/docs/2.1.2.RELEASE/reference/htmlsingle/#boot-features-jta) |
|
||||
| [springboot-druid-mybatis-atomikos](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/springboot-druid-mybatis-multi) | spring boot + druid + mybatis + atomikos<BR> 配置多数据源、支持分布式事务 ( JTA 方式实现) | [Distributed Transactions with JTA](https://docs.spring.io/spring-boot/docs/2.1.2.RELEASE/reference/htmlsingle/#boot-features-jta) |
|
||||
|
||||
<br/>
|
||||
|
||||
## 5.参考资料
|
||||
|
||||
相关参考文档放在了仓库的referenced documents 目录下,文件目录如下:
|
||||
相关参考文档放在了仓库的 referenced documents 目录下,文件目录如下:
|
||||
|
||||
- Servlet3.1规范(最终版).pdf
|
||||
- Thymeleaf中⽂参考⼿册.pdf
|
||||
- Servlet3.1 规范(最终版).pdf
|
||||
- Thymeleaf 中⽂参考⼿册.pdf
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 实现分布式 session
|
||||
|
||||
# spring boot 实现分布式 session
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一项目结构">一、项目结构</a><br/>
|
||||
<a href="#二分布式session的配置">二、分布式session的配置</a><br/>
|
||||
<a href="#21-引入依赖">2.1 引入依赖</a><br/>
|
||||
@@ -10,153 +11,153 @@
|
||||
<a href="#32-启动项目">3.2 启动项目</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
## 一、项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-session.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、分布式session的配置
|
||||
|
||||
#### 2.1 引入依赖
|
||||
|
||||
```xml
|
||||
<!--分布式 session 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 2.2 Redis配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
jedis:
|
||||
pool:
|
||||
# 连接池最大连接数,使用负值表示无限制。
|
||||
max-active: 8
|
||||
# 连接池最大阻塞等待时间,使用负值表示无限制。
|
||||
max-wait: -1s
|
||||
# 连接池最大空闲数,使用负值表示无限制。
|
||||
max-idle: 8
|
||||
# 连接池最小空闲连接,只有设置为正值时候才有效
|
||||
min-idle: 1
|
||||
timeout: 300ms
|
||||
session:
|
||||
# session 存储方式 支持redis、mongo、jdbc、hazelcast
|
||||
store-type: redis
|
||||
|
||||
# 如果是集群节点 采用如下配置指定节点
|
||||
#spring.redis.cluster.nodes
|
||||
|
||||
```
|
||||
|
||||
有两点需要特别说明:
|
||||
|
||||
1. spring-session 不仅提供了redis作为公共session存储的方案,同时也支持jdbc、mongodb、Hazelcast等作为公共session的存储,可以用session.store-type 指定;
|
||||
2. 对于redis 存储方案而言,官方也提供了不止一种整合方式,这里我们选取的整合方案是jedis客户端作为连接,当然也可以使用Lettuce作为客户端连接。
|
||||
|
||||
#### 2.3 启动类上添加@EnableRedisHttpSession 注解开启 spring-session-redis 整合方案的自动配置
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 1800) //开启redis session支持,并配置session过期时间
|
||||
public class SpringBootSessionApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootSessionApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、验证分布式session
|
||||
|
||||
#### 3.1 创建测试controller和测试页面
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@RequestMapping
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@RequestMapping("home")
|
||||
public String home() {
|
||||
return "home";
|
||||
}
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(User user, HttpSession session) {
|
||||
// 随机生成用户id
|
||||
user.setUserId(Math.round(Math.floor(Math.random() * 10 * 1000)));
|
||||
// 将用户信息保存到id中
|
||||
session.setAttribute("USER", user);
|
||||
return "home";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
登录页面index.ftl:
|
||||
|
||||
```jsp
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>登录页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="post">
|
||||
用户:<input type="text" name="username"><br/>
|
||||
密码:<input type="password" name="password"><br/>
|
||||
<button type="submit">登录</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
session 信息展示页面home.ftl:
|
||||
|
||||
```jsp
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>主页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<h5>登录用户: ${Session["USER"].username} </h5>
|
||||
<h5>用户编号: ${Session["USER"].userId} </h5>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 3.2 启动项目
|
||||
|
||||
由于我们这里采用的是spring boot 的内置容器作为web容器,所以直接启动两个实例测试即可。
|
||||
|
||||
应用1启动配置:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-session-app1.png"/> </div>
|
||||
|
||||
应用2启动配置,需要用 `--server.port `指定不同的端口号:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-session-app2.png"/> </div>
|
||||
|
||||
**测试结果:**
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-session-8080.png"/> </div>
|
||||
|
||||
## 一、项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-session.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、分布式session的配置
|
||||
|
||||
#### 2.1 引入依赖
|
||||
|
||||
```xml
|
||||
<!--分布式 session 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 2.2 Redis配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
jedis:
|
||||
pool:
|
||||
# 连接池最大连接数,使用负值表示无限制。
|
||||
max-active: 8
|
||||
# 连接池最大阻塞等待时间,使用负值表示无限制。
|
||||
max-wait: -1s
|
||||
# 连接池最大空闲数,使用负值表示无限制。
|
||||
max-idle: 8
|
||||
# 连接池最小空闲连接,只有设置为正值时候才有效
|
||||
min-idle: 1
|
||||
timeout: 300ms
|
||||
session:
|
||||
# session 存储方式 支持 redis、mongo、jdbc、hazelcast
|
||||
store-type: redis
|
||||
|
||||
# 如果是集群节点 采用如下配置指定节点
|
||||
#spring.redis.cluster.nodes
|
||||
|
||||
```
|
||||
|
||||
有两点需要特别说明:
|
||||
|
||||
1. spring-session 不仅提供了 redis 作为公共 session 存储的方案,同时也支持 jdbc、mongodb、Hazelcast 等作为公共 session 的存储,可以用 session.store-type 指定;
|
||||
2. 对于 redis 存储方案而言,官方也提供了不止一种整合方式,这里我们选取的整合方案是 jedis 客户端作为连接,当然也可以使用 Lettuce 作为客户端连接。
|
||||
|
||||
#### 2.3 启动类上添加@EnableRedisHttpSession 注解开启 spring-session-redis 整合方案的自动配置
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 1800) //开启 redis session 支持,并配置 session 过期时间
|
||||
public class SpringBootSessionApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootSessionApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、验证分布式session
|
||||
|
||||
#### 3.1 创建测试controller和测试页面
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@RequestMapping
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@RequestMapping("home")
|
||||
public String home() {
|
||||
return "home";
|
||||
}
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(User user, HttpSession session) {
|
||||
// 随机生成用户 id
|
||||
user.setUserId(Math.round(Math.floor(Math.random() * 10 * 1000)));
|
||||
// 将用户信息保存到 id 中
|
||||
session.setAttribute("USER", user);
|
||||
return "home";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
登录页面 index.ftl:
|
||||
|
||||
```jsp
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>登录页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="post">
|
||||
用户:<input type="text" name="username"><br/>
|
||||
密码:<input type="password" name="password"><br/>
|
||||
<button type="submit">登录</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
session 信息展示页面 home.ftl:
|
||||
|
||||
```jsp
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>主页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<h5>登录用户: ${Session["USER"].username} </h5>
|
||||
<h5>用户编号: ${Session["USER"].userId} </h5>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 3.2 启动项目
|
||||
|
||||
由于我们这里采用的是 spring boot 的内置容器作为 web 容器,所以直接启动两个实例测试即可。
|
||||
|
||||
应用 1 启动配置:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-session-app1.png"/> </div>
|
||||
|
||||
应用 2 启动配置,需要用 `--server.port ` 指定不同的端口号:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-session-app2.png"/> </div>
|
||||
|
||||
**测试结果:**
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-session-8080.png"/> </div>
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-session-8090.png"/> </div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring session 实现分布式 session
|
||||
|
||||
# spring session 实现分布式 session
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一项目结构">一、项目结构</a><br/>
|
||||
<a href="#二分布式session的配置">二、分布式session的配置</a><br/>
|
||||
<a href="#21-引入依赖">2.1 引入依赖</a><br/>
|
||||
@@ -10,190 +11,190 @@
|
||||
<a href="#32-启动项目">3.2 启动项目</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
## 一、项目结构
|
||||
|
||||
分布式session 主要配置文件为spring-session.xml和web.xml,其他的配置为标准的web工程的配置。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-session.png"/> </div>
|
||||
|
||||
## 二、分布式session的配置
|
||||
|
||||
#### 2.1 引入依赖
|
||||
|
||||
```xml
|
||||
<!--分布式 session 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 2.2 在web.xml中配置session拦截器
|
||||
|
||||
```xml
|
||||
<!--配置http session-->
|
||||
<filter>
|
||||
<filter-name>springSessionRepositoryFilter</filter-name>
|
||||
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>springSessionRepositoryFilter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 创建配置文件spring- session.xml,配置redis连接
|
||||
|
||||
有两点需要特别说明:
|
||||
|
||||
1. spring-session 不仅提供了redis作为公共session存储的方案,同时也支持jdbc、mongodb、Hazelcast等作为公共session的存储;
|
||||
2. 对于redis 存储方案而言,官方也提供了不止一种整合方式,这里我们选取的整合方案是jedis客户端作为连接,当然也可以使用Lettuce作为客户端连接。
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
|
||||
|
||||
<context:property-placeholder location="classpath:redis.properties"/>
|
||||
|
||||
|
||||
<!--配置 http session-->
|
||||
<bean id="redisHttpSessionConfiguration"
|
||||
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
|
||||
<!--session 有效期 单位秒 每次访问都会刷新有效期-->
|
||||
<property name="maxInactiveIntervalInSeconds" value="1800"/>
|
||||
</bean>
|
||||
|
||||
<!--单机版本配置redis 配置-->
|
||||
<bean id="redisStandaloneConfiguration"
|
||||
class="org.springframework.data.redis.connection.RedisStandaloneConfiguration">
|
||||
<constructor-arg name="hostName" value="${redis.host}"/>
|
||||
<constructor-arg name="port" value="${redis.port}"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
|
||||
p:usePool="true">
|
||||
<!--单机版本配置-->
|
||||
<constructor-arg name="standaloneConfig" ref="redisStandaloneConfiguration"/>
|
||||
<!--集群配置-->
|
||||
<!--<constructor-arg name="clusterConfig" ref="redisClusterConfiguration"/>-->
|
||||
</bean>
|
||||
|
||||
|
||||
<!--集群配置-->
|
||||
<!--<bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
|
||||
<property name="maxRedirects" value="3"/>
|
||||
<constructor-arg>
|
||||
<set>
|
||||
<value>127.0.0.1:6379</value>
|
||||
<value>127.0.0.1:6380</value>
|
||||
<value>127.0.0.1:6381</value>
|
||||
</set>
|
||||
</constructor-arg>
|
||||
</bean>-->
|
||||
|
||||
|
||||
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
|
||||
p:connection-factory-ref="jedisConnectionFactory"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
## 三、验证分布式session
|
||||
|
||||
#### 3.1 创建测试controller和测试页面
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@RequestMapping
|
||||
public String index(){
|
||||
return "index";
|
||||
}
|
||||
|
||||
@RequestMapping("home")
|
||||
public String home(){
|
||||
return "home";
|
||||
}
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(User user, HttpSession session, HttpServletRequest request, Model model){
|
||||
// 随机生成用户id
|
||||
user.setUserId(Math.round(Math.floor(Math.random() *10*1000)));
|
||||
// 将用户信息保存到id中
|
||||
session.setAttribute("USER",user);
|
||||
return "redirect:home";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
登录页面:
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>登录页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<h5>服务器:<%=request.getServerName()+":"+request.getServerPort()%></h5>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
用户:<input type="text" name="username"><br/>
|
||||
密码:<input type="password" name="password"><br/>
|
||||
<button type="submit">登录</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
session 信息展示页面(home.jsp):
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>主页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<h5>服务器:<%=request.getServerName()+":"+request.getServerPort()%></h5>
|
||||
<h5>登录用户: ${sessionScope.USER.username} </h5>
|
||||
<h5>用户编号: ${sessionScope.USER.userId} </h5>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 3.2 启动项目
|
||||
|
||||
这里我们采用两个tomcat分别启动项目,在第一个项目index.jsp页面进行登录,第二个项目不登录,直接访问session展示页(home.jsp)
|
||||
|
||||
tomcat 1 配置:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-session-tomcat01.png"/> </div>
|
||||
|
||||
tomcat 2 配置:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-session-tomcat02.png"/> </div>
|
||||
|
||||
**测试结果:**
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-session-8080.png"/> </div>
|
||||
|
||||
## 一、项目结构
|
||||
|
||||
分布式 session 主要配置文件为 spring-session.xml 和 web.xml,其他的配置为标准的 web 工程的配置。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-session.png"/> </div>
|
||||
|
||||
## 二、分布式session的配置
|
||||
|
||||
#### 2.1 引入依赖
|
||||
|
||||
```xml
|
||||
<!--分布式 session 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 2.2 在web.xml中配置session拦截器
|
||||
|
||||
```xml
|
||||
<!--配置http session-->
|
||||
<filter>
|
||||
<filter-name>springSessionRepositoryFilter</filter-name>
|
||||
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>springSessionRepositoryFilter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 创建配置文件spring- session.xml,配置redis连接
|
||||
|
||||
有两点需要特别说明:
|
||||
|
||||
1. spring-session 不仅提供了 redis 作为公共 session 存储的方案,同时也支持 jdbc、mongodb、Hazelcast 等作为公共 session 的存储;
|
||||
2. 对于 redis 存储方案而言,官方也提供了不止一种整合方式,这里我们选取的整合方案是 jedis 客户端作为连接,当然也可以使用 Lettuce 作为客户端连接。
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
|
||||
|
||||
<context:property-placeholder location="classpath:redis.properties"/>
|
||||
|
||||
|
||||
<!--配置 http session-->
|
||||
<bean id="redisHttpSessionConfiguration"
|
||||
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
|
||||
<!--session 有效期 单位秒 每次访问都会刷新有效期-->
|
||||
<property name="maxInactiveIntervalInSeconds" value="1800"/>
|
||||
</bean>
|
||||
|
||||
<!--单机版本配置 redis 配置-->
|
||||
<bean id="redisStandaloneConfiguration"
|
||||
class="org.springframework.data.redis.connection.RedisStandaloneConfiguration">
|
||||
<constructor-arg name="hostName" value="${redis.host}"/>
|
||||
<constructor-arg name="port" value="${redis.port}"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
|
||||
p:usePool="true">
|
||||
<!--单机版本配置-->
|
||||
<constructor-arg name="standaloneConfig" ref="redisStandaloneConfiguration"/>
|
||||
<!--集群配置-->
|
||||
<!--<constructor-arg name="clusterConfig" ref="redisClusterConfiguration"/>-->
|
||||
</bean>
|
||||
|
||||
|
||||
<!--集群配置-->
|
||||
<!--<bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
|
||||
<property name="maxRedirects" value="3"/>
|
||||
<constructor-arg>
|
||||
<set>
|
||||
<value>127.0.0.1:6379</value>
|
||||
<value>127.0.0.1:6380</value>
|
||||
<value>127.0.0.1:6381</value>
|
||||
</set>
|
||||
</constructor-arg>
|
||||
</bean>-->
|
||||
|
||||
|
||||
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
|
||||
p:connection-factory-ref="jedisConnectionFactory"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
## 三、验证分布式session
|
||||
|
||||
#### 3.1 创建测试controller和测试页面
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@RequestMapping
|
||||
public String index(){
|
||||
return "index";
|
||||
}
|
||||
|
||||
@RequestMapping("home")
|
||||
public String home(){
|
||||
return "home";
|
||||
}
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(User user, HttpSession session, HttpServletRequest request, Model model){
|
||||
// 随机生成用户 id
|
||||
user.setUserId(Math.round(Math.floor(Math.random() *10*1000)));
|
||||
// 将用户信息保存到 id 中
|
||||
session.setAttribute("USER",user);
|
||||
return "redirect:home";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
登录页面:
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>登录页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<h5>服务器:<%=request.getServerName()+":"+request.getServerPort()%></h5>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
用户:<input type="text" name="username"><br/>
|
||||
密码:<input type="password" name="password"><br/>
|
||||
<button type="submit">登录</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
session 信息展示页面 (home.jsp):
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>主页面</title>
|
||||
</head>
|
||||
<body>
|
||||
<h5>服务器:<%=request.getServerName()+":"+request.getServerPort()%></h5>
|
||||
<h5>登录用户: ${sessionScope.USER.username} </h5>
|
||||
<h5>用户编号: ${sessionScope.USER.userId} </h5>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 3.2 启动项目
|
||||
|
||||
这里我们采用两个 tomcat 分别启动项目,在第一个项目 index.jsp 页面进行登录,第二个项目不登录,直接访问 session 展示页(home.jsp)
|
||||
|
||||
tomcat 1 配置:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-session-tomcat01.png"/> </div>
|
||||
|
||||
tomcat 2 配置:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-session-tomcat02.png"/> </div>
|
||||
|
||||
**测试结果:**
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-session-8080.png"/> </div>
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-session-8090.png"/> </div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot actuator
|
||||
|
||||
# spring boot actuator
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一用例涉及到的概念综述">一、用例涉及到的概念综述</a><br/>
|
||||
<a href="#11-端点">1.1 端点</a><br/>
|
||||
<a href="#12-启用端点">1.2 启用端点</a><br/>
|
||||
@@ -20,315 +21,315 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、用例涉及到的概念综述
|
||||
|
||||
### 1.1 端点
|
||||
|
||||
执行器端点(endpoints)可用于监控应用及与应用进行交互,Spring Boot包含很多内置的端点,你也可以添加自己的。例如,health端点提供了应用的基本健康信息。 端点暴露的方式取决于你采用的技术类型,大部分应用选择HTTP监控,端点的ID映射到一个URL。例如,health端点默认映射到/health。
|
||||
|
||||
下面的端点都是可用的:
|
||||
|
||||
| ID | 描述 | 是否敏感 |
|
||||
| ----------- | ------------------------------------------------------------ | -------- |
|
||||
| actuator | 为其他端点提供基于超文本的导航页面,需要添加Spring HATEOAS依赖 | true |
|
||||
| autoconfig | 显示一个自动配置类的报告,该报告展示所有自动配置候选者及它们被应用或未被应用的原因 | true |
|
||||
| beans | 显示一个应用中所有Spring Beans的完整列表 | true |
|
||||
| configprops | 显示一个所有@ConfigurationProperties的集合列表 | true |
|
||||
| dump | 执行一个线程转储 | true |
|
||||
| env | 暴露来自Spring ConfigurableEnvironment的属性 | true |
|
||||
| flyway | 显示数据库迁移路径,如果有的话 | true |
|
||||
| health | 展示应用的健康信息(当使用一个未认证连接访问时显示一个简单的'status',使用认证连接访问则显示全部信息详情) | false |
|
||||
| info | 显示任意的应用信息 | false |
|
||||
| liquibase | 展示任何Liquibase数据库迁移路径,如果有的话 | true |
|
||||
| metrics | 展示当前应用的'metrics'信息 | true |
|
||||
| mappings | 显示一个所有@RequestMapping路径的集合列表 | true |
|
||||
| shutdown | 允许应用以优雅的方式关闭(默认情况下不启用) | true |
|
||||
| trace | 显示trace信息(默认为最新的100条HTTP请求) | true |
|
||||
|
||||
如果使用Spring MVC,你还可以使用以下端点:
|
||||
|
||||
| ID | 描述 | 是否敏感 |
|
||||
| -------- | ------------------------------------------------------------ | -------- |
|
||||
| docs | 展示Actuator的文档,包括示例请求和响应,需添加spring-boot-actuator-docs依赖 | false |
|
||||
| heapdump | 返回一个GZip压缩的hprof堆转储文件 | true |
|
||||
| jolokia | 通过HTTP暴露JMX beans(依赖Jolokia) | true |
|
||||
| logfile | 返回日志文件内容(如果设置logging.file或logging.path属性),支持使用HTTP Range头接收日志文件内容的部分信息 | |
|
||||
|
||||
注:根据端点暴露的方式,sensitive属性可用做安全提示,例如,在使用HTTP访问敏感(sensitive)端点时需要提供用户名/密码(如果没有启用web安全,可能会简化为禁止访问该端点)。
|
||||
|
||||
|
||||
|
||||
### 1.2 启用端点
|
||||
|
||||
默认情况下,除了以外的所有端点shutdown都已启用。要配置端点的启用,请使用其management.endpoint.<id>.enabled属性。以下示例启用shutdown端点:
|
||||
|
||||
```properties
|
||||
management.endpoint.shutdown.enabled = true
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 1.3 暴露端点
|
||||
|
||||
由于端点可能包含敏感信息,因此应仔细考虑何时公开它们。下表显示了内置端点的默认曝光情况:
|
||||
|
||||
| ID | JMX | Web |
|
||||
| -------------- | ----- | ---- |
|
||||
| auditevents | 是 | 没有 |
|
||||
| beans | 是 | 没有 |
|
||||
| conditions | 是 | 没有 |
|
||||
| configprops | 是 | 没有 |
|
||||
| env | 是 | 没有 |
|
||||
| flyway | 是 | 没有 |
|
||||
| health | 是 | 是 |
|
||||
| heapdump | N / A | 没有 |
|
||||
| httptrace | 是 | 没有 |
|
||||
| info | 是 | 是 |
|
||||
| jolokia | N / A | 没有 |
|
||||
| logfile | N / A | 没有 |
|
||||
| loggers | 是 | 没有 |
|
||||
| liquibase | 是 | 没有 |
|
||||
| metrics | 是 | 没有 |
|
||||
| mappings | 是 | 没有 |
|
||||
| prometheus | N / A | 没有 |
|
||||
| scheduledtasks | 是 | 没有 |
|
||||
| sessions | 是 | 没有 |
|
||||
| shutdown | 是 | 没有 |
|
||||
| threaddump | 是 | 没有 |
|
||||
|
||||
**可以选择是否暴露端点(include)或者排除端点(exclude),其中排除优先于暴露:**
|
||||
|
||||
| 属性 | 默认 |
|
||||
| ----------------------------------------- | ------------ |
|
||||
| management.endpoints.jmx.exposure.exclude | |
|
||||
| management.endpoints.jmx.exposure.include | * |
|
||||
| management.endpoints.web.exposure.exclude | |
|
||||
| management.endpoints.web.exposure.include | info, health |
|
||||
|
||||
|
||||
|
||||
### 1.4 健康检查信息
|
||||
|
||||
您可以使用健康信息来检查正在运行的应用程序的状态。health端点公开的信息取决于management.endpoint.health.show-details可以使用以下值之一配置的属性:
|
||||
|
||||
| 名称 | 描述 |
|
||||
| --------------- | ------------------------------------------------------------ |
|
||||
| never | 细节永远不会显示。 |
|
||||
| when-authorized | 详细信息仅向授权用户显示。授权角色可以使用配置 management.endpoint.health.roles。 |
|
||||
| always | 详细信息显示给所有用户。 |
|
||||
|
||||
|
||||
|
||||
## 二、项目说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
1. CustomHealthIndicator 自定义健康指标;
|
||||
2. CustomHealthAggregator:自定义健康聚合规则;
|
||||
3. CustomEndPoint:自定义端点。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-actuator.png"/> </div>
|
||||
|
||||
#### 1.2 主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 1.3 项目配置
|
||||
|
||||
```yaml
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
# 这里用* 代表暴露所有端点只是为了观察效果,实际中按照需进行端点暴露
|
||||
include: "*"
|
||||
endpoint:
|
||||
health:
|
||||
# 详细信息显示给所有用户。
|
||||
show-details: always
|
||||
health:
|
||||
status:
|
||||
http-mapping:
|
||||
# 自定义健康检查返回状态码对应的http状态码
|
||||
FATAL: 503
|
||||
```
|
||||
|
||||
#### 1.4 查看监控状态
|
||||
|
||||
导入actuator 的 start 并进行配置后,访问http://127.0.0.1:8080/actuator/health 就可以看到对应的项目监控状态。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health.png"/> </div>
|
||||
|
||||
需要注意的是这里的监控状态根据实际项目所用到的技术不同而不同。因为以下HealthIndicators情况在适当时由Spring Boot自动配置的:
|
||||
|
||||
| 名称 | 描述 |
|
||||
| ------------------------------------------------------------ | -------------------------------- |
|
||||
| [CassandraHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraHealthIndicator.java) | 检查Cassandra数据库是否启动。 |
|
||||
| [DiskSpaceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java) | 检查磁盘空间不足。 |
|
||||
| [DataSourceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java) | 检查是否可以获得连接DataSource。 |
|
||||
| [ElasticsearchHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.java) | 检查Elasticsearch集群是否启动。 |
|
||||
| [InfluxDbHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java) | 检查InfluxDB服务器是否启动。 |
|
||||
| [JmsHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jms/JmsHealthIndicator.java) | 检查JMS代理是否启动。 |
|
||||
| [MailHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java) | 检查邮件服务器是否启动。 |
|
||||
| [MongoHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mongo/MongoHealthIndicator.java) | 检查Mongo数据库是否启动。 |
|
||||
| [Neo4jHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java) | 检查Neo4j服务器是否启动。 |
|
||||
| [RabbitHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/amqp/RabbitHealthIndicator.java) | 检查Rabbit服务器是否启动。 |
|
||||
| [RedisHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisHealthIndicator.java) | 检查Redis服务器是否启动。 |
|
||||
| [SolrHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java) | 检查Solr服务器是否已启动。 |
|
||||
|
||||
|
||||
|
||||
## 三、自定义健康检查指标
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义健康检查指标
|
||||
*/
|
||||
@Component
|
||||
public class CustomHealthIndicator implements HealthIndicator {
|
||||
|
||||
@Override
|
||||
public Health health() {
|
||||
double random = Math.random();
|
||||
// 这里用随机数模拟健康检查的结果
|
||||
if (random > 0.5) {
|
||||
return Health.status("FATAL").withDetail("error code", "某健康专项检查失败").build();
|
||||
} else {
|
||||
return Health.up().withDetail("success code", "自定义检查一切正常").build();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
自定义检查通过的情况下:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-health-up.png"/> </div>
|
||||
|
||||
自定义检查失败的情况:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health-fatal-200.png"/> </div>
|
||||
|
||||
|
||||
|
||||
这里我们可以看到自定义检查不论是否通过都不会影响整体的status,两种情况下都是status都是“up”。如果我们想通过自定义的检查检查去影响最终的检查结果,比如我们健康检查针对的是支付业务,在支付业务的不可用的情况下,我们就认为整个服务是不可用的。这个时候就需要实现自定义实现健康状态的聚合。
|
||||
|
||||
|
||||
|
||||
## 四、自定义健康状态聚合规则
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 对所有的自定义健康指标进行聚合,按照自定义规则返回总和健康状态
|
||||
*/
|
||||
@Component
|
||||
public class CustomHealthAggregator implements HealthAggregator {
|
||||
|
||||
@Override
|
||||
public Health aggregate(Map<String, Health> healths) {
|
||||
for (Health health : healths.values()) {
|
||||
// 聚合规则可以自定义,这里假设我们自定义的监控状态中有一项FATAL,就认为整个服务都是不可用的,否则认为整个服务是可用的
|
||||
if (health.getStatus().getCode().equals("FATAL")) {
|
||||
return Health.status("FATAL").withDetail("error code", "综合判断后服务宕机").build();
|
||||
}
|
||||
}
|
||||
return Health.up().build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当我们自定义健康检查不通过时候的结果如下:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-heath-503.png"/> </div>
|
||||
|
||||
这里需要注意的是返回我们自定义的聚合状态的时候,状态码也变成了503,这是我们在配置文件中进行定义的:
|
||||
|
||||
```properties
|
||||
management.health.status.http-mapping.FATAL = 503
|
||||
```
|
||||
|
||||
下表显示了内置状态的默认状态映射:
|
||||
|
||||
| Status | Mapping |
|
||||
| -------------- | -------------------------------------------- |
|
||||
| DOWN | SERVICE_UNAVAILABLE (503) |
|
||||
| OUT_OF_SERVICE | SERVICE_UNAVAILABLE (503) |
|
||||
| UP | No mapping by default, so http status is 200 |
|
||||
| UNKNOWN | No mapping by default, so http status is 200 |
|
||||
|
||||
|
||||
|
||||
## 五、@Endpoint自定义端点
|
||||
|
||||
#### 5.1 自定义端点
|
||||
|
||||
spring boot 支持使用@Endpoint来自定义端点暴露应用信息。这里我们采用第三方sigar来暴露服务所在硬件的监控信息。
|
||||
|
||||
Sigar是Hyperic-hq产品的基础包,是Hyperic HQ主要的数据收集组件。Sigar.jar的底层是用C语言编写的,它通过本地方法来调用操作系统API来获取系统相关数据 [jar包下载地址](https://sourceforge.net/projects/sigar/)。
|
||||
|
||||
Sigar为不同平台提供了不同的库文件,下载后需要将库文件放到服务所在主机的对应位置:
|
||||
|
||||
- Windows下配置:根据自己的操作系统版本选择sigar-amd64-winnt.dll或sigar-x86-winnt.dll拷贝到C:\Windows\System32中
|
||||
|
||||
- Linux下配置:将libsigar-amd64-linux.so或libsigar-x86-linux.so拷贝到/usr/lib64或/lib64或/lib或/usr/lib目录下,如果不起作用,还需要sudochmod 744修改libsigar-amd64-linux.so文件权限
|
||||
|
||||
```java
|
||||
@Endpoint(id = "customEndPoint")
|
||||
@Component
|
||||
public class CustomEndPoint {
|
||||
|
||||
@ReadOperation
|
||||
public Map<String, Object> getCupInfo() throws SigarException {
|
||||
|
||||
Map<String, Object> cupInfoMap = new LinkedHashMap<>();
|
||||
|
||||
Sigar sigar = new Sigar();
|
||||
|
||||
CpuInfo infoList[] = sigar.getCpuInfoList();
|
||||
CpuPerc[] cpuList = sigar.getCpuPercList();
|
||||
|
||||
for (int i = 0; i < infoList.length; i++) {
|
||||
CpuInfo info = infoList[i];
|
||||
cupInfoMap.put("CPU " + i + " 的总量MHz", info.getMhz()); // CPU的总量MHz
|
||||
cupInfoMap.put("CPU " + i + " 生产商", info.getVendor()); // 获得CPU的生产商,如:Intel
|
||||
cupInfoMap.put("CPU " + i + " 类别", info.getModel()); // 获得CPU的类别,如:Core
|
||||
cupInfoMap.put("CPU " + i + " 缓存数量", info.getCacheSize()); // 缓冲存储器数量
|
||||
cupInfoMap.put("CPU " + i + " 用户使用率", CpuPerc.format(cpuList[i].getUser())); // 用户使用率
|
||||
cupInfoMap.put("CPU " + i + " 系统使用率", CpuPerc.format(cpuList[i].getSys())); // 系统使用率
|
||||
cupInfoMap.put("CPU " + i + " 当前等待率", CpuPerc.format(cpuList[i].getWait())); // 当前等待率
|
||||
cupInfoMap.put("CPU " + i + " 当前错误率", CpuPerc.format(cpuList[i].getNice())); // 当前错误率
|
||||
cupInfoMap.put("CPU " + i + " 当前空闲率", CpuPerc.format(cpuList[i].getIdle())); // 当前空闲率
|
||||
cupInfoMap.put("CPU " + i + " 总的使用率", CpuPerc.format(cpuList[i].getCombined()));// 总的使用率
|
||||
}
|
||||
return cupInfoMap;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
其中可用的方法注解由http操作决定:
|
||||
|
||||
| operation | HTTP方法 |
|
||||
| ---------------- | -------- |
|
||||
| @ReadOperation | GET |
|
||||
| @WriteOperation | POST |
|
||||
| @DeleteOperation | DELETE |
|
||||
|
||||
#### 5.2 访问自定义端点http://127.0.0.1:8080/actuator/customEndPoint
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-customEndPoint.png"/> </div>
|
||||
|
||||
|
||||
|
||||
关于 Sigar 的 更多监控参数可以参考博客:[java读取计算机CPU、内存等信息(Sigar使用)](https://blog.csdn.net/wudiazu/article/details/73829324)
|
||||
|
||||
Sigar 下载包中也提供了各种参数的参考用例:
|
||||
|
||||
## 一、用例涉及到的概念综述
|
||||
|
||||
### 1.1 端点
|
||||
|
||||
执行器端点(endpoints)可用于监控应用及与应用进行交互,Spring Boot 包含很多内置的端点,你也可以添加自己的。例如,health 端点提供了应用的基本健康信息。 端点暴露的方式取决于你采用的技术类型,大部分应用选择 HTTP 监控,端点的 ID 映射到一个 URL。例如,health 端点默认映射到/health。
|
||||
|
||||
下面的端点都是可用的:
|
||||
|
||||
| ID | 描述 | 是否敏感 |
|
||||
| ----------- | ------------------------------------------------------------ | -------- |
|
||||
| actuator | 为其他端点提供基于超文本的导航页面,需要添加 Spring HATEOAS 依赖 | true |
|
||||
| autoconfig | 显示一个自动配置类的报告,该报告展示所有自动配置候选者及它们被应用或未被应用的原因 | true |
|
||||
| beans | 显示一个应用中所有 Spring Beans 的完整列表 | true |
|
||||
| configprops | 显示一个所有@ConfigurationProperties 的集合列表 | true |
|
||||
| dump | 执行一个线程转储 | true |
|
||||
| env | 暴露来自 Spring ConfigurableEnvironment 的属性 | true |
|
||||
| flyway | 显示数据库迁移路径,如果有的话 | true |
|
||||
| health | 展示应用的健康信息(当使用一个未认证连接访问时显示一个简单的'status',使用认证连接访问则显示全部信息详情) | false |
|
||||
| info | 显示任意的应用信息 | false |
|
||||
| liquibase | 展示任何 Liquibase 数据库迁移路径,如果有的话 | true |
|
||||
| metrics | 展示当前应用的'metrics'信息 | true |
|
||||
| mappings | 显示一个所有@RequestMapping 路径的集合列表 | true |
|
||||
| shutdown | 允许应用以优雅的方式关闭(默认情况下不启用) | true |
|
||||
| trace | 显示 trace 信息(默认为最新的 100 条 HTTP 请求) | true |
|
||||
|
||||
如果使用 Spring MVC,你还可以使用以下端点:
|
||||
|
||||
| ID | 描述 | 是否敏感 |
|
||||
| -------- | ------------------------------------------------------------ | -------- |
|
||||
| docs | 展示 Actuator 的文档,包括示例请求和响应,需添加 spring-boot-actuator-docs 依赖 | false |
|
||||
| heapdump | 返回一个 GZip 压缩的 hprof 堆转储文件 | true |
|
||||
| jolokia | 通过 HTTP 暴露 JMX beans(依赖 Jolokia) | true |
|
||||
| logfile | 返回日志文件内容(如果设置 logging.file 或 logging.path 属性),支持使用 HTTP Range 头接收日志文件内容的部分信息 | |
|
||||
|
||||
注:根据端点暴露的方式,sensitive 属性可用做安全提示,例如,在使用 HTTP 访问敏感(sensitive)端点时需要提供用户名/密码(如果没有启用 web 安全,可能会简化为禁止访问该端点)。
|
||||
|
||||
|
||||
|
||||
### 1.2 启用端点
|
||||
|
||||
默认情况下,除了以外的所有端点 shutdown 都已启用。要配置端点的启用,请使用其 management.endpoint.<id>.enabled 属性。以下示例启用 shutdown 端点:
|
||||
|
||||
```properties
|
||||
management.endpoint.shutdown.enabled = true
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 1.3 暴露端点
|
||||
|
||||
由于端点可能包含敏感信息,因此应仔细考虑何时公开它们。下表显示了内置端点的默认曝光情况:
|
||||
|
||||
| ID | JMX | Web |
|
||||
| -------------- | ----- | ---- |
|
||||
| auditevents | 是 | 没有 |
|
||||
| beans | 是 | 没有 |
|
||||
| conditions | 是 | 没有 |
|
||||
| configprops | 是 | 没有 |
|
||||
| env | 是 | 没有 |
|
||||
| flyway | 是 | 没有 |
|
||||
| health | 是 | 是 |
|
||||
| heapdump | N / A | 没有 |
|
||||
| httptrace | 是 | 没有 |
|
||||
| info | 是 | 是 |
|
||||
| jolokia | N / A | 没有 |
|
||||
| logfile | N / A | 没有 |
|
||||
| loggers | 是 | 没有 |
|
||||
| liquibase | 是 | 没有 |
|
||||
| metrics | 是 | 没有 |
|
||||
| mappings | 是 | 没有 |
|
||||
| prometheus | N / A | 没有 |
|
||||
| scheduledtasks | 是 | 没有 |
|
||||
| sessions | 是 | 没有 |
|
||||
| shutdown | 是 | 没有 |
|
||||
| threaddump | 是 | 没有 |
|
||||
|
||||
**可以选择是否暴露端点(include)或者排除端点(exclude),其中排除优先于暴露:**
|
||||
|
||||
| 属性 | 默认 |
|
||||
| ----------------------------------------- | ------------ |
|
||||
| management.endpoints.jmx.exposure.exclude | |
|
||||
| management.endpoints.jmx.exposure.include | * |
|
||||
| management.endpoints.web.exposure.exclude | |
|
||||
| management.endpoints.web.exposure.include | info, health |
|
||||
|
||||
|
||||
|
||||
### 1.4 健康检查信息
|
||||
|
||||
您可以使用健康信息来检查正在运行的应用程序的状态。health 端点公开的信息取决于 management.endpoint.health.show-details 可以使用以下值之一配置的属性:
|
||||
|
||||
| 名称 | 描述 |
|
||||
| --------------- | ------------------------------------------------------------ |
|
||||
| never | 细节永远不会显示。 |
|
||||
| when-authorized | 详细信息仅向授权用户显示。授权角色可以使用配置 management.endpoint.health.roles。 |
|
||||
| always | 详细信息显示给所有用户。 |
|
||||
|
||||
|
||||
|
||||
## 二、项目说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
1. CustomHealthIndicator 自定义健康指标;
|
||||
2. CustomHealthAggregator:自定义健康聚合规则;
|
||||
3. CustomEndPoint:自定义端点。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-actuator.png"/> </div>
|
||||
|
||||
#### 1.2 主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 1.3 项目配置
|
||||
|
||||
```yaml
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
# 这里用* 代表暴露所有端点只是为了观察效果,实际中按照需进行端点暴露
|
||||
include: "*"
|
||||
endpoint:
|
||||
health:
|
||||
# 详细信息显示给所有用户。
|
||||
show-details: always
|
||||
health:
|
||||
status:
|
||||
http-mapping:
|
||||
# 自定义健康检查返回状态码对应的 http 状态码
|
||||
FATAL: 503
|
||||
```
|
||||
|
||||
#### 1.4 查看监控状态
|
||||
|
||||
导入 actuator 的 start 并进行配置后,访问 http://127.0.0.1:8080/actuator/health 就可以看到对应的项目监控状态。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health.png"/> </div>
|
||||
|
||||
需要注意的是这里的监控状态根据实际项目所用到的技术不同而不同。因为以下 HealthIndicators 情况在适当时由 Spring Boot 自动配置的:
|
||||
|
||||
| 名称 | 描述 |
|
||||
| ------------------------------------------------------------ | -------------------------------- |
|
||||
| [CassandraHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cassandra/CassandraHealthIndicator.java) | 检查 Cassandra 数据库是否启动。 |
|
||||
| [DiskSpaceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/DiskSpaceHealthIndicator.java) | 检查磁盘空间不足。 |
|
||||
| [DataSourceHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jdbc/DataSourceHealthIndicator.java) | 检查是否可以获得连接 DataSource。 |
|
||||
| [ElasticsearchHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.java) | 检查 Elasticsearch 集群是否启动。 |
|
||||
| [InfluxDbHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/influx/InfluxDbHealthIndicator.java) | 检查 InfluxDB 服务器是否启动。 |
|
||||
| [JmsHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/jms/JmsHealthIndicator.java) | 检查 JMS 代理是否启动。 |
|
||||
| [MailHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mail/MailHealthIndicator.java) | 检查邮件服务器是否启动。 |
|
||||
| [MongoHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/mongo/MongoHealthIndicator.java) | 检查 Mongo 数据库是否启动。 |
|
||||
| [Neo4jHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/neo4j/Neo4jHealthIndicator.java) | 检查 Neo4j 服务器是否启动。 |
|
||||
| [RabbitHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/amqp/RabbitHealthIndicator.java) | 检查 Rabbit 服务器是否启动。 |
|
||||
| [RedisHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/redis/RedisHealthIndicator.java) | 检查 Redis 服务器是否启动。 |
|
||||
| [SolrHealthIndicator](https://github.com/spring-projects/spring-boot/tree/v2.0.1.RELEASE/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/solr/SolrHealthIndicator.java) | 检查 Solr 服务器是否已启动。 |
|
||||
|
||||
|
||||
|
||||
## 三、自定义健康检查指标
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义健康检查指标
|
||||
*/
|
||||
@Component
|
||||
public class CustomHealthIndicator implements HealthIndicator {
|
||||
|
||||
@Override
|
||||
public Health health() {
|
||||
double random = Math.random();
|
||||
// 这里用随机数模拟健康检查的结果
|
||||
if (random > 0.5) {
|
||||
return Health.status("FATAL").withDetail("error code", "某健康专项检查失败").build();
|
||||
} else {
|
||||
return Health.up().withDetail("success code", "自定义检查一切正常").build();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
自定义检查通过的情况下:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-health-up.png"/> </div>
|
||||
|
||||
自定义检查失败的情况:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/health-fatal-200.png"/> </div>
|
||||
|
||||
|
||||
|
||||
这里我们可以看到自定义检查不论是否通过都不会影响整体的 status,两种情况下都是 status 都是“up”。如果我们想通过自定义的检查检查去影响最终的检查结果,比如我们健康检查针对的是支付业务,在支付业务的不可用的情况下,我们就认为整个服务是不可用的。这个时候就需要实现自定义实现健康状态的聚合。
|
||||
|
||||
|
||||
|
||||
## 四、自定义健康状态聚合规则
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 对所有的自定义健康指标进行聚合,按照自定义规则返回总和健康状态
|
||||
*/
|
||||
@Component
|
||||
public class CustomHealthAggregator implements HealthAggregator {
|
||||
|
||||
@Override
|
||||
public Health aggregate(Map<String, Health> healths) {
|
||||
for (Health health : healths.values()) {
|
||||
// 聚合规则可以自定义,这里假设我们自定义的监控状态中有一项 FATAL,就认为整个服务都是不可用的,否则认为整个服务是可用的
|
||||
if (health.getStatus().getCode().equals("FATAL")) {
|
||||
return Health.status("FATAL").withDetail("error code", "综合判断后服务宕机").build();
|
||||
}
|
||||
}
|
||||
return Health.up().build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当我们自定义健康检查不通过时候的结果如下:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-heath-503.png"/> </div>
|
||||
|
||||
这里需要注意的是返回我们自定义的聚合状态的时候,状态码也变成了 503,这是我们在配置文件中进行定义的:
|
||||
|
||||
```properties
|
||||
management.health.status.http-mapping.FATAL = 503
|
||||
```
|
||||
|
||||
下表显示了内置状态的默认状态映射:
|
||||
|
||||
| Status | Mapping |
|
||||
| -------------- | -------------------------------------------- |
|
||||
| DOWN | SERVICE_UNAVAILABLE (503) |
|
||||
| OUT_OF_SERVICE | SERVICE_UNAVAILABLE (503) |
|
||||
| UP | No mapping by default, so http status is 200 |
|
||||
| UNKNOWN | No mapping by default, so http status is 200 |
|
||||
|
||||
|
||||
|
||||
## 五、@Endpoint自定义端点
|
||||
|
||||
#### 5.1 自定义端点
|
||||
|
||||
spring boot 支持使用@Endpoint 来自定义端点暴露应用信息。这里我们采用第三方 sigar 来暴露服务所在硬件的监控信息。
|
||||
|
||||
Sigar 是 Hyperic-hq 产品的基础包,是 Hyperic HQ 主要的数据收集组件。Sigar.jar 的底层是用 C 语言编写的,它通过本地方法来调用操作系统 API 来获取系统相关数据 [jar 包下载地址](https://sourceforge.net/projects/sigar/)。
|
||||
|
||||
Sigar 为不同平台提供了不同的库文件,下载后需要将库文件放到服务所在主机的对应位置:
|
||||
|
||||
- Windows 下配置:根据自己的操作系统版本选择 sigar-amd64-winnt.dll 或 sigar-x86-winnt.dll 拷贝到 C:\Windows\System32 中
|
||||
|
||||
- Linux 下配置:将 libsigar-amd64-linux.so 或 libsigar-x86-linux.so 拷贝到/usr/lib64 或/lib64 或/lib 或/usr/lib 目录下,如果不起作用,还需要 sudochmod 744 修改 libsigar-amd64-linux.so 文件权限
|
||||
|
||||
```java
|
||||
@Endpoint(id = "customEndPoint")
|
||||
@Component
|
||||
public class CustomEndPoint {
|
||||
|
||||
@ReadOperation
|
||||
public Map<String, Object> getCupInfo() throws SigarException {
|
||||
|
||||
Map<String, Object> cupInfoMap = new LinkedHashMap<>();
|
||||
|
||||
Sigar sigar = new Sigar();
|
||||
|
||||
CpuInfo infoList[] = sigar.getCpuInfoList();
|
||||
CpuPerc[] cpuList = sigar.getCpuPercList();
|
||||
|
||||
for (int i = 0; i < infoList.length; i++) {
|
||||
CpuInfo info = infoList[i];
|
||||
cupInfoMap.put("CPU " + i + " 的总量 MHz", info.getMhz()); // CPU 的总量 MHz
|
||||
cupInfoMap.put("CPU " + i + " 生产商", info.getVendor()); // 获得 CPU 的生产商,如:Intel
|
||||
cupInfoMap.put("CPU " + i + " 类别", info.getModel()); // 获得 CPU 的类别,如:Core
|
||||
cupInfoMap.put("CPU " + i + " 缓存数量", info.getCacheSize()); // 缓冲存储器数量
|
||||
cupInfoMap.put("CPU " + i + " 用户使用率", CpuPerc.format(cpuList[i].getUser())); // 用户使用率
|
||||
cupInfoMap.put("CPU " + i + " 系统使用率", CpuPerc.format(cpuList[i].getSys())); // 系统使用率
|
||||
cupInfoMap.put("CPU " + i + " 当前等待率", CpuPerc.format(cpuList[i].getWait())); // 当前等待率
|
||||
cupInfoMap.put("CPU " + i + " 当前错误率", CpuPerc.format(cpuList[i].getNice())); // 当前错误率
|
||||
cupInfoMap.put("CPU " + i + " 当前空闲率", CpuPerc.format(cpuList[i].getIdle())); // 当前空闲率
|
||||
cupInfoMap.put("CPU " + i + " 总的使用率", CpuPerc.format(cpuList[i].getCombined()));// 总的使用率
|
||||
}
|
||||
return cupInfoMap;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
其中可用的方法注解由 http 操作决定:
|
||||
|
||||
| operation | HTTP 方法 |
|
||||
| ---------------- | -------- |
|
||||
| @ReadOperation | GET |
|
||||
| @WriteOperation | POST |
|
||||
| @DeleteOperation | DELETE |
|
||||
|
||||
#### 5.2 访问自定义端点http://127.0.0.1:8080/actuator/customEndPoint
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/actuator-customEndPoint.png"/> </div>
|
||||
|
||||
|
||||
|
||||
关于 Sigar 的 更多监控参数可以参考博客:[java 读取计算机 CPU、内存等信息(Sigar 使用)](https://blog.csdn.net/wudiazu/article/details/73829324)
|
||||
|
||||
Sigar 下载包中也提供了各种参数的参考用例:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/sigar.png"/> </div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring-boot 基础
|
||||
|
||||
# spring-boot 基础
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-项目依赖">1.2 项目依赖</a><br/>
|
||||
@@ -14,248 +15,248 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
1. 本项目搭建一个简单的hello spring 的 web工程,简单说明spring-boot 的开箱即用的特性;
|
||||
2. 模板引擎采用freemaker 和 thymeleaf 作为示例,分别对应模板文件makershow.ftl 和 leafShow.html;
|
||||
3. spring boot 2.x 默认是不支持jsp的,需要额外的配置,关于使用jsp的整合可以参考[spring-boot-jsp](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-jsp)项目。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-base.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
导入相关的starter(启动器)
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-base</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-boot-base</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--模板引擎-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<!--web 启动器-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--lombok 插件-->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--测试相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!--Spring Boot的Maven插件(Spring Boot Maven plugin)能够以Maven的方式为应用提供Spring Boot的支持-->
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
```
|
||||
|
||||
1. spring boot 项目默认继承自spring-boot-starter-parent,而spring-boot-starter-parent继承自spring-boot-dependencies, spring-boot-dependencies中定义了关于spring boot 依赖的各种jar包的版本,是spring boot 的版本管理中心。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dependencies.png"/> </div>
|
||||
|
||||
2. 关于spring boot 2.x官方支持的所有starter 可以参见官方文档 [Table 13.1. Spring Boot application starters](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#using-boot-starter)
|
||||
|
||||
|
||||
|
||||
## 二、spring boot 主启动类
|
||||
|
||||
如果采用IDEA 或者 Spring Tool Suite (STS) 等开发工具创建的spring boot 工程,会默认创建启动类,如果没有创建,需要手动创建启动类
|
||||
|
||||
```java
|
||||
package com.heibaiying.springbootbase;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SpringBootBaseApplication {
|
||||
|
||||
// 启动类默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootBaseApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@SpringBootApplication 注解是一个复合注解,里面包含了@ComponentScan注解,默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
|
||||
|
||||
```java
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan(excludeFilters = {
|
||||
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
|
||||
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
|
||||
public @interface SpringBootApplication {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、开箱即用的web工程
|
||||
|
||||
在springbootBaseApplication.java 的同级目录创建controller文件夹,并在其中创建RestfulController.java,启动项目访问localhost:8080/restful/programmers 即可看到项目搭建成功。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : restful 控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("restful")
|
||||
public class RestfulController {
|
||||
|
||||
@GetMapping("programmers")
|
||||
private List<Programmer> getProgrammers() {
|
||||
List<Programmer> programmers = new ArrayList<>();
|
||||
programmers.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
|
||||
programmers.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
|
||||
return programmers;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里之所以能够开箱即用,是因为我们在项目中导入spring-boot-starter-web启动器,而@SpringBootApplication 复合注解中默认开启了@EnableAutoConfiguration注解允许开启自动化配置,spring在检查导入starter-web的依赖后就会开启web的自动化配置。
|
||||
|
||||
|
||||
|
||||
## 四、模板引擎
|
||||
|
||||
这里我们在一个项目中同时导入了freemaker 和 thymeleaf的starter(虽然并不推荐,但是在同一个项目中是可以混用这两种模板引擎的)。
|
||||
|
||||
#### 4.1 freemarker
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("freemarker")
|
||||
public class FreeMarkerController {
|
||||
|
||||
@RequestMapping("show")
|
||||
private String programmerShow(ModelMap modelMap){
|
||||
List<Programmer> programmerList=new ArrayList<>();
|
||||
programmerList.add(new Programmer("xiaoming",12,100000.00f,LocalDate.of(2019,Month.AUGUST,2)));
|
||||
programmerList.add(new Programmer("xiaohong",23,900000.00f,LocalDate.of(2013,Month.FEBRUARY,2)));
|
||||
modelMap.addAttribute("programmers",programmerList);
|
||||
return "markerShow";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>freemarker模板引擎</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<#list programmers as programmer>
|
||||
<li>姓名: ${programmer.name} 年龄: ${programmer.age}</li>
|
||||
</#list>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 4.2 thymeleaf
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 跳转渲染模板引擎 默认模板的存放位置为classpath:templates
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("thymeleaf")
|
||||
public class ThymeleafController {
|
||||
|
||||
@RequestMapping("show")
|
||||
private String programmerShow(ModelMap modelMap) {
|
||||
List<Programmer> programmerList = new ArrayList<>();
|
||||
programmerList.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
|
||||
programmerList.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
|
||||
modelMap.addAttribute("programmers", programmerList);
|
||||
return "leafShow";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>thymeleaf模板引擎</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul th:each="programmer:${programmers}">
|
||||
<li>
|
||||
姓名:<span th:text="${programmer.name}"></span>
|
||||
薪水:<span th:text="${programmer.salary}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 4.3 文档说明
|
||||
|
||||
freemarker:提供了完善的中文文档,地址 http://freemarker.foofun.cn/
|
||||
|
||||
thymeleaf:官方英文文档地址:[thymeleaf 3.0.11RELEASE](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf)
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
1. 本项目搭建一个简单的 hello spring 的 web 工程,简单说明 spring-boot 的开箱即用的特性;
|
||||
2. 模板引擎采用 freemaker 和 thymeleaf 作为示例,分别对应模板文件 makershow.ftl 和 leafShow.html;
|
||||
3. spring boot 2.x 默认是不支持 jsp 的,需要额外的配置,关于使用 jsp 的整合可以参考[spring-boot-jsp](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-jsp) 项目。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-base.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
导入相关的 starter(启动器)
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-base</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-boot-base</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--模板引擎-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<!--web 启动器-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--lombok 插件-->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--测试相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<!--Spring Boot 的 Maven 插件(Spring Boot Maven plugin)能够以 Maven 的方式为应用提供 Spring Boot 的支持-->
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
```
|
||||
|
||||
1. spring boot 项目默认继承自 spring-boot-starter-parent,而 spring-boot-starter-parent 继承自 spring-boot-dependencies, spring-boot-dependencies 中定义了关于 spring boot 依赖的各种 jar 包的版本,是 spring boot 的版本管理中心。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dependencies.png"/> </div>
|
||||
|
||||
2. 关于 spring boot 2.x 官方支持的所有 starter 可以参见官方文档 [Table 13.1. Spring Boot application starters](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#using-boot-starter)
|
||||
|
||||
|
||||
|
||||
## 二、spring boot 主启动类
|
||||
|
||||
如果采用 IDEA 或者 Spring Tool Suite (STS) 等开发工具创建的 spring boot 工程,会默认创建启动类,如果没有创建,需要手动创建启动类
|
||||
|
||||
```java
|
||||
package com.heibaiying.springbootbase;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SpringBootBaseApplication {
|
||||
|
||||
// 启动类默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootBaseApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@SpringBootApplication 注解是一个复合注解,里面包含了@ComponentScan 注解,默认开启包扫描,扫描与主程序所在包及其子包,对于本工程而言 默认扫描 com.heibaiying.springbootbase
|
||||
|
||||
```java
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan(excludeFilters = {
|
||||
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
|
||||
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
|
||||
public @interface SpringBootApplication {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、开箱即用的web工程
|
||||
|
||||
在 springbootBaseApplication.java 的同级目录创建 controller 文件夹,并在其中创建 RestfulController.java,启动项目访问 localhost:8080/restful/programmers 即可看到项目搭建成功。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : restful 控制器
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("restful")
|
||||
public class RestfulController {
|
||||
|
||||
@GetMapping("programmers")
|
||||
private List<Programmer> getProgrammers() {
|
||||
List<Programmer> programmers = new ArrayList<>();
|
||||
programmers.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
|
||||
programmers.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
|
||||
return programmers;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里之所以能够开箱即用,是因为我们在项目中导入 spring-boot-starter-web 启动器,而@SpringBootApplication 复合注解中默认开启了@EnableAutoConfiguration 注解允许开启自动化配置,spring 在检查导入 starter-web 的依赖后就会开启 web 的自动化配置。
|
||||
|
||||
|
||||
|
||||
## 四、模板引擎
|
||||
|
||||
这里我们在一个项目中同时导入了 freemaker 和 thymeleaf 的 starter(虽然并不推荐,但是在同一个项目中是可以混用这两种模板引擎的)。
|
||||
|
||||
#### 4.1 freemarker
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 跳转渲染模板引擎 默认模板的存放位置为 classpath:templates
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("freemarker")
|
||||
public class FreeMarkerController {
|
||||
|
||||
@RequestMapping("show")
|
||||
private String programmerShow(ModelMap modelMap){
|
||||
List<Programmer> programmerList=new ArrayList<>();
|
||||
programmerList.add(new Programmer("xiaoming",12,100000.00f,LocalDate.of(2019,Month.AUGUST,2)));
|
||||
programmerList.add(new Programmer("xiaohong",23,900000.00f,LocalDate.of(2013,Month.FEBRUARY,2)));
|
||||
modelMap.addAttribute("programmers",programmerList);
|
||||
return "markerShow";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>freemarker 模板引擎</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<#list programmers as programmer>
|
||||
<li>姓名: ${programmer.name} 年龄: ${programmer.age}</li>
|
||||
</#list>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 4.2 thymeleaf
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 跳转渲染模板引擎 默认模板的存放位置为 classpath:templates
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("thymeleaf")
|
||||
public class ThymeleafController {
|
||||
|
||||
@RequestMapping("show")
|
||||
private String programmerShow(ModelMap modelMap) {
|
||||
List<Programmer> programmerList = new ArrayList<>();
|
||||
programmerList.add(new Programmer("xiaoming", 12, 100000.00f, LocalDate.of(2019, Month.AUGUST, 2)));
|
||||
programmerList.add(new Programmer("xiaohong", 23, 900000.00f, LocalDate.of(2013, Month.FEBRUARY, 2)));
|
||||
modelMap.addAttribute("programmers", programmerList);
|
||||
return "leafShow";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>thymeleaf 模板引擎</title>
|
||||
</head>
|
||||
<body>
|
||||
<ul th:each="programmer:${programmers}">
|
||||
<li>
|
||||
姓名:<span th:text="${programmer.name}"></span>
|
||||
薪水:<span th:text="${programmer.salary}"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 4.3 文档说明
|
||||
|
||||
freemarker:提供了完善的中文文档,地址 http://freemarker.foofun.cn/
|
||||
|
||||
thymeleaf:官方英文文档地址:[thymeleaf 3.0.11RELEASE](https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.pdf)
|
||||
|
||||
注:我在本仓库中也上传了一份[thymeleaf 中文文档(gangzi828(刘明刚 译)](https://github.com/heibaiying/spring-samples-for-all/tree/master/referenced%20documents),翻译的版本为 3.0.5RELEASE
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot data jpa
|
||||
|
||||
# spring boot data jpa
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
|
||||
@@ -12,194 +13,194 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-data-jpa.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<!-- data-jpa -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<!--引入mysql驱动-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<!--单元测试包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
## 二、data jpa 的使用
|
||||
|
||||
#### 2.1 在application.yml 中配置数据源
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
#Hibernate默认创建的表是myisam引擎,可以用以下方式指定为使用innodb创建表
|
||||
database-platform: org.hibernate.dialect.MySQL57Dialect
|
||||
show-sql: true
|
||||
```
|
||||
|
||||
#### 2.2 新建查询接口
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 查询接口继承自CrudRepository,CrudRepository 默认定义了部分增删改查方法
|
||||
*/
|
||||
public interface ProgRepository extends CrudRepository<Programmer, Integer> {
|
||||
|
||||
/*
|
||||
* 方法名遵循命名规范的查询 更多命名规范可以参考官方文档所列出的这张表格
|
||||
*/
|
||||
List<Programmer> findAllByName(String name);
|
||||
|
||||
/*
|
||||
*分页排序查询
|
||||
*/
|
||||
Page<Programmer> findAll(Pageable pageable);
|
||||
|
||||
|
||||
/*
|
||||
* 占位符查询
|
||||
*/
|
||||
@Query(value = "select u from Programmer u where u.name = ?1 or u.salary = ?2")
|
||||
List<Programmer> findByConditionAndOrder(String name, float salary, Sort.Order order);
|
||||
|
||||
|
||||
/*
|
||||
* 传入参数名称
|
||||
*/
|
||||
@Query("select u from Programmer u where u.name = :name or u.age = :age")
|
||||
Programmer findByParam(@Param("name") String name,
|
||||
@Param("age") int age);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
关于查询方法遵循的命名规范和关键词见下表:
|
||||
|
||||
| Keyword | Sample | JPQL snippet |
|
||||
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| `And` | `findByLastnameAndFirstname` | `… where x.lastname = ?1 and x.firstname = ?2` |
|
||||
| `Or` | `findByLastnameOrFirstname` | `… where x.lastname = ?1 or x.firstname = ?2` |
|
||||
| `Is,Equals` | `findByFirstname`,`findByFirstnameIs`,<br>`findByFirstnameEquals` | `… where x.firstname = ?1` |
|
||||
| `Between` | `findByStartDateBetween` | `… where x.startDate between ?1 and ?2` |
|
||||
| `LessThan` | `findByAgeLessThan` | `… where x.age < ?1` |
|
||||
| `LessThanEqual` | `findByAgeLessThanEqual` | `… where x.age <= ?1` |
|
||||
| `GreaterThan` | `findByAgeGreaterThan` | `… where x.age > ?1` |
|
||||
| `GreaterThanEqual` | `findByAgeGreaterThanEqual` | `… where x.age >= ?1` |
|
||||
| `After` | `findByStartDateAfter` | `… where x.startDate > ?1` |
|
||||
| `Before` | `findByStartDateBefore` | `… where x.startDate < ?1` |
|
||||
| `IsNull` | `findByAgeIsNull` | `… where x.age is null` |
|
||||
| `IsNotNull,NotNull` | `findByAge(Is)NotNull` | `… where x.age not null` |
|
||||
| `Like` | `findByFirstnameLike` | `… where x.firstname like ?1` |
|
||||
| `NotLike` | `findByFirstnameNotLike` | `… where x.firstname not like ?1` |
|
||||
| `StartingWith` | `findByFirstnameStartingWith` | `… where x.firstname like ?1`(parameter bound with appended `%`) |
|
||||
| `EndingWith` | `findByFirstnameEndingWith` | `… where x.firstname like ?1`(parameter bound with prepended `%`) |
|
||||
| `Containing` | `findByFirstnameContaining` | `… where x.firstname like ?1`(parameter bound wrapped in `%`) |
|
||||
| `OrderBy` | `findByAgeOrderByLastnameDesc` | `… where x.age = ?1 order by x.lastname desc` |
|
||||
| `Not` | `findByLastnameNot` | `… where x.lastname <> ?1` |
|
||||
| `In` | `findByAgeIn(Collection<Age> ages)` | `… where x.age in ?1` |
|
||||
| `NotIn` | `findByAgeNotIn(Collection<Age> ages)` | `… where x.age not in ?1` |
|
||||
| `True` | `findByActiveTrue()` | `… where x.active = true` |
|
||||
| `False` | `findByActiveFalse()` | `… where x.active = false` |
|
||||
| `IgnoreCase` | `findByFirstnameIgnoreCase` | `… where UPPER(x.firstame) = UPPER(?1)` |
|
||||
|
||||
#### 2.3 测试类
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class DataJPATests {
|
||||
|
||||
@Autowired
|
||||
private ProgRepository repository;
|
||||
|
||||
/**
|
||||
* 保存数据测试
|
||||
*/
|
||||
@Test
|
||||
public void save() {
|
||||
// 保存单条数据
|
||||
repository.save(new Programmer("pro01", 12, 2121.34f, new Date()));
|
||||
// 保存多条数据
|
||||
List<Programmer> programmers = new ArrayList<>();
|
||||
programmers.add(new Programmer("pro02", 22, 3221.34f, new Date()));
|
||||
programmers.add(new Programmer("pro03", 32, 3321.34f, new Date()));
|
||||
programmers.add(new Programmer("pro04", 44, 4561.34f, new Date()));
|
||||
programmers.add(new Programmer("pro01", 44, 4561.34f, new Date()));
|
||||
repository.saveAll(programmers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询数据测试
|
||||
*/
|
||||
@Test
|
||||
public void get() {
|
||||
|
||||
// 遵循命名规范的查询
|
||||
List<Programmer> programmers = repository.findAllByName("pro01");
|
||||
programmers.forEach(System.out::println);
|
||||
|
||||
// 传入参数名称
|
||||
Programmer param = repository.findByParam("pro02", 22);
|
||||
System.out.println("findByParam:" + param);
|
||||
|
||||
// 占位符查询
|
||||
List<Programmer> byCondition = repository.findByConditionAndOrder("pro03", 3321.34f, Sort.Order.asc("salary"));
|
||||
System.out.println("byCondition:" + byCondition);
|
||||
|
||||
//条件与分页查询 需要注意的是这里的页数是从第0页开始计算的
|
||||
Page<Programmer> page = repository.findAll(PageRequest.of(0, 10, Sort.Direction.DESC, "salary"));
|
||||
page.get().forEach(System.out::println);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新数据测试
|
||||
*/
|
||||
@Test
|
||||
public void update() {
|
||||
// 保存主键相同的数据就认为是更新操作
|
||||
repository.save(new Programmer(1, "updatePro01", 12, 2121.34f, new Date()));
|
||||
Optional<Programmer> programmer = repository.findById(1);
|
||||
Assert.assertEquals(programmer.get().getName(), "updatePro01");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据测试
|
||||
*/
|
||||
@Test
|
||||
public void delete() {
|
||||
Optional<Programmer> programmer = repository.findById(2);
|
||||
if (programmer.isPresent()) {
|
||||
repository.deleteById(2);
|
||||
}
|
||||
Assert.assertFalse(programmer.isPresent());
|
||||
}
|
||||
}
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-data-jpa.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<!-- data-jpa -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<!--引入 mysql 驱动-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<!--单元测试包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
## 二、data jpa 的使用
|
||||
|
||||
#### 2.1 在application.yml 中配置数据源
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
#Hibernate 默认创建的表是 myisam 引擎,可以用以下方式指定为使用 innodb 创建表
|
||||
database-platform: org.hibernate.dialect.MySQL57Dialect
|
||||
show-sql: true
|
||||
```
|
||||
|
||||
#### 2.2 新建查询接口
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 查询接口继承自 CrudRepository,CrudRepository 默认定义了部分增删改查方法
|
||||
*/
|
||||
public interface ProgRepository extends CrudRepository<Programmer, Integer> {
|
||||
|
||||
/*
|
||||
* 方法名遵循命名规范的查询 更多命名规范可以参考官方文档所列出的这张表格
|
||||
*/
|
||||
List<Programmer> findAllByName(String name);
|
||||
|
||||
/*
|
||||
*分页排序查询
|
||||
*/
|
||||
Page<Programmer> findAll(Pageable pageable);
|
||||
|
||||
|
||||
/*
|
||||
* 占位符查询
|
||||
*/
|
||||
@Query(value = "select u from Programmer u where u.name = ?1 or u.salary = ?2")
|
||||
List<Programmer> findByConditionAndOrder(String name, float salary, Sort.Order order);
|
||||
|
||||
|
||||
/*
|
||||
* 传入参数名称
|
||||
*/
|
||||
@Query("select u from Programmer u where u.name = :name or u.age = :age")
|
||||
Programmer findByParam(@Param("name") String name,
|
||||
@Param("age") int age);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
关于查询方法遵循的命名规范和关键词见下表:
|
||||
|
||||
| Keyword | Sample | JPQL snippet |
|
||||
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| `And` | `findByLastnameAndFirstname` | `… where x.lastname = ?1 and x.firstname = ?2` |
|
||||
| `Or` | `findByLastnameOrFirstname` | `… where x.lastname = ?1 or x.firstname = ?2` |
|
||||
| `Is,Equals` | `findByFirstname`,`findByFirstnameIs`,<br>`findByFirstnameEquals` | `… where x.firstname = ?1` |
|
||||
| `Between` | `findByStartDateBetween` | `… where x.startDate between ?1 and ?2` |
|
||||
| `LessThan` | `findByAgeLessThan` | `… where x.age < ?1` |
|
||||
| `LessThanEqual` | `findByAgeLessThanEqual` | `… where x.age <= ?1` |
|
||||
| `GreaterThan` | `findByAgeGreaterThan` | `… where x.age > ?1` |
|
||||
| `GreaterThanEqual` | `findByAgeGreaterThanEqual` | `… where x.age >= ?1` |
|
||||
| `After` | `findByStartDateAfter` | `… where x.startDate > ?1` |
|
||||
| `Before` | `findByStartDateBefore` | `… where x.startDate < ?1` |
|
||||
| `IsNull` | `findByAgeIsNull` | `… where x.age is null` |
|
||||
| `IsNotNull,NotNull` | `findByAge(Is)NotNull` | `… where x.age not null` |
|
||||
| `Like` | `findByFirstnameLike` | `… where x.firstname like ?1` |
|
||||
| `NotLike` | `findByFirstnameNotLike` | `… where x.firstname not like ?1` |
|
||||
| `StartingWith` | `findByFirstnameStartingWith` | `… where x.firstname like ?1`(parameter bound with appended `%`) |
|
||||
| `EndingWith` | `findByFirstnameEndingWith` | `… where x.firstname like ?1`(parameter bound with prepended `%`) |
|
||||
| `Containing` | `findByFirstnameContaining` | `… where x.firstname like ?1`(parameter bound wrapped in `%`) |
|
||||
| `OrderBy` | `findByAgeOrderByLastnameDesc` | `… where x.age = ?1 order by x.lastname desc` |
|
||||
| `Not` | `findByLastnameNot` | `… where x.lastname <> ?1` |
|
||||
| `In` | `findByAgeIn(Collection<Age> ages)` | `… where x.age in ?1` |
|
||||
| `NotIn` | `findByAgeNotIn(Collection<Age> ages)` | `… where x.age not in ?1` |
|
||||
| `True` | `findByActiveTrue()` | `… where x.active = true` |
|
||||
| `False` | `findByActiveFalse()` | `… where x.active = false` |
|
||||
| `IgnoreCase` | `findByFirstnameIgnoreCase` | `… where UPPER(x.firstame) = UPPER(?1)` |
|
||||
|
||||
#### 2.3 测试类
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class DataJPATests {
|
||||
|
||||
@Autowired
|
||||
private ProgRepository repository;
|
||||
|
||||
/**
|
||||
* 保存数据测试
|
||||
*/
|
||||
@Test
|
||||
public void save() {
|
||||
// 保存单条数据
|
||||
repository.save(new Programmer("pro01", 12, 2121.34f, new Date()));
|
||||
// 保存多条数据
|
||||
List<Programmer> programmers = new ArrayList<>();
|
||||
programmers.add(new Programmer("pro02", 22, 3221.34f, new Date()));
|
||||
programmers.add(new Programmer("pro03", 32, 3321.34f, new Date()));
|
||||
programmers.add(new Programmer("pro04", 44, 4561.34f, new Date()));
|
||||
programmers.add(new Programmer("pro01", 44, 4561.34f, new Date()));
|
||||
repository.saveAll(programmers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询数据测试
|
||||
*/
|
||||
@Test
|
||||
public void get() {
|
||||
|
||||
// 遵循命名规范的查询
|
||||
List<Programmer> programmers = repository.findAllByName("pro01");
|
||||
programmers.forEach(System.out::println);
|
||||
|
||||
// 传入参数名称
|
||||
Programmer param = repository.findByParam("pro02", 22);
|
||||
System.out.println("findByParam:" + param);
|
||||
|
||||
// 占位符查询
|
||||
List<Programmer> byCondition = repository.findByConditionAndOrder("pro03", 3321.34f, Sort.Order.asc("salary"));
|
||||
System.out.println("byCondition:" + byCondition);
|
||||
|
||||
//条件与分页查询 需要注意的是这里的页数是从第 0 页开始计算的
|
||||
Page<Programmer> page = repository.findAll(PageRequest.of(0, 10, Sort.Direction.DESC, "salary"));
|
||||
page.get().forEach(System.out::println);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新数据测试
|
||||
*/
|
||||
@Test
|
||||
public void update() {
|
||||
// 保存主键相同的数据就认为是更新操作
|
||||
repository.save(new Programmer(1, "updatePro01", 12, 2121.34f, new Date()));
|
||||
Optional<Programmer> programmer = repository.findById(1);
|
||||
Assert.assertEquals(programmer.get().getName(), "updatePro01");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据测试
|
||||
*/
|
||||
@Test
|
||||
public void delete() {
|
||||
Optional<Programmer> programmer = repository.findById(2);
|
||||
if (programmer.isPresent()) {
|
||||
repository.deleteById(2);
|
||||
}
|
||||
Assert.assertFalse(programmer.isPresent());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 druid+mybatis
|
||||
|
||||
# spring boot 整合 druid+mybatis
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
|
||||
@@ -13,197 +14,197 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下;
|
||||
|
||||
2. 为了使用druid控制台的功能,项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid-mybatis.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
需要说明的是按照spring 官方对于自定义的starter 命名规范的推荐:
|
||||
|
||||
- 官方的starter命名:spring-boot-starter-XXXX
|
||||
- 其他第三方starter命名:XXXX-spring-boot-starte
|
||||
|
||||
所以mybatis的starter命名为mybatis-spring-boot-starter,如果有自定义starter需求,也需要按照此命名规则进行命名。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<!--引入mysql驱动-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<!--druid 依赖-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>1.1.10</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
spring boot 与 mybatis 版本的对应关系:
|
||||
|
||||
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
|
||||
| --------------------------- | ------------------------------------------------------------ | ------------- |
|
||||
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
|
||||
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
|
||||
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
|
||||
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
|
||||
|
||||
|
||||
|
||||
## 二、整合 druid + mybatis
|
||||
|
||||
#### 2.1 在application.yml 中配置数据源
|
||||
|
||||
本用例采用druid作为数据库连接池,虽然druid性能略逊于Hikari,但是提供了更为全面的监控管理,可以按照实际需求选用druid或者Hikari。(关于Hikari数据源的配置可以参考[spring-boot-mybatis项目](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mybatis))
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
# 使用 druid 作为连接池 更多配置的说明可以参见 druid starter 中文文档 https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
druid:
|
||||
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
|
||||
initialSize: 5
|
||||
# 最小连接池数量
|
||||
minIdle: 5
|
||||
# 最大连接池数量
|
||||
maxActive: 10
|
||||
# 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
|
||||
maxWait: 60000
|
||||
# Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
# 连接保持空闲而不被驱逐的最小时间
|
||||
minEvictableIdleTimeMillis: 300000
|
||||
# 用来检测连接是否有效的sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL
|
||||
validationQuery: SELECT 1
|
||||
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
|
||||
testWhileIdle: true
|
||||
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
|
||||
testOnBorrow: false
|
||||
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
|
||||
testOnReturn: false
|
||||
# 是否自动回收超时连接
|
||||
removeAbandoned: true
|
||||
# 超时时间(以秒数为单位)
|
||||
remove-abandoned-timeout: 180
|
||||
|
||||
# druid 监控的配置 如果不使用 druid 的监控功能的话 以下配置就不是必须的
|
||||
# 本项目监控台访问地址: http://localhost:8080/druid/login.html
|
||||
|
||||
# WebStatFilter用于采集web-jdbc关联监控的数据。
|
||||
# 更多配置可参见: https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter
|
||||
web-stat-filter:
|
||||
# 是否开启 WebStatFilter 默认是true
|
||||
enabled: true
|
||||
# 需要拦截的url
|
||||
url-pattern: /*
|
||||
# 排除静态资源的请求
|
||||
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
|
||||
|
||||
# Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。
|
||||
# 更多配置可参见:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
|
||||
stat-view-servlet:
|
||||
#是否启用StatViewServlet 默认值true
|
||||
enabled: true
|
||||
# 需要拦截的url
|
||||
url-pattern: /druid/*
|
||||
# 允许清空统计数据
|
||||
reset-enable: true
|
||||
login-username: druid
|
||||
login-password: druid
|
||||
|
||||
|
||||
|
||||
# mybatis 相关配置
|
||||
mybatis:
|
||||
configuration:
|
||||
# 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
|
||||
# oracle数据库建议配置为JdbcType.NULL, 默认是Other
|
||||
jdbc-type-for-null: 'null'
|
||||
# 是否打印sql语句 调试的时候可以开启
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
```
|
||||
|
||||
#### 2.2 新建查询接口和controller
|
||||
|
||||
```java
|
||||
@Mapper
|
||||
public interface ProgrammerDao {
|
||||
|
||||
|
||||
@Select("select * from programmer")
|
||||
List<Programmer> selectAll();
|
||||
|
||||
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
|
||||
void save(Programmer programmer);
|
||||
|
||||
@Select("select * from programmer where name = #{id}")
|
||||
Programmer selectById(int id);
|
||||
|
||||
@Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
|
||||
int modify(Programmer programmer);
|
||||
|
||||
@Delete(" delete from programmer where id = #{id}")
|
||||
void delete(int id);
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
@RestController
|
||||
public class ProgrammerController {
|
||||
|
||||
@Autowired
|
||||
private ProgrammerDao programmerDao;
|
||||
|
||||
@GetMapping("/programmers")
|
||||
public List<Programmer> get() {
|
||||
return programmerDao.selectAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 关于druid监控数据的外部化调用
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :在 Spring Boot 中可以通过 HTTP 接口将 Druid 监控数据以JSON 的形式暴露出去,
|
||||
* 实际使用中你可以根据你的需要自由地对监控数据、暴露方式进行扩展。
|
||||
*/
|
||||
|
||||
@RestController
|
||||
public class DruidStatController {
|
||||
|
||||
@GetMapping("/stat")
|
||||
public Object druidStat() {
|
||||
// DruidStatManagerFacade#getDataSourceStatDataList 该方法可以获取所有数据源的监控数据
|
||||
return DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/druid-status.png"/> </div>
|
||||
|
||||
|
||||
|
||||
#### 2.4 druid 控制台的使用,默认访问地址 http://localhost:8080/druid/login.html
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
1. 项目查询用的表对应的建表语句放置在 resources 的 sql 文件夹下;
|
||||
|
||||
2. 为了使用 druid 控制台的功能,项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid-mybatis.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
需要说明的是按照 spring 官方对于自定义的 starter 命名规范的推荐:
|
||||
|
||||
- 官方的 starter 命名:spring-boot-starter-XXXX
|
||||
- 其他第三方 starter 命名:XXXX-spring-boot-starte
|
||||
|
||||
所以 mybatis 的 starter 命名为 mybatis-spring-boot-starter,如果有自定义 starter 需求,也需要按照此命名规则进行命名。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<!--引入mysql驱动-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<!--druid 依赖-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>1.1.10</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
spring boot 与 mybatis 版本的对应关系:
|
||||
|
||||
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
|
||||
| --------------------------- | ------------------------------------------------------------ | ------------- |
|
||||
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
|
||||
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
|
||||
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
|
||||
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
|
||||
|
||||
|
||||
|
||||
## 二、整合 druid + mybatis
|
||||
|
||||
#### 2.1 在application.yml 中配置数据源
|
||||
|
||||
本用例采用 druid 作为数据库连接池,虽然 druid 性能略逊于 Hikari,但是提供了更为全面的监控管理,可以按照实际需求选用 druid 或者 Hikari。(关于 Hikari 数据源的配置可以参考[spring-boot-mybatis 项目](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-mybatis))
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
# 使用 druid 作为连接池 更多配置的说明可以参见 druid starter 中文文档 https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
druid:
|
||||
# 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时
|
||||
initialSize: 5
|
||||
# 最小连接池数量
|
||||
minIdle: 5
|
||||
# 最大连接池数量
|
||||
maxActive: 10
|
||||
# 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
|
||||
maxWait: 60000
|
||||
# Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
# 连接保持空闲而不被驱逐的最小时间
|
||||
minEvictableIdleTimeMillis: 300000
|
||||
# 用来检测连接是否有效的 sql 因数据库方言而差, 例如 oracle 应该写成 SELECT 1 FROM DUAL
|
||||
validationQuery: SELECT 1
|
||||
# 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
|
||||
testWhileIdle: true
|
||||
# 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
|
||||
testOnBorrow: false
|
||||
# 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
|
||||
testOnReturn: false
|
||||
# 是否自动回收超时连接
|
||||
removeAbandoned: true
|
||||
# 超时时间 (以秒数为单位)
|
||||
remove-abandoned-timeout: 180
|
||||
|
||||
# druid 监控的配置 如果不使用 druid 的监控功能的话 以下配置就不是必须的
|
||||
# 本项目监控台访问地址: http://localhost:8080/druid/login.html
|
||||
|
||||
# WebStatFilter 用于采集 web-jdbc 关联监控的数据。
|
||||
# 更多配置可参见: https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_%E9%85%8D%E7%BD%AEWebStatFilter
|
||||
web-stat-filter:
|
||||
# 是否开启 WebStatFilter 默认是 true
|
||||
enabled: true
|
||||
# 需要拦截的 url
|
||||
url-pattern: /*
|
||||
# 排除静态资源的请求
|
||||
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
|
||||
|
||||
# Druid 内置提供了一个 StatViewServlet 用于展示 Druid 的统计信息。
|
||||
# 更多配置可参见:https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
|
||||
stat-view-servlet:
|
||||
#是否启用 StatViewServlet 默认值 true
|
||||
enabled: true
|
||||
# 需要拦截的 url
|
||||
url-pattern: /druid/*
|
||||
# 允许清空统计数据
|
||||
reset-enable: true
|
||||
login-username: druid
|
||||
login-password: druid
|
||||
|
||||
|
||||
|
||||
# mybatis 相关配置
|
||||
mybatis:
|
||||
configuration:
|
||||
# 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
|
||||
# oracle 数据库建议配置为 JdbcType.NULL, 默认是 Other
|
||||
jdbc-type-for-null: 'null'
|
||||
# 是否打印 sql 语句 调试的时候可以开启
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
```
|
||||
|
||||
#### 2.2 新建查询接口和controller
|
||||
|
||||
```java
|
||||
@Mapper
|
||||
public interface ProgrammerDao {
|
||||
|
||||
|
||||
@Select("select * from programmer")
|
||||
List<Programmer> selectAll();
|
||||
|
||||
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
|
||||
void save(Programmer programmer);
|
||||
|
||||
@Select("select * from programmer where name = #{id}")
|
||||
Programmer selectById(int id);
|
||||
|
||||
@Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
|
||||
int modify(Programmer programmer);
|
||||
|
||||
@Delete(" delete from programmer where id = #{id}")
|
||||
void delete(int id);
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
@RestController
|
||||
public class ProgrammerController {
|
||||
|
||||
@Autowired
|
||||
private ProgrammerDao programmerDao;
|
||||
|
||||
@GetMapping("/programmers")
|
||||
public List<Programmer> get() {
|
||||
return programmerDao.selectAll();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 关于druid监控数据的外部化调用
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :在 Spring Boot 中可以通过 HTTP 接口将 Druid 监控数据以 JSON 的形式暴露出去,
|
||||
* 实际使用中你可以根据你的需要自由地对监控数据、暴露方式进行扩展。
|
||||
*/
|
||||
|
||||
@RestController
|
||||
public class DruidStatController {
|
||||
|
||||
@GetMapping("/stat")
|
||||
public Object druidStat() {
|
||||
// DruidStatManagerFacade#getDataSourceStatDataList 该方法可以获取所有数据源的监控数据
|
||||
return DruidStatManagerFacade.getInstance().getDataSourceStatDataList();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/druid-status.png"/> </div>
|
||||
|
||||
|
||||
|
||||
#### 2.4 druid 控制台的使用,默认访问地址 http://localhost:8080/druid/login.html
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-druid%20%E6%8E%A7%E5%88%B6%E5%8F%B0.png"/> </div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 dubbo
|
||||
|
||||
# spring boot 整合 dubbo
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
|
||||
<a href="#二关键依赖">二、关键依赖</a><br/>
|
||||
<a href="#三公共模块boot-dubbo-common">三、公共模块(boot-dubbo-common)</a><br/>
|
||||
@@ -16,349 +17,349 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-boot-dubbo下构建三个子模块:
|
||||
|
||||
1. boot-dubbo-common 是公共模块,用于存放公共的接口和bean,被boot-dubbo-provider和boot-dubbo-consumer在pom.xml中引用;
|
||||
2. boot-dubbo-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. boot-dubbo-consumer是服务的消费者,调用provider提供的查询服务。
|
||||
|
||||
1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记 [Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、关键依赖
|
||||
|
||||
在父工程的项目中统一导入依赖dubbo的starter,父工程的pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>boot-dubbo-common</module>
|
||||
<module>boot-dubbo-consumer</module>
|
||||
<module>boot-dubbo-provider</module>
|
||||
</modules>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-dubbo</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-boot-dubbo</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--引入dubbo start依赖-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.boot</groupId>
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
<version>0.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(boot-dubbo-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(boot-dubbo-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-provider.png"/> </div>
|
||||
|
||||
#### 4.1 提供方配置
|
||||
|
||||
```yaml
|
||||
dubbo:
|
||||
application:
|
||||
name: boot-duboo-provider
|
||||
# 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心,并且在start依赖中引入了zookeeper的java客户端Curator
|
||||
registry:
|
||||
protocol: zookeeper
|
||||
address: 127.0.0.1:2181
|
||||
protocol.name: dubbo
|
||||
```
|
||||
|
||||
#### 4.2 使用注解@Service暴露服务
|
||||
|
||||
需要注意的是这里的@Service注解不是spring的注解,而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
|
||||
|
||||
```java
|
||||
package com.heibaiying.dubboprovider.service;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Service;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service(timeout = 5000)
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 五、服务消费者(boot-dubbo-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-consumer1.png"/> </div>
|
||||
|
||||
#### 1.消费方的配置
|
||||
|
||||
```yaml
|
||||
dubbo:
|
||||
application:
|
||||
name: boot-duboo-provider
|
||||
# 指定注册协议和注册地址 dubbo推荐使用zookeeper作为注册中心,并且在start依赖中引入了zookeeper的java客户端Curator
|
||||
registry:
|
||||
protocol: zookeeper
|
||||
address: 127.0.0.1:2181
|
||||
protocol.name: dubbo
|
||||
# 关闭所有服务的启动时检查 (没有提供者时报错)视实际情况设置
|
||||
consumer:
|
||||
check: false
|
||||
server:
|
||||
port: 8090
|
||||
```
|
||||
|
||||
#### 2.使用注解@Reference引用远程服务
|
||||
|
||||
```java
|
||||
package com.heibaiying.dubboconsumer.controller;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
// dubbo远程引用注解
|
||||
@Reference
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer中 pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>spring-boot-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>boot-dubbo-consumer</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>boot-dubbo-consumer</name>
|
||||
<description>dubbo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<!--引入对公共模块的依赖-->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>boot-dubbo-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider中 pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>spring-boot-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<artifactId>boot-dubbo-provider</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>boot-dubbo-provider</name>
|
||||
<description>dubbo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<!--引入对公共模块的依赖-->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>boot-dubbo-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照 dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用 maven 多模块的构建方式,在 spring-boot-dubbo 下构建三个子模块:
|
||||
|
||||
1. boot-dubbo-common 是公共模块,用于存放公共的接口和 bean,被 boot-dubbo-provider 和 boot-dubbo-consumer 在 pom.xml 中引用;
|
||||
2. boot-dubbo-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. boot-dubbo-consumer 是服务的消费者,调用 provider 提供的查询服务。
|
||||
|
||||
1.2 本项目 dubbo 的搭建采用 zookeeper 作为注册中心, 关于 zookeeper 的安装和基本操作可以参见我的手记 [Zookeeper 基础命令与 Java 客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、关键依赖
|
||||
|
||||
在父工程的项目中统一导入依赖 dubbo 的 starter,父工程的 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>boot-dubbo-common</module>
|
||||
<module>boot-dubbo-consumer</module>
|
||||
<module>boot-dubbo-provider</module>
|
||||
</modules>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-dubbo</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-boot-dubbo</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--引入 dubbo start 依赖-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.boot</groupId>
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
<version>0.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(boot-dubbo-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(boot-dubbo-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-provider.png"/> </div>
|
||||
|
||||
#### 4.1 提供方配置
|
||||
|
||||
```yaml
|
||||
dubbo:
|
||||
application:
|
||||
name: boot-duboo-provider
|
||||
# 指定注册协议和注册地址 dubbo 推荐使用 zookeeper 作为注册中心,并且在 start 依赖中引入了 zookeeper 的 java 客户端 Curator
|
||||
registry:
|
||||
protocol: zookeeper
|
||||
address: 127.0.0.1:2181
|
||||
protocol.name: dubbo
|
||||
```
|
||||
|
||||
#### 4.2 使用注解@Service暴露服务
|
||||
|
||||
需要注意的是这里的@Service 注解不是 spring 的注解,而是 dubbo 的注解 com.alibaba.dubbo.config.annotation.Service
|
||||
|
||||
```java
|
||||
package com.heibaiying.dubboprovider.service;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Service;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service(timeout = 5000)
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 五、服务消费者(boot-dubbo-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/boot-dubbo-consumer1.png"/> </div>
|
||||
|
||||
#### 1.消费方的配置
|
||||
|
||||
```yaml
|
||||
dubbo:
|
||||
application:
|
||||
name: boot-duboo-provider
|
||||
# 指定注册协议和注册地址 dubbo 推荐使用 zookeeper 作为注册中心,并且在 start 依赖中引入了 zookeeper 的 java 客户端 Curator
|
||||
registry:
|
||||
protocol: zookeeper
|
||||
address: 127.0.0.1:2181
|
||||
protocol.name: dubbo
|
||||
# 关闭所有服务的启动时检查 (没有提供者时报错)视实际情况设置
|
||||
consumer:
|
||||
check: false
|
||||
server:
|
||||
port: 8090
|
||||
```
|
||||
|
||||
#### 2.使用注解@Reference引用远程服务
|
||||
|
||||
```java
|
||||
package com.heibaiying.dubboconsumer.controller;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
// dubbo 远程引用注解
|
||||
@Reference
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer 和 provider 模块均依赖公共模块,所以在构建 consumer 和 provider 项目前需要将 common 模块安装到本地仓库,**依次**对**父工程**和**common 模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer 中 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>spring-boot-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>boot-dubbo-consumer</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>boot-dubbo-consumer</name>
|
||||
<description>dubbo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<!--引入对公共模块的依赖-->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>boot-dubbo-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider 中 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<artifactId>spring-boot-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
|
||||
<artifactId>boot-dubbo-provider</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>boot-dubbo-provider</name>
|
||||
<description>dubbo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<!--引入对公共模块的依赖-->
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>boot-dubbo-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是 127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 内置容器 整合 jsp
|
||||
|
||||
# spring boot 内置容器 整合 jsp
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
|
||||
@@ -12,85 +13,85 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-jsp.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<!--整合 jsp 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--jsp jstl 标签支持-->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>jstl</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、整合 jsp
|
||||
|
||||
#### 2.1 导入整合的依赖
|
||||
|
||||
```xml
|
||||
<!--整合 jsp 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--jsp jstl 标签支持-->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>jstl</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀
|
||||
|
||||
```yml
|
||||
spring:
|
||||
mvc:
|
||||
view:
|
||||
prefix: /WEB-INF/jsp/
|
||||
suffix: .jsp
|
||||
```
|
||||
|
||||
#### 2.3 新建controller和show.jsp 测试整合是否成功
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@RequestMapping("index")
|
||||
public class JspController {
|
||||
|
||||
@RequestMapping
|
||||
public String jsp(Model model){
|
||||
Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
|
||||
model.addAttribute("programmer",programmer);
|
||||
return "show";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>programmer</title>
|
||||
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>姓名: ${programmer.name}</li>
|
||||
<li>年龄: ${programmer.age}</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-jsp.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<!--整合 jsp 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--jsp jstl 标签支持-->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>jstl</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、整合 jsp
|
||||
|
||||
#### 2.1 导入整合的依赖
|
||||
|
||||
```xml
|
||||
<!--整合 jsp 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--jsp jstl 标签支持-->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>jstl</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀
|
||||
|
||||
```yml
|
||||
spring:
|
||||
mvc:
|
||||
view:
|
||||
prefix: /WEB-INF/jsp/
|
||||
suffix: .jsp
|
||||
```
|
||||
|
||||
#### 2.3 新建controller和show.jsp 测试整合是否成功
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@RequestMapping("index")
|
||||
public class JspController {
|
||||
|
||||
@RequestMapping
|
||||
public String jsp(Model model){
|
||||
Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
|
||||
model.addAttribute("programmer",programmer);
|
||||
return "show";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>programmer</title>
|
||||
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>姓名: ${programmer.name}</li>
|
||||
<li>年龄: ${programmer.age}</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 kafka
|
||||
|
||||
# spring boot 整合 kafka
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一kafka的相关概念">一、kafka的相关概念:</a><br/>
|
||||
<a href="#1主题和分区">1.主题和分区</a><br/>
|
||||
<a href="#2分区复制">2.分区复制</a><br/>
|
||||
@@ -25,374 +26,374 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、kafka的相关概念:
|
||||
|
||||
### 1.主题和分区
|
||||
|
||||
kafka 的消息通过主题进行分类。一个主题可以被分为若干个分区,一个分区就是一个提交日志。消息以追加的方式写入分区,然后以先入先出的顺序读取。kafka通过分区来实现数据的冗余和伸缩性,分区可以分布在不同的服务器上,也就是说一个主题可以横跨多个服务器,以此来提供比单个服务器更强大的性能(类比HDFS分布式文件系统)。
|
||||
|
||||
注意:由于一个主题包含多个分区,因此无法在整个主题范围内保证消息的顺序性,**但可以保证消息在单个分区内的顺序性**。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka主题和分区.png"/> </div>
|
||||
|
||||
### 2.分区复制
|
||||
|
||||
每个主题被分为若干个分区,每个分区有多个副本。那些副本被保存在 broker 上,每个 broker 可以保存成百上千个属于不同主题和分区的副本。副本有以下两种类型 :
|
||||
|
||||
- 首领副本 每个分区都有一个首领副本 。 为了保证一致性,所有生产者请求和消费者请求都会经过这个副本。
|
||||
- 跟随者副本 首领以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,它们唯一的任务就是从首领那里复制消息,保持与首领一致的状态。如果首领发生崩渍,其中的一个跟随者会被提升为新首领。
|
||||
|
||||
### 3. 生产者
|
||||
|
||||
- 默认情况下生产者在把消息均衡地分布到在主题的所有分区上,而并不关心特定消息会被写到那个分区;
|
||||
- 如果指定消息键,则通过对消息键的散列来实现分区;
|
||||
- 也可以通过消息键和分区器来实现把消息直接写到指定的分区,这个需要自定义分区器,需要实现Partitioner 接口,并重写其中的partition方法。
|
||||
|
||||
### 4. 消费者
|
||||
|
||||
消费者是**消费者群组**的一部分。也就是说,会有一个或者多个消费者共同读取一个主题,群组保证每个分区只能被一个消费者使用。
|
||||
|
||||
**一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组里面的多个消费者读取。多个消费者群组可以共同读取同一个主题,彼此之间互不影响**。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka消费者.png"/> </div>
|
||||
|
||||
### 5.broker和集群
|
||||
|
||||
一个独立的kafka服务器被称为broker。broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。broker为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘的消息。
|
||||
|
||||
broker是集群的组成部分。每一个集群都有一个broker同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。控制器负责管理工作,包括将分区分配给broker和监控broker。**在集群中,一个分区从属一个broker,该broker被称为分区的首领**。一个分区可以分配给多个broker,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余,如果有一个broker失效,其他broker可以接管领导权。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka集群复制.png"/> </div>
|
||||
|
||||
更多kafka 的说明可以参考我的个人笔记:[《Kafka权威指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka/%E3%80%8AKafka%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#53-%E5%A4%8D%E5%88%B6)
|
||||
|
||||
|
||||
|
||||
## 二、项目说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
本项目提供kafka发送简单消息、对象消息、和多消费者组消费消息三种情况下的sample。
|
||||
|
||||
1. kafkaSimpleConsumer 用于普通消息的监听;
|
||||
2. kafkaBeanConsumer 用于对象消息监听;
|
||||
3. kafkaGroupConsumer 用于多消费者组和多消费者对主题分区消息监听的情况。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-kafka.png"/> </div>
|
||||
|
||||
#### 1.2 主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、 整合 kafka
|
||||
|
||||
#### 2.1 kafka基本配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
kafka:
|
||||
# 以逗号分隔的地址列表,用于建立与Kafka集群的初始连接(kafka 默认的端口号为9092)
|
||||
bootstrap-servers: 127.0.0.1:9092
|
||||
producer:
|
||||
# 发生错误后,消息重发的次数。
|
||||
retries: 0
|
||||
#当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
|
||||
batch-size: 16384
|
||||
# 设置生产者内存缓冲区的大小。
|
||||
buffer-memory: 33554432
|
||||
# 键的序列化方式
|
||||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
# 值的序列化方式
|
||||
value-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
# acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
|
||||
# acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
|
||||
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
|
||||
acks: 1
|
||||
consumer:
|
||||
# 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式,如1S,1M,2H,5D
|
||||
auto-commit-interval: 1S
|
||||
# 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
|
||||
# latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
|
||||
# earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
|
||||
auto-offset-reset: earliest
|
||||
# 是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
|
||||
enable-auto-commit: true
|
||||
# 键的反序列化方式
|
||||
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
# 值的反序列化方式
|
||||
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
listener:
|
||||
# 在侦听器容器中运行的线程数。
|
||||
concurrency: 5
|
||||
|
||||
```
|
||||
|
||||
这里需要说明的是:
|
||||
|
||||
在spring boot 2.X 版本 auto-commit-interval(自动提交的时间间隔)采用的是值的类型为Duration ,Duration 是 jdk 1.8 版本之后引入的类,在其源码中我们可以看到对于其字符串的表达需要符合一定的规范,即数字+单位,如下的写法1s ,1.5s, 0s, 0.001S ,1h, 2d 在yaml 中都是有效的。如果传入无效的字符串,则spring boot 在启动阶段解析配置文件的时候就会抛出异常。
|
||||
|
||||
```java
|
||||
public final class Duration
|
||||
implements TemporalAmount, Comparable<Duration>, Serializable {
|
||||
|
||||
/**
|
||||
* The pattern for parsing.
|
||||
*/
|
||||
private static final Pattern PATTERN =
|
||||
Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" +
|
||||
"(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
........
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 KafkaTemplate实现消息发送
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class KafKaCustomrProducer {
|
||||
|
||||
@Autowired
|
||||
private KafkaTemplate kafkaTemplate;
|
||||
|
||||
public void sendMessage(String topic, Object object) {
|
||||
|
||||
/*
|
||||
* 这里的ListenableFuture类是spring对java原生Future的扩展增强,是一个泛型接口,用于监听异步方法的回调
|
||||
* 而对于kafka send 方法返回值而言,这里的泛型所代表的实际类型就是 SendResult<K, V>,而这里K,V的泛型实际上
|
||||
* 被用于ProducerRecord<K, V> producerRecord,即生产者发送消息的key,value 类型
|
||||
*/
|
||||
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, object);
|
||||
|
||||
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
log.info("发送消息失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(SendResult<String, Object> sendResult) {
|
||||
System.out.println("发送结果:" + sendResult.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 @KafkaListener注解实现消息的监听
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class KafkaSimpleConsumer {
|
||||
|
||||
// 简单消费者
|
||||
@KafkaListener(groupId = "simpleGroup", topics = Topic.SIMPLE)
|
||||
public void consumer1_1(ConsumerRecord<String, Object> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) {
|
||||
System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic);
|
||||
/*
|
||||
* 如果需要手工提交异步 consumer.commitSync();
|
||||
* 手工同步提交 consumer.commitAsync()
|
||||
*/
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 测试整合结果
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class SendMsgController {
|
||||
|
||||
@Autowired
|
||||
private KafKaCustomrProducer producer;
|
||||
@Autowired
|
||||
private KafkaTemplate kafkaTemplate;
|
||||
|
||||
/***
|
||||
* 发送消息体为基本类型的消息
|
||||
*/
|
||||
@GetMapping("sendSimple")
|
||||
public void sendSimple() {
|
||||
producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、关于多消费者组的测试
|
||||
|
||||
#### 3.1 创建多分区主题
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : kafka配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class KafkaConfig {
|
||||
|
||||
@Bean
|
||||
public NewTopic groupTopic() {
|
||||
// 指定主题名称,分区数量,和复制因子
|
||||
return new NewTopic(Topic.GROUP, 10, (short) 2);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 多消费者组对同一主题的监听
|
||||
|
||||
1. 消费者1-1 监听主题的 0、1 分区
|
||||
2. 消费者1-2 监听主题的 2、3 分区
|
||||
3. 消费者1-3 监听主题的 0、1 分区
|
||||
4. 消费者2-1 监听主题的所有分区
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : kafka 消费者组
|
||||
* <p>
|
||||
* 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class KafkaGroupConsumer {
|
||||
|
||||
// 分组1 中的消费者1
|
||||
@KafkaListener(id = "consumer1-1", groupId = "group1", topicPartitions =
|
||||
{@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
|
||||
})
|
||||
public void consumer1_1(ConsumerRecord<String, Object> record) {
|
||||
System.out.println("consumer1-1 收到消息:" + record.value());
|
||||
}
|
||||
|
||||
// 分组1 中的消费者2
|
||||
@KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions =
|
||||
{@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"})
|
||||
})
|
||||
public void consumer1_2(ConsumerRecord<String, Object> record) {
|
||||
System.out.println("consumer1-2 收到消息:" + record.value());
|
||||
}
|
||||
|
||||
// 分组1 中的消费者3
|
||||
@KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions =
|
||||
{@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
|
||||
})
|
||||
public void consumer1_3(ConsumerRecord<String, Object> record) {
|
||||
System.out.println("consumer1-3 收到消息:" + record.value());
|
||||
}
|
||||
|
||||
// 分组2 中的消费者
|
||||
@KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP)
|
||||
public void consumer2_1(ConsumerRecord<String, Object> record) {
|
||||
System.err.println("consumer2-1 收到消息:" + record.value());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 3.2 发送消息时候指定主题的具体分区
|
||||
|
||||
```java
|
||||
/***
|
||||
* 多消费者组、组中多消费者对同一主题的消费情况
|
||||
*/
|
||||
@GetMapping("sendGroup")
|
||||
public void sendGroup() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
// 第二个参数指定分区,第三个参数指定消息键 分区优先
|
||||
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i);
|
||||
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
log.info("发送消息失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(SendResult<String, Object> sendResult) {
|
||||
System.out.println("发送结果:" + sendResult.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
测试结果:
|
||||
|
||||
```yaml
|
||||
# 主要看每次发送结果中的 partition 属性,代表四次消息分别发送到了主题的0,1,2,3分区
|
||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=1, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 1, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-1@13]
|
||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=0, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 0, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-0@19]
|
||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=3, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 3, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-3@13]
|
||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=2, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 2, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-2@13]
|
||||
# 消费者组2 接收情况
|
||||
consumer2-1 收到消息:hello group 1
|
||||
consumer2-1 收到消息:hello group 0
|
||||
consumer2-1 收到消息:hello group 2
|
||||
consumer2-1 收到消息:hello group 3
|
||||
# 消费者1-1接收情况
|
||||
consumer1-1 收到消息:hello group 1
|
||||
consumer1-1 收到消息:hello group 0
|
||||
# 消费者1-3接收情况
|
||||
consumer1-3 收到消息:hello group 1
|
||||
consumer1-3 收到消息:hello group 0
|
||||
# 消费者1-2接收情况
|
||||
consumer1-2 收到消息:hello group 3
|
||||
consumer1-2 收到消息:hello group 2
|
||||
```
|
||||
|
||||
#### 3.4 测试结果
|
||||
|
||||
1. 和kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响;
|
||||
2. 和kafka原本机制不一样的是,这里我们消费者1-1和消费1-3共同属于同一个消费者组,并且监听同样的分区,按照原本kafka的机制,群组保证每个分区只能被同一个消费者组的一个消费者使用,但是按照spring的声明方式实现的消息监听,这里被两个消费者都监听到了。
|
||||
|
||||
|
||||
|
||||
## 四、序列化与反序列化
|
||||
|
||||
用例采用的是第三方fastjson将实体类序列化为json后发送。实现如下:
|
||||
|
||||
```java
|
||||
/***
|
||||
* 发送消息体为bean的消息
|
||||
*/
|
||||
@GetMapping("sendBean")
|
||||
public void sendBean() {
|
||||
Programmer programmer = new Programmer("xiaoming", 12, 21212.33f, new Date());
|
||||
producer.sendMessage(Topic.BEAN, JSON.toJSON(programmer).toString());
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class KafkaBeanConsumer {
|
||||
|
||||
@KafkaListener(groupId = "beanGroup",topics = Topic.BEAN)
|
||||
public void consumer(ConsumerRecord<String, Object> record) {
|
||||
System.out.println("消费者收到消息:" + JSON.parseObject(record.value().toString(), Programmer.class));
|
||||
}
|
||||
}
|
||||
```
|
||||
## 一、kafka的相关概念:
|
||||
|
||||
### 1.主题和分区
|
||||
|
||||
kafka 的消息通过主题进行分类。一个主题可以被分为若干个分区,一个分区就是一个提交日志。消息以追加的方式写入分区,然后以先入先出的顺序读取。kafka 通过分区来实现数据的冗余和伸缩性,分区可以分布在不同的服务器上,也就是说一个主题可以横跨多个服务器,以此来提供比单个服务器更强大的性能(类比 HDFS 分布式文件系统)。
|
||||
|
||||
注意:由于一个主题包含多个分区,因此无法在整个主题范围内保证消息的顺序性,**但可以保证消息在单个分区内的顺序性**。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka主题和分区.png"/> </div>
|
||||
|
||||
### 2.分区复制
|
||||
|
||||
每个主题被分为若干个分区,每个分区有多个副本。那些副本被保存在 broker 上,每个 broker 可以保存成百上千个属于不同主题和分区的副本。副本有以下两种类型 :
|
||||
|
||||
- 首领副本 每个分区都有一个首领副本 。 为了保证一致性,所有生产者请求和消费者请求都会经过这个副本。
|
||||
- 跟随者副本 首领以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,它们唯一的任务就是从首领那里复制消息,保持与首领一致的状态。如果首领发生崩渍,其中的一个跟随者会被提升为新首领。
|
||||
|
||||
### 3. 生产者
|
||||
|
||||
- 默认情况下生产者在把消息均衡地分布到在主题的所有分区上,而并不关心特定消息会被写到那个分区;
|
||||
- 如果指定消息键,则通过对消息键的散列来实现分区;
|
||||
- 也可以通过消息键和分区器来实现把消息直接写到指定的分区,这个需要自定义分区器,需要实现 Partitioner 接口,并重写其中的 partition 方法。
|
||||
|
||||
### 4. 消费者
|
||||
|
||||
消费者是**消费者群组**的一部分。也就是说,会有一个或者多个消费者共同读取一个主题,群组保证每个分区只能被一个消费者使用。
|
||||
|
||||
**一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组里面的多个消费者读取。多个消费者群组可以共同读取同一个主题,彼此之间互不影响**。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka消费者.png"/> </div>
|
||||
|
||||
### 5.broker和集群
|
||||
|
||||
一个独立的 kafka 服务器被称为 broker。broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。broker 为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘的消息。
|
||||
|
||||
broker 是集群的组成部分。每一个集群都有一个 broker 同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。控制器负责管理工作,包括将分区分配给 broker 和监控 broker。**在集群中,一个分区从属一个 broker,该 broker 被称为分区的首领**。一个分区可以分配给多个 broker,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余,如果有一个 broker 失效,其他 broker 可以接管领导权。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/kafka集群复制.png"/> </div>
|
||||
|
||||
更多 kafka 的说明可以参考我的个人笔记:[《Kafka 权威指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka/%E3%80%8AKafka%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#53-%E5%A4%8D%E5%88%B6)
|
||||
|
||||
|
||||
|
||||
## 二、项目说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
本项目提供 kafka 发送简单消息、对象消息、和多消费者组消费消息三种情况下的 sample。
|
||||
|
||||
1. kafkaSimpleConsumer 用于普通消息的监听;
|
||||
2. kafkaBeanConsumer 用于对象消息监听;
|
||||
3. kafkaGroupConsumer 用于多消费者组和多消费者对主题分区消息监听的情况。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-kafka.png"/> </div>
|
||||
|
||||
#### 1.2 主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、 整合 kafka
|
||||
|
||||
#### 2.1 kafka基本配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
kafka:
|
||||
# 以逗号分隔的地址列表,用于建立与 Kafka 集群的初始连接 (kafka 默认的端口号为 9092)
|
||||
bootstrap-servers: 127.0.0.1:9092
|
||||
producer:
|
||||
# 发生错误后,消息重发的次数。
|
||||
retries: 0
|
||||
#当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
|
||||
batch-size: 16384
|
||||
# 设置生产者内存缓冲区的大小。
|
||||
buffer-memory: 33554432
|
||||
# 键的序列化方式
|
||||
key-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
# 值的序列化方式
|
||||
value-serializer: org.apache.kafka.common.serialization.StringSerializer
|
||||
# acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
|
||||
# acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
|
||||
# acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
|
||||
acks: 1
|
||||
consumer:
|
||||
# 自动提交的时间间隔 在 spring boot 2.X 版本中这里采用的是值的类型为 Duration 需要符合特定的格式,如 1S,1M,2H,5D
|
||||
auto-commit-interval: 1S
|
||||
# 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
|
||||
# latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
|
||||
# earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
|
||||
auto-offset-reset: earliest
|
||||
# 是否自动提交偏移量,默认值是 true,为了避免出现重复数据和数据丢失,可以把它设置为 false,然后手动提交偏移量
|
||||
enable-auto-commit: true
|
||||
# 键的反序列化方式
|
||||
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
# 值的反序列化方式
|
||||
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
|
||||
listener:
|
||||
# 在侦听器容器中运行的线程数。
|
||||
concurrency: 5
|
||||
|
||||
```
|
||||
|
||||
这里需要说明的是:
|
||||
|
||||
在 spring boot 2.X 版本 auto-commit-interval(自动提交的时间间隔)采用的是值的类型为 Duration ,Duration 是 jdk 1.8 版本之后引入的类,在其源码中我们可以看到对于其字符串的表达需要符合一定的规范,即数字 + 单位,如下的写法 1s ,1.5s, 0s, 0.001S ,1h, 2d 在 yaml 中都是有效的。如果传入无效的字符串,则 spring boot 在启动阶段解析配置文件的时候就会抛出异常。
|
||||
|
||||
```java
|
||||
public final class Duration
|
||||
implements TemporalAmount, Comparable<Duration>, Serializable {
|
||||
|
||||
/**
|
||||
* The pattern for parsing.
|
||||
*/
|
||||
private static final Pattern PATTERN =
|
||||
Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" +
|
||||
"(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
........
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 KafkaTemplate实现消息发送
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class KafKaCustomrProducer {
|
||||
|
||||
@Autowired
|
||||
private KafkaTemplate kafkaTemplate;
|
||||
|
||||
public void sendMessage(String topic, Object object) {
|
||||
|
||||
/*
|
||||
* 这里的 ListenableFuture 类是 spring 对 java 原生 Future 的扩展增强,是一个泛型接口,用于监听异步方法的回调
|
||||
* 而对于 kafka send 方法返回值而言,这里的泛型所代表的实际类型就是 SendResult<K, V>,而这里 K,V 的泛型实际上
|
||||
* 被用于 ProducerRecord<K, V> producerRecord,即生产者发送消息的 key,value 类型
|
||||
*/
|
||||
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, object);
|
||||
|
||||
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
log.info("发送消息失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(SendResult<String, Object> sendResult) {
|
||||
System.out.println("发送结果:" + sendResult.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 @KafkaListener注解实现消息的监听
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class KafkaSimpleConsumer {
|
||||
|
||||
// 简单消费者
|
||||
@KafkaListener(groupId = "simpleGroup", topics = Topic.SIMPLE)
|
||||
public void consumer1_1(ConsumerRecord<String, Object> record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) {
|
||||
System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic);
|
||||
/*
|
||||
* 如果需要手工提交异步 consumer.commitSync();
|
||||
* 手工同步提交 consumer.commitAsync()
|
||||
*/
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 测试整合结果
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class SendMsgController {
|
||||
|
||||
@Autowired
|
||||
private KafKaCustomrProducer producer;
|
||||
@Autowired
|
||||
private KafkaTemplate kafkaTemplate;
|
||||
|
||||
/***
|
||||
* 发送消息体为基本类型的消息
|
||||
*/
|
||||
@GetMapping("sendSimple")
|
||||
public void sendSimple() {
|
||||
producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、关于多消费者组的测试
|
||||
|
||||
#### 3.1 创建多分区主题
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : kafka 配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class KafkaConfig {
|
||||
|
||||
@Bean
|
||||
public NewTopic groupTopic() {
|
||||
// 指定主题名称,分区数量,和复制因子
|
||||
return new NewTopic(Topic.GROUP, 10, (short) 2);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 多消费者组对同一主题的监听
|
||||
|
||||
1. 消费者 1-1 监听主题的 0、1 分区
|
||||
2. 消费者 1-2 监听主题的 2、3 分区
|
||||
3. 消费者 1-3 监听主题的 0、1 分区
|
||||
4. 消费者 2-1 监听主题的所有分区
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : kafka 消费者组
|
||||
* <p>
|
||||
* 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class KafkaGroupConsumer {
|
||||
|
||||
// 分组 1 中的消费者 1
|
||||
@KafkaListener(id = "consumer1-1", groupId = "group1", topicPartitions =
|
||||
{@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
|
||||
})
|
||||
public void consumer1_1(ConsumerRecord<String, Object> record) {
|
||||
System.out.println("consumer1-1 收到消息:" + record.value());
|
||||
}
|
||||
|
||||
// 分组 1 中的消费者 2
|
||||
@KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions =
|
||||
{@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"})
|
||||
})
|
||||
public void consumer1_2(ConsumerRecord<String, Object> record) {
|
||||
System.out.println("consumer1-2 收到消息:" + record.value());
|
||||
}
|
||||
|
||||
// 分组 1 中的消费者 3
|
||||
@KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions =
|
||||
{@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
|
||||
})
|
||||
public void consumer1_3(ConsumerRecord<String, Object> record) {
|
||||
System.out.println("consumer1-3 收到消息:" + record.value());
|
||||
}
|
||||
|
||||
// 分组 2 中的消费者
|
||||
@KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP)
|
||||
public void consumer2_1(ConsumerRecord<String, Object> record) {
|
||||
System.err.println("consumer2-1 收到消息:" + record.value());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 3.2 发送消息时候指定主题的具体分区
|
||||
|
||||
```java
|
||||
/***
|
||||
* 多消费者组、组中多消费者对同一主题的消费情况
|
||||
*/
|
||||
@GetMapping("sendGroup")
|
||||
public void sendGroup() {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
// 第二个参数指定分区,第三个参数指定消息键 分区优先
|
||||
ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i);
|
||||
future.addCallback(new ListenableFutureCallback<SendResult<String, Object>>() {
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
log.info("发送消息失败:" + throwable.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(SendResult<String, Object> sendResult) {
|
||||
System.out.println("发送结果:" + sendResult.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
测试结果:
|
||||
|
||||
```yaml
|
||||
# 主要看每次发送结果中的 partition 属性,代表四次消息分别发送到了主题的0,1,2,3分区
|
||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=1, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 1, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-1@13]
|
||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=0, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 0, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-0@19]
|
||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=3, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 3, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-3@13]
|
||||
发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=2, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 2, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-2@13]
|
||||
# 消费者组2 接收情况
|
||||
consumer2-1 收到消息:hello group 1
|
||||
consumer2-1 收到消息:hello group 0
|
||||
consumer2-1 收到消息:hello group 2
|
||||
consumer2-1 收到消息:hello group 3
|
||||
# 消费者1-1接收情况
|
||||
consumer1-1 收到消息:hello group 1
|
||||
consumer1-1 收到消息:hello group 0
|
||||
# 消费者1-3接收情况
|
||||
consumer1-3 收到消息:hello group 1
|
||||
consumer1-3 收到消息:hello group 0
|
||||
# 消费者1-2接收情况
|
||||
consumer1-2 收到消息:hello group 3
|
||||
consumer1-2 收到消息:hello group 2
|
||||
```
|
||||
|
||||
#### 3.4 测试结果
|
||||
|
||||
1. 和 kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响;
|
||||
2. 和 kafka 原本机制不一样的是,这里我们消费者 1-1 和消费 1-3 共同属于同一个消费者组,并且监听同样的分区,按照原本 kafka 的机制,群组保证每个分区只能被同一个消费者组的一个消费者使用,但是按照 spring 的声明方式实现的消息监听,这里被两个消费者都监听到了。
|
||||
|
||||
|
||||
|
||||
## 四、序列化与反序列化
|
||||
|
||||
用例采用的是第三方 fastjson 将实体类序列化为 json 后发送。实现如下:
|
||||
|
||||
```java
|
||||
/***
|
||||
* 发送消息体为 bean 的消息
|
||||
*/
|
||||
@GetMapping("sendBean")
|
||||
public void sendBean() {
|
||||
Programmer programmer = new Programmer("xiaoming", 12, 21212.33f, new Date());
|
||||
producer.sendMessage(Topic.BEAN, JSON.toJSON(programmer).toString());
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class KafkaBeanConsumer {
|
||||
|
||||
@KafkaListener(groupId = "beanGroup",topics = Topic.BEAN)
|
||||
public void consumer(ConsumerRecord<String, Object> record) {
|
||||
System.out.println("消费者收到消息:" + JSON.parseObject(record.value().toString(), Programmer.class));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 mecached
|
||||
|
||||
# spring boot 整合 mecached
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11--XMemcached客户端说明">1.1 XMemcached客户端说明</a><br/>
|
||||
<a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
|
||||
@@ -15,149 +16,149 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
spring boot 官方并没有提供关于 memcached 的starter,所以我们这里还是采用XMemcached作为客户端进行整合。 XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
memcached的整合配置位于config文件夹下。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-memcached.png"/> </div>
|
||||
|
||||
### 1.3 主要依赖
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring boot 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClient() {
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClientForCluster() {
|
||||
|
||||
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11211));
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
|
||||
// 赋予权重
|
||||
int[] weights = {1, 2};
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
|
||||
// 设置连接池大小
|
||||
builder.setConnectionPoolSize(10);
|
||||
// 协议工厂
|
||||
builder.setCommandFactory(new TextCommandFactory());
|
||||
// 分布策略,一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
|
||||
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
|
||||
// 设置序列化器
|
||||
builder.setTranscoder(new SerializingTranscoder());
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached单机版本和集群版本注入的实例是相同的。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
|
||||
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
spring boot 官方并没有提供关于 memcached 的 starter,所以我们这里还是采用 XMemcached 作为客户端进行整合。 XMemcached 是基于 java nio 的 memcached 高性能客户端,支持完整的 memcached 协议,支持客户端分布并且提供了一致性哈希 (consistent hash) 算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
memcached 的整合配置位于 config 文件夹下。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-memcached.png"/> </div>
|
||||
|
||||
### 1.3 主要依赖
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring boot 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClient() {
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClientForCluster() {
|
||||
|
||||
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11211));
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
|
||||
// 赋予权重
|
||||
int[] weights = {1, 2};
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
|
||||
// 设置连接池大小
|
||||
builder.setConnectionPoolSize(10);
|
||||
// 协议工厂
|
||||
builder.setCommandFactory(new TextCommandFactory());
|
||||
// 分布策略,一致性哈希 KetamaMemcachedSessionLocator 或者 ArraySessionLocator(默认)
|
||||
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
|
||||
// 设置序列化器
|
||||
builder.setTranscoder(new SerializingTranscoder());
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached 单机版本和集群版本注入的实例是相同的。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当 key 不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在 key 的 value 值 |
|
||||
| 追加内容 append | append key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 前面追加 prepend | prepend key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr 和 decr 只能操作能转换为数字的 Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr 不能将数字减少至 0 以下 |
|
||||
| 清库 | flush_all | |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 mongodb
|
||||
|
||||
# spring boot 整合 mongodb
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-用例结构">1.1 用例结构</a><br/>
|
||||
<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
|
||||
@@ -12,183 +13,183 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 用例结构
|
||||
|
||||
1. 本用例提供mongdb的简单整合用例;
|
||||
2. 提供用MongoTemplate的方式操作mongdb,见测试用例MongoOriginalTests.java
|
||||
3. 提供基于spring data jpa 的方式操作mongodb(推荐),见测试用例MongoJPATests.java
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mongodb.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、整合 mongodb
|
||||
|
||||
#### 2.1 在application.yml 中配置mongodb数据源
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
data:
|
||||
mongodb:
|
||||
database: spring
|
||||
uri: mongodb://192.168.0.108:27017
|
||||
```
|
||||
|
||||
#### 2.2 基于MongoTemplate实现对mongodb的操作
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MongoOriginalTests {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 使用 data jpa 方式操作mongodb (推荐使用)
|
||||
|
||||
1.新建查询结构,查询方法按照支持的关键字命名
|
||||
|
||||
```java
|
||||
public interface ProgrammerRepository extends MongoRepository<Programmer, String> {
|
||||
|
||||
void deleteAllByName(String name);
|
||||
|
||||
Programmer findAllByName(String names);
|
||||
|
||||
Programmer findByNameAndAge(String name, int age);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
2.测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MongoJPATests {
|
||||
|
||||
@Autowired
|
||||
private ProgrammerRepository repository;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
repository.save(new Programmer("python", 23, 21832.34f, new Date()));
|
||||
// 批量插入
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
programmers.add(new Programmer("java", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
|
||||
repository.saveAll(programmers);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Programmer java = repository.findByNameAndAge("java", 21);
|
||||
Assert.assertEquals(java.getSalary(), 52200.21f, 0.01);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
repository.save(new Programmer("Go", 8, 500.21f, new Date()));
|
||||
Programmer go = repository.findAllByName("Go");
|
||||
Assert.assertEquals(go.getAge(), 8);
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
repository.deleteAllByName("python");
|
||||
Optional<Programmer> python = repository.findById("python");
|
||||
Assert.assertFalse(python.isPresent());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
查询方法支持的关键字如下,更多命名规范可以参见Spring Data MongoDB官方文档[Query Methods](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongodb.repositories.queries):
|
||||
|
||||
| Keyword | Sample | Logical result |
|
||||
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| `After` | `findByBirthdateAfter(Date date)` | `{"birthdate" : {"$gt" : date}}` |
|
||||
| `GreaterThan` | `findByAgeGreaterThan(int age)` | `{"age" : {"$gt" : age}}` |
|
||||
| `GreaterThanEqual` | `findByAgeGreaterThanEqual(int age)` | `{"age" : {"$gte" : age}}` |
|
||||
| `Before` | `findByBirthdateBefore(Date date)` | `{"birthdate" : {"$lt" : date}}` |
|
||||
| `LessThan` | `findByAgeLessThan(int age)` | `{"age" : {"$lt" : age}}` |
|
||||
| `LessThanEqual` | `findByAgeLessThanEqual(int age)` | `{"age" : {"$lte" : age}}` |
|
||||
| `Between` | `findByAgeBetween(int from, int to)` | `{"age" : {"$gt" : from, "$lt" : to}}` |
|
||||
| `In` | `findByAgeIn(Collection ages)` | `{"age" : {"$in" : [ages…]}}` |
|
||||
| `NotIn` | `findByAgeNotIn(Collection ages)` | `{"age" : {"$nin" : [ages…]}}` |
|
||||
| `IsNotNull`, `NotNull` | `findByFirstnameNotNull()` | `{"firstname" : {"$ne" : null}}` |
|
||||
| `IsNull`, `Null` | `findByFirstnameNull()` | `{"firstname" : null}` |
|
||||
| `Like`, `StartingWith`, `EndingWith` | `findByFirstnameLike(String name)` | `{"firstname" : name} (name as regex)` |
|
||||
| `NotLike`, `IsNotLike` | `findByFirstnameNotLike(String name)` | `{"firstname" : { "$not" : name }} (name as regex)` |
|
||||
| `Containing` on String | `findByFirstnameContaining(String name)` | `{"firstname" : name} (name as regex)` |
|
||||
| `NotContaining` on String | `findByFirstnameNotContaining(String name)` | `{"firstname" : { "$not" : name}} (name as regex)` |
|
||||
| `Containing` on Collection | `findByAddressesContaining(Address address)` | `{"addresses" : { "$in" : address}}` |
|
||||
| `NotContaining` on Collection | `findByAddressesNotContaining(Address address)` | `{"addresses" : { "$not" : { "$in" : address}}}` |
|
||||
| `Regex` | `findByFirstnameRegex(String firstname)` | `{"firstname" : {"$regex" : firstname }}` |
|
||||
| `(No keyword)` | `findByFirstname(String name)` | `{"firstname" : name}` |
|
||||
| `Not` | `findByFirstnameNot(String name)` | `{"firstname" : {"$ne" : name}}` |
|
||||
| `Near` | `findByLocationNear(Point point)` | `{"location" : {"$near" : [x,y]}}` |
|
||||
| `Near` | `findByLocationNear(Point point, Distance max)` | `{"location" : {"$near" : [x,y], "$maxDistance" : max}}` |
|
||||
| `Near` | `findByLocationNear(Point point, Distance min, Distance max)` | `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}` |
|
||||
| `Within` | `findByLocationWithin(Circle circle)` | `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}` |
|
||||
| `Within` | `findByLocationWithin(Box box)` | `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}` |
|
||||
| `IsTrue`, `True` | `findByActiveIsTrue()` | `{"active" : true}` |
|
||||
| `IsFalse`, `False` | `findByActiveIsFalse()` | `{"active" : false}` |
|
||||
| `Exists` | `findByLocationExists(boolean exists)` | `{"location" : {"$exists" : exists }}` |
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 用例结构
|
||||
|
||||
1. 本用例提供 mongdb 的简单整合用例;
|
||||
2. 提供用 MongoTemplate 的方式操作 mongdb,见测试用例 MongoOriginalTests.java
|
||||
3. 提供基于 spring data jpa 的方式操作 mongodb(推荐),见测试用例 MongoJPATests.java
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mongodb.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、整合 mongodb
|
||||
|
||||
#### 2.1 在application.yml 中配置mongodb数据源
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
data:
|
||||
mongodb:
|
||||
database: spring
|
||||
uri: mongodb://192.168.0.108:27017
|
||||
```
|
||||
|
||||
#### 2.2 基于MongoTemplate实现对mongodb的操作
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MongoOriginalTests {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 使用 data jpa 方式操作mongodb (推荐使用)
|
||||
|
||||
1.新建查询结构,查询方法按照支持的关键字命名
|
||||
|
||||
```java
|
||||
public interface ProgrammerRepository extends MongoRepository<Programmer, String> {
|
||||
|
||||
void deleteAllByName(String name);
|
||||
|
||||
Programmer findAllByName(String names);
|
||||
|
||||
Programmer findByNameAndAge(String name, int age);
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
2.测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MongoJPATests {
|
||||
|
||||
@Autowired
|
||||
private ProgrammerRepository repository;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
repository.save(new Programmer("python", 23, 21832.34f, new Date()));
|
||||
// 批量插入
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
programmers.add(new Programmer("java", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
|
||||
repository.saveAll(programmers);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Programmer java = repository.findByNameAndAge("java", 21);
|
||||
Assert.assertEquals(java.getSalary(), 52200.21f, 0.01);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
repository.save(new Programmer("Go", 8, 500.21f, new Date()));
|
||||
Programmer go = repository.findAllByName("Go");
|
||||
Assert.assertEquals(go.getAge(), 8);
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
repository.deleteAllByName("python");
|
||||
Optional<Programmer> python = repository.findById("python");
|
||||
Assert.assertFalse(python.isPresent());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
查询方法支持的关键字如下,更多命名规范可以参见 Spring Data MongoDB 官方文档[Query Methods](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongodb.repositories.queries):
|
||||
|
||||
| Keyword | Sample | Logical result |
|
||||
| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| `After` | `findByBirthdateAfter(Date date)` | `{"birthdate" : {"$gt" : date}}` |
|
||||
| `GreaterThan` | `findByAgeGreaterThan(int age)` | `{"age" : {"$gt" : age}}` |
|
||||
| `GreaterThanEqual` | `findByAgeGreaterThanEqual(int age)` | `{"age" : {"$gte" : age}}` |
|
||||
| `Before` | `findByBirthdateBefore(Date date)` | `{"birthdate" : {"$lt" : date}}` |
|
||||
| `LessThan` | `findByAgeLessThan(int age)` | `{"age" : {"$lt" : age}}` |
|
||||
| `LessThanEqual` | `findByAgeLessThanEqual(int age)` | `{"age" : {"$lte" : age}}` |
|
||||
| `Between` | `findByAgeBetween(int from, int to)` | `{"age" : {"$gt" : from, "$lt" : to}}` |
|
||||
| `In` | `findByAgeIn(Collection ages)` | `{"age" : {"$in" : [ages…]}}` |
|
||||
| `NotIn` | `findByAgeNotIn(Collection ages)` | `{"age" : {"$nin" : [ages…]}}` |
|
||||
| `IsNotNull`, `NotNull` | `findByFirstnameNotNull()` | `{"firstname" : {"$ne" : null}}` |
|
||||
| `IsNull`, `Null` | `findByFirstnameNull()` | `{"firstname" : null}` |
|
||||
| `Like`, `StartingWith`, `EndingWith` | `findByFirstnameLike(String name)` | `{"firstname" : name} (name as regex)` |
|
||||
| `NotLike`, `IsNotLike` | `findByFirstnameNotLike(String name)` | `{"firstname" : { "$not" : name }} (name as regex)` |
|
||||
| `Containing` on String | `findByFirstnameContaining(String name)` | `{"firstname" : name} (name as regex)` |
|
||||
| `NotContaining` on String | `findByFirstnameNotContaining(String name)` | `{"firstname" : { "$not" : name}} (name as regex)` |
|
||||
| `Containing` on Collection | `findByAddressesContaining(Address address)` | `{"addresses" : { "$in" : address}}` |
|
||||
| `NotContaining` on Collection | `findByAddressesNotContaining(Address address)` | `{"addresses" : { "$not" : { "$in" : address}}}` |
|
||||
| `Regex` | `findByFirstnameRegex(String firstname)` | `{"firstname" : {"$regex" : firstname }}` |
|
||||
| `(No keyword)` | `findByFirstname(String name)` | `{"firstname" : name}` |
|
||||
| `Not` | `findByFirstnameNot(String name)` | `{"firstname" : {"$ne" : name}}` |
|
||||
| `Near` | `findByLocationNear(Point point)` | `{"location" : {"$near" : [x,y]}}` |
|
||||
| `Near` | `findByLocationNear(Point point, Distance max)` | `{"location" : {"$near" : [x,y], "$maxDistance" : max}}` |
|
||||
| `Near` | `findByLocationNear(Point point, Distance min, Distance max)` | `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}` |
|
||||
| `Within` | `findByLocationWithin(Circle circle)` | `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}` |
|
||||
| `Within` | `findByLocationWithin(Box box)` | `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}` |
|
||||
| `IsTrue`, `True` | `findByActiveIsTrue()` | `{"active" : true}` |
|
||||
| `IsFalse`, `False` | `findByActiveIsFalse()` | `{"active" : false}` |
|
||||
| `Exists` | `findByLocationExists(boolean exists)` | `{"location" : {"$exists" : exists }}` |
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 mybatis
|
||||
|
||||
# spring boot 整合 mybatis
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
|
||||
@@ -12,250 +13,250 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下;
|
||||
|
||||
2. 关于mybatis sql的写法提供两种方式:
|
||||
|
||||
xml 写法:对应的类为ProgrammerMapper.java 和 programmerMapper.xml,用MybatisXmlTest进行测试;
|
||||
|
||||
注解写法:对应的类为Programmer.java ,用MybatisAnnotationTest进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mybatis.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
需要说明的是按照spring 官方对应自定义的starter 命名规范的推荐:
|
||||
|
||||
- 官方的starter命名:spring-boot-starter-XXXX
|
||||
- 其他第三方starter命名:XXXX-spring-boot-starte
|
||||
|
||||
所以mybatis的starter命名为mybatis-spring-boot-starter,如果有自定义starter需求,也需要按照此命名规则进行命名。
|
||||
|
||||
```xml
|
||||
<!--spring 1.5 x 以上版本对应 mybatis 1.3.x (1.3.1)
|
||||
关于更多spring-boot 与 mybatis 的版本对应可以参见 <a href="http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/">-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<!--引入mysql驱动-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
spring boot 与 mybatis 版本的对应关系:
|
||||
|
||||
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
|
||||
| --------------------------- | ------------------------------------------------------------ | ------------- |
|
||||
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
|
||||
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
|
||||
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
|
||||
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
|
||||
|
||||
## 二、整合 mybatis
|
||||
|
||||
#### 2.1 在application.yml 中配置数据源
|
||||
|
||||
spring boot 2.x 版本默认采用Hikari作为数据库连接池,Hikari是目前java平台性能最好的连接池,性能好于druid。
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
# 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
|
||||
# spring-boot 2 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
hikari:
|
||||
# 池中维护的最小空闲连接数
|
||||
minimum-idle: 10
|
||||
# 池中最大连接数,包括闲置和使用中的连接
|
||||
maximum-pool-size: 20
|
||||
# 此属性控制从池返回的连接的默认自动提交行为。默认为true
|
||||
auto-commit: true
|
||||
# 允许最长空闲时间
|
||||
idle-timeout: 30000
|
||||
# 此属性表示连接池的用户定义名称,主要显示在日志记录和JMX管理控制台中,以标识池和池配置。 默认值:自动生成
|
||||
pool-name: custom-hikari
|
||||
#此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
|
||||
max-lifetime: 1800000
|
||||
# 数据库连接超时时间,默认30秒,即30000
|
||||
connection-timeout: 30000
|
||||
# 连接测试sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成 select 1 from dual
|
||||
connection-test-query: SELECT 1
|
||||
|
||||
# mybatis 相关配置
|
||||
mybatis:
|
||||
# 指定 sql xml 文件的位置
|
||||
mapper-locations: classpath*:mappers/*.xml
|
||||
configuration:
|
||||
# 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
|
||||
# oracle数据库建议配置为JdbcType.NULL, 默认是Other
|
||||
jdbc-type-for-null: 'null'
|
||||
# 是否打印sql语句 调试的时候可以开启
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
```
|
||||
|
||||
#### 2.2 xml方式的sql语句
|
||||
|
||||
新建 ProgrammerMapper.java 和 programmerMapper.xml,及其测试类
|
||||
|
||||
```java
|
||||
@Mapper
|
||||
public interface ProgrammerMapper {
|
||||
|
||||
void save(Programmer programmer);
|
||||
|
||||
Programmer selectById(int id);
|
||||
|
||||
int modify(Programmer programmer);
|
||||
|
||||
void delete(int id);
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="com.heibaiying.springboot.dao.ProgrammerMapper">
|
||||
|
||||
<insert id="save">
|
||||
insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})
|
||||
</insert>
|
||||
|
||||
<select id="selectById" resultType="com.heibaiying.springboot.bean.Programmer">
|
||||
select * from programmer where name = #{id}
|
||||
</select>
|
||||
|
||||
<update id="modify">
|
||||
update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}
|
||||
</update>
|
||||
|
||||
<delete id="delete">
|
||||
delete from programmer where id = #{id}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
测试类
|
||||
|
||||
```java
|
||||
/***
|
||||
* @description: xml Sql测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MybatisXmlTest {
|
||||
|
||||
@Autowired
|
||||
private ProgrammerMapper mapper;
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
|
||||
mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modify() {
|
||||
mapper.modify(new Programmer(1, "xiaohong", 112, 347.34f, new Date()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectByCondition() {
|
||||
Programmer programmers = mapper.selectById(1);
|
||||
System.out.println(programmers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() {
|
||||
mapper.delete(2);
|
||||
Programmer programmers = mapper.selectById(2);
|
||||
Assert.assertNull(programmers);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 注解方式的sql语句
|
||||
|
||||
```java
|
||||
@Mapper
|
||||
public interface ProgrammerDao {
|
||||
|
||||
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
|
||||
void save(Programmer programmer);
|
||||
|
||||
@Select("select * from programmer where name = #{id}")
|
||||
Programmer selectById(int id);
|
||||
|
||||
@Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
|
||||
int modify(Programmer programmer);
|
||||
|
||||
@Delete(" delete from programmer where id = #{id}")
|
||||
void delete(int id);
|
||||
}
|
||||
```
|
||||
|
||||
测试类
|
||||
|
||||
```java
|
||||
/***
|
||||
* @description: 注解Sql测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MybatisAnnotationTest {
|
||||
|
||||
@Autowired
|
||||
private ProgrammerDao programmerDao;
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
|
||||
programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modify() {
|
||||
programmerDao.modify(new Programmer(1, "xiaolan", 21, 347.34f, new Date()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectByCondition() {
|
||||
Programmer programmers = programmerDao.selectById(1);
|
||||
System.out.println(programmers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() {
|
||||
programmerDao.delete(3);
|
||||
Programmer programmers = programmerDao.selectById(3);
|
||||
Assert.assertNull(programmers);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
1. 项目查询用的表对应的建表语句放置在 resources 的 sql 文件夹下;
|
||||
|
||||
2. 关于 mybatis sql 的写法提供两种方式:
|
||||
|
||||
xml 写法:对应的类为 ProgrammerMapper.java 和 programmerMapper.xml,用 MybatisXmlTest 进行测试;
|
||||
|
||||
注解写法:对应的类为 Programmer.java ,用 MybatisAnnotationTest 进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-mybatis.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
需要说明的是按照 spring 官方对应自定义的 starter 命名规范的推荐:
|
||||
|
||||
- 官方的 starter 命名:spring-boot-starter-XXXX
|
||||
- 其他第三方 starter 命名:XXXX-spring-boot-starte
|
||||
|
||||
所以 mybatis 的 starter 命名为 mybatis-spring-boot-starter,如果有自定义 starter 需求,也需要按照此命名规则进行命名。
|
||||
|
||||
```xml
|
||||
<!--spring 1.5 x 以上版本对应 mybatis 1.3.x (1.3.1)
|
||||
关于更多 spring-boot 与 mybatis 的版本对应可以参见 <a href="http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/">-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<!--引入mysql驱动-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
spring boot 与 mybatis 版本的对应关系:
|
||||
|
||||
| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
|
||||
| --------------------------- | ------------------------------------------------------------ | ------------- |
|
||||
| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
|
||||
| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
|
||||
| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
|
||||
| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
|
||||
|
||||
## 二、整合 mybatis
|
||||
|
||||
#### 2.1 在application.yml 中配置数据源
|
||||
|
||||
spring boot 2.x 版本默认采用 Hikari 作为数据库连接池,Hikari 是目前 java 平台性能最好的连接池,性能好于 druid。
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
|
||||
username: root
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
# 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
|
||||
# spring-boot 2 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
hikari:
|
||||
# 池中维护的最小空闲连接数
|
||||
minimum-idle: 10
|
||||
# 池中最大连接数,包括闲置和使用中的连接
|
||||
maximum-pool-size: 20
|
||||
# 此属性控制从池返回的连接的默认自动提交行为。默认为 true
|
||||
auto-commit: true
|
||||
# 允许最长空闲时间
|
||||
idle-timeout: 30000
|
||||
# 此属性表示连接池的用户定义名称,主要显示在日志记录和 JMX 管理控制台中,以标识池和池配置。 默认值:自动生成
|
||||
pool-name: custom-hikari
|
||||
#此属性控制池中连接的最长生命周期,值 0 表示无限生命周期,默认 1800000 即 30 分钟
|
||||
max-lifetime: 1800000
|
||||
# 数据库连接超时时间,默认 30 秒,即 30000
|
||||
connection-timeout: 30000
|
||||
# 连接测试 sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成 select 1 from dual
|
||||
connection-test-query: SELECT 1
|
||||
|
||||
# mybatis 相关配置
|
||||
mybatis:
|
||||
# 指定 sql xml 文件的位置
|
||||
mapper-locations: classpath*:mappers/*.xml
|
||||
configuration:
|
||||
# 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
|
||||
# oracle 数据库建议配置为 JdbcType.NULL, 默认是 Other
|
||||
jdbc-type-for-null: 'null'
|
||||
# 是否打印 sql 语句 调试的时候可以开启
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
```
|
||||
|
||||
#### 2.2 xml方式的sql语句
|
||||
|
||||
新建 ProgrammerMapper.java 和 programmerMapper.xml,及其测试类
|
||||
|
||||
```java
|
||||
@Mapper
|
||||
public interface ProgrammerMapper {
|
||||
|
||||
void save(Programmer programmer);
|
||||
|
||||
Programmer selectById(int id);
|
||||
|
||||
int modify(Programmer programmer);
|
||||
|
||||
void delete(int id);
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="com.heibaiying.springboot.dao.ProgrammerMapper">
|
||||
|
||||
<insert id="save">
|
||||
insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})
|
||||
</insert>
|
||||
|
||||
<select id="selectById" resultType="com.heibaiying.springboot.bean.Programmer">
|
||||
select * from programmer where name = #{id}
|
||||
</select>
|
||||
|
||||
<update id="modify">
|
||||
update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}
|
||||
</update>
|
||||
|
||||
<delete id="delete">
|
||||
delete from programmer where id = #{id}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
测试类
|
||||
|
||||
```java
|
||||
/***
|
||||
* @description: xml Sql 测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MybatisXmlTest {
|
||||
|
||||
@Autowired
|
||||
private ProgrammerMapper mapper;
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
|
||||
mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modify() {
|
||||
mapper.modify(new Programmer(1, "xiaohong", 112, 347.34f, new Date()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectByCondition() {
|
||||
Programmer programmers = mapper.selectById(1);
|
||||
System.out.println(programmers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() {
|
||||
mapper.delete(2);
|
||||
Programmer programmers = mapper.selectById(2);
|
||||
Assert.assertNull(programmers);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 注解方式的sql语句
|
||||
|
||||
```java
|
||||
@Mapper
|
||||
public interface ProgrammerDao {
|
||||
|
||||
@Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
|
||||
void save(Programmer programmer);
|
||||
|
||||
@Select("select * from programmer where name = #{id}")
|
||||
Programmer selectById(int id);
|
||||
|
||||
@Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
|
||||
int modify(Programmer programmer);
|
||||
|
||||
@Delete(" delete from programmer where id = #{id}")
|
||||
void delete(int id);
|
||||
}
|
||||
```
|
||||
|
||||
测试类
|
||||
|
||||
```java
|
||||
/***
|
||||
* @description: 注解 Sql 测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class MybatisAnnotationTest {
|
||||
|
||||
@Autowired
|
||||
private ProgrammerDao programmerDao;
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
|
||||
programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modify() {
|
||||
programmerDao.modify(new Programmer(1, "xiaolan", 21, 347.34f, new Date()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectByCondition() {
|
||||
Programmer programmers = programmerDao.selectById(1);
|
||||
System.out.println(programmers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete() {
|
||||
programmerDao.delete(3);
|
||||
Programmer programmers = programmerDao.selectById(3);
|
||||
Assert.assertNull(programmers);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 rabbitmq
|
||||
|
||||
# spring boot 整合 rabbitmq
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
|
||||
<a href="#二关键依赖">二、关键依赖</a><br/>
|
||||
<a href="#三公共模块rabbitmq-common">三、公共模块(rabbitmq-common)</a><br/>
|
||||
@@ -16,391 +17,391 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 之前关于spring 整合 rabbitmq 我们采用的是单项目的方式,为了使得用例更具有实际意义,这里采用maven多模块的构建方式,在spring-boot-rabbitmq下构建三个子模块:
|
||||
|
||||
1. rabbitmq-common 是公共模块,用于存放公共的接口、配置和bean,被rabbitmq-producer和rabbitmq-consumer在pom.xml中引用;
|
||||
2. rabbitmq-producer 是消息的生产者模块;
|
||||
3. rabbitmq-consumer是消息的消费者模块。
|
||||
|
||||
1.2 关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-rabbitmq.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、关键依赖
|
||||
|
||||
在父工程的项目中统一导入依赖rabbitmq的starter(spring-boot-starter-amqp),父工程的pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>rabbitmq-consumer</module>
|
||||
<module>rabbitmq-producer</module>
|
||||
<module>rabbitmq-common</module>
|
||||
</modules>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-rabbitmq</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-boot-rabbitmq</name>
|
||||
<description>RabbitMQ project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(rabbitmq-common)
|
||||
|
||||
- bean 下为公共的实体类。
|
||||
- constant 下为公共配置,用静态常量引用。(这里我使用静态常量是为了方便引用,实际中也可以按照情况,抽取为公共配置文件)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-common.png"/> </div>
|
||||
|
||||
```java
|
||||
package com.heibaiying.constant;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : rabbit 公用配置信息
|
||||
*/
|
||||
public class RabbitInfo {
|
||||
|
||||
// queue 配置
|
||||
public static final String QUEUE_NAME = "spring.boot.simple.queue";
|
||||
public static final String QUEUE_DURABLE = "true";
|
||||
|
||||
// exchange 配置
|
||||
public static final String EXCHANGE_NAME = "spring.boot.simple.exchange";
|
||||
public static final String EXCHANGE_TYPE = "topic";
|
||||
|
||||
// routing key
|
||||
public static final String ROUTING_KEY = "springboot.simple.*";
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、服务消费者(rabbitmq-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-consumer.png"/> </div>
|
||||
|
||||
#### 4.1 消息消费者配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
rabbitmq:
|
||||
addresses: 127.0.0.1:5672
|
||||
# RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
|
||||
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
|
||||
username: guest
|
||||
password: guest
|
||||
virtual-host: /
|
||||
listener:
|
||||
simple:
|
||||
# 为了保证信息能够被正确消费,建议签收模式设置为手工签收,并在代码中实现手工签收
|
||||
acknowledge-mode: manual
|
||||
# 侦听器调用者线程的最小数量
|
||||
concurrency: 10
|
||||
# 侦听器调用者线程的最大数量
|
||||
max-concurrency: 50
|
||||
```
|
||||
|
||||
#### 4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者
|
||||
|
||||
1. 使用注解创建的交换机、队列、和绑定关系会在项目初始化的时候自动创建,但是不会重复创建;
|
||||
2. 这里我们创建两个消息监听器,分别演示消息是基本类型和消息是对象时的配置区别。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 消息是对象的消费者
|
||||
*/
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RabbitmqBeanConsumer {
|
||||
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
value = @Queue(value = RabbitBeanInfo.QUEUE_NAME, durable = RabbitBeanInfo.QUEUE_DURABLE),
|
||||
exchange = @Exchange(value = RabbitBeanInfo.EXCHANGE_NAME, type = RabbitBeanInfo.EXCHANGE_TYPE),
|
||||
key = RabbitBeanInfo.ROUTING_KEY)
|
||||
)
|
||||
@RabbitHandler
|
||||
public void onMessage(@Payload Programmer programmer, @Headers Map<String, Object> headers, Channel channel) throws Exception {
|
||||
log.info("programmer:{} ", programmer);
|
||||
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
|
||||
channel.basicAck(deliveryTag, false);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RabbitmqConsumer {
|
||||
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
value = @Queue(value = RabbitInfo.QUEUE_NAME, durable = RabbitInfo.QUEUE_DURABLE),
|
||||
exchange = @Exchange(value = RabbitInfo.EXCHANGE_NAME, type = RabbitInfo.EXCHANGE_TYPE),
|
||||
key = RabbitInfo.ROUTING_KEY)
|
||||
)
|
||||
@RabbitHandler
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
MessageHeaders headers = message.getHeaders();
|
||||
// 获取消息头信息和消息体
|
||||
log.info("msgInfo:{} ; payload:{} ", headers.get("msgInfo"), message.getPayload());
|
||||
// DELIVERY_TAG 代表 RabbitMQ 向该Channel投递的这条消息的唯一标识ID,是一个单调递增的正整数
|
||||
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
|
||||
// 第二个参数代表是否一次签收多条,当该参数为 true 时,则可以一次性确认 DELIVERY_TAG 小于等于传入值的所有消息
|
||||
channel.basicAck(deliveryTag, false);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 五、 消息生产者(rabbitmq-producer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-producer.png"/> </div>
|
||||
|
||||
#### 5.1 消息生产者配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
rabbitmq:
|
||||
addresses: 127.0.0.1:5672
|
||||
# RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
|
||||
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
|
||||
username: guest
|
||||
password: guest
|
||||
virtual-host: /
|
||||
# 是否启用发布者确认 具体确认回调实现见代码
|
||||
publisher-confirms: true
|
||||
# 是否启用发布者返回 具体返回回调实现见代码
|
||||
publisher-returns: true
|
||||
# 是否启用强制消息 保证消息的有效监听
|
||||
template.mandatory: true
|
||||
|
||||
server:
|
||||
port: 8090
|
||||
```
|
||||
|
||||
#### 5.2 创建消息生产者
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 消息生产者
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RabbitmqProducer {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
public void sendSimpleMessage(Map<String, Object> headers, Object message,
|
||||
String messageId, String exchangeName, String key) {
|
||||
// 自定义消息头
|
||||
MessageHeaders messageHeaders = new MessageHeaders(headers);
|
||||
// 创建消息
|
||||
Message<Object> msg = MessageBuilder.createMessage(message, messageHeaders);
|
||||
/* 确认的回调 确认消息是否到达 Broker 服务器 其实就是是否到达交换器
|
||||
如果发送时候指定的交换器不存在 ack就是false 代表消息不可达 */
|
||||
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
|
||||
log.info("correlationData:{} , ack:{}", correlationData.getId(), ack);
|
||||
if (!ack) {
|
||||
System.out.println("进行对应的消息补偿机制");
|
||||
}
|
||||
});
|
||||
/* 消息失败的回调
|
||||
* 例如消息已经到达交换器上,但路由键匹配任何绑定到该交换器的队列,会触发这个回调,此时 replyText: NO_ROUTE
|
||||
*/
|
||||
rabbitTemplate.setReturnCallback((message1, replyCode, replyText, exchange, routingKey) -> {
|
||||
log.info("message:{}; replyCode: {}; replyText: {} ; exchange:{} ; routingKey:{}",
|
||||
message1, replyCode, replyText, exchange, routingKey);
|
||||
});
|
||||
// 在实际中ID 应该是全局唯一 能够唯一标识消息 消息不可达的时候触发ConfirmCallback回调方法时可以获取该值,进行对应的错误处理
|
||||
CorrelationData correlationData = new CorrelationData(messageId);
|
||||
rabbitTemplate.convertAndSend(exchangeName, key, msg, correlationData);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.3 以单元测试的方式发送消息
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class RabbitmqProducerTests {
|
||||
|
||||
@Autowired
|
||||
private RabbitmqProducer producer;
|
||||
|
||||
/***
|
||||
* 发送消息体为简单数据类型的消息
|
||||
*/
|
||||
@Test
|
||||
public void send() {
|
||||
Map<String, Object> heads = new HashMap<>();
|
||||
heads.put("msgInfo", "自定义消息头信息");
|
||||
// 模拟生成消息ID,在实际中应该是全局唯一的 消息不可达时候可以在setConfirmCallback回调中取得,可以进行对应的重发或错误处理
|
||||
String id = String.valueOf(Math.round(Math.random() * 10000));
|
||||
producer.sendSimpleMessage(heads, "hello Spring", id, RabbitInfo.EXCHANGE_NAME, "springboot.simple.abc");
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 发送消息体为bean的消息
|
||||
*/
|
||||
@Test
|
||||
public void sendBean() {
|
||||
String id = String.valueOf(Math.round(Math.random() * 10000));
|
||||
Programmer programmer = new Programmer("xiaoMing", 12, 12123.45f, new Date());
|
||||
producer.sendSimpleMessage(null, programmer, id, RabbitBeanInfo.EXCHANGE_NAME, RabbitBeanInfo.ROUTING_KEY);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer和producer模块均依赖公共模块,所以在构建consumer和producer项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer中 pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-rabbitmq</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>rabbitmq-consumer</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>rabbitmq-consumer</name>
|
||||
<description>RabbitMQ consumer project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>rabbitmq-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
```
|
||||
|
||||
producer中 pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-rabbitmq</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>rabbitmq-producer</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>rabbitmq-producer</name>
|
||||
<description>RabbitMQ producer project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>rabbitmq-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 之前关于 spring 整合 rabbitmq 我们采用的是单项目的方式,为了使得用例更具有实际意义,这里采用 maven 多模块的构建方式,在 spring-boot-rabbitmq 下构建三个子模块:
|
||||
|
||||
1. rabbitmq-common 是公共模块,用于存放公共的接口、配置和 bean,被 rabbitmq-producer 和 rabbitmq-consumer 在 pom.xml 中引用;
|
||||
2. rabbitmq-producer 是消息的生产者模块;
|
||||
3. rabbitmq-consumer 是消息的消费者模块。
|
||||
|
||||
1.2 关于 rabbitmq 安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ 实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-rabbitmq.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、关键依赖
|
||||
|
||||
在父工程的项目中统一导入依赖 rabbitmq 的 starter(spring-boot-starter-amqp),父工程的 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>rabbitmq-consumer</module>
|
||||
<module>rabbitmq-producer</module>
|
||||
<module>rabbitmq-common</module>
|
||||
</modules>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.1.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-rabbitmq</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-boot-rabbitmq</name>
|
||||
<description>RabbitMQ project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(rabbitmq-common)
|
||||
|
||||
- bean 下为公共的实体类。
|
||||
- constant 下为公共配置,用静态常量引用。(这里我使用静态常量是为了方便引用,实际中也可以按照情况,抽取为公共配置文件)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-common.png"/> </div>
|
||||
|
||||
```java
|
||||
package com.heibaiying.constant;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : rabbit 公用配置信息
|
||||
*/
|
||||
public class RabbitInfo {
|
||||
|
||||
// queue 配置
|
||||
public static final String QUEUE_NAME = "spring.boot.simple.queue";
|
||||
public static final String QUEUE_DURABLE = "true";
|
||||
|
||||
// exchange 配置
|
||||
public static final String EXCHANGE_NAME = "spring.boot.simple.exchange";
|
||||
public static final String EXCHANGE_TYPE = "topic";
|
||||
|
||||
// routing key
|
||||
public static final String ROUTING_KEY = "springboot.simple.*";
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、服务消费者(rabbitmq-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-consumer.png"/> </div>
|
||||
|
||||
#### 4.1 消息消费者配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
rabbitmq:
|
||||
addresses: 127.0.0.1:5672
|
||||
# RabbitMQ 默认的用户名和密码都是 guest 而虚拟主机名称是 "/"
|
||||
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
|
||||
username: guest
|
||||
password: guest
|
||||
virtual-host: /
|
||||
listener:
|
||||
simple:
|
||||
# 为了保证信息能够被正确消费,建议签收模式设置为手工签收,并在代码中实现手工签收
|
||||
acknowledge-mode: manual
|
||||
# 侦听器调用者线程的最小数量
|
||||
concurrency: 10
|
||||
# 侦听器调用者线程的最大数量
|
||||
max-concurrency: 50
|
||||
```
|
||||
|
||||
#### 4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者
|
||||
|
||||
1. 使用注解创建的交换机、队列、和绑定关系会在项目初始化的时候自动创建,但是不会重复创建;
|
||||
2. 这里我们创建两个消息监听器,分别演示消息是基本类型和消息是对象时的配置区别。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 消息是对象的消费者
|
||||
*/
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RabbitmqBeanConsumer {
|
||||
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
value = @Queue(value = RabbitBeanInfo.QUEUE_NAME, durable = RabbitBeanInfo.QUEUE_DURABLE),
|
||||
exchange = @Exchange(value = RabbitBeanInfo.EXCHANGE_NAME, type = RabbitBeanInfo.EXCHANGE_TYPE),
|
||||
key = RabbitBeanInfo.ROUTING_KEY)
|
||||
)
|
||||
@RabbitHandler
|
||||
public void onMessage(@Payload Programmer programmer, @Headers Map<String, Object> headers, Channel channel) throws Exception {
|
||||
log.info("programmer:{} ", programmer);
|
||||
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
|
||||
channel.basicAck(deliveryTag, false);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RabbitmqConsumer {
|
||||
|
||||
@RabbitListener(bindings = @QueueBinding(
|
||||
value = @Queue(value = RabbitInfo.QUEUE_NAME, durable = RabbitInfo.QUEUE_DURABLE),
|
||||
exchange = @Exchange(value = RabbitInfo.EXCHANGE_NAME, type = RabbitInfo.EXCHANGE_TYPE),
|
||||
key = RabbitInfo.ROUTING_KEY)
|
||||
)
|
||||
@RabbitHandler
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
MessageHeaders headers = message.getHeaders();
|
||||
// 获取消息头信息和消息体
|
||||
log.info("msgInfo:{} ; payload:{} ", headers.get("msgInfo"), message.getPayload());
|
||||
// DELIVERY_TAG 代表 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数
|
||||
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
|
||||
// 第二个参数代表是否一次签收多条,当该参数为 true 时,则可以一次性确认 DELIVERY_TAG 小于等于传入值的所有消息
|
||||
channel.basicAck(deliveryTag, false);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 五、 消息生产者(rabbitmq-producer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/rabbitmq-producer.png"/> </div>
|
||||
|
||||
#### 5.1 消息生产者配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
rabbitmq:
|
||||
addresses: 127.0.0.1:5672
|
||||
# RabbitMQ 默认的用户名和密码都是 guest 而虚拟主机名称是 "/"
|
||||
# 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
|
||||
username: guest
|
||||
password: guest
|
||||
virtual-host: /
|
||||
# 是否启用发布者确认 具体确认回调实现见代码
|
||||
publisher-confirms: true
|
||||
# 是否启用发布者返回 具体返回回调实现见代码
|
||||
publisher-returns: true
|
||||
# 是否启用强制消息 保证消息的有效监听
|
||||
template.mandatory: true
|
||||
|
||||
server:
|
||||
port: 8090
|
||||
```
|
||||
|
||||
#### 5.2 创建消息生产者
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 消息生产者
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RabbitmqProducer {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
public void sendSimpleMessage(Map<String, Object> headers, Object message,
|
||||
String messageId, String exchangeName, String key) {
|
||||
// 自定义消息头
|
||||
MessageHeaders messageHeaders = new MessageHeaders(headers);
|
||||
// 创建消息
|
||||
Message<Object> msg = MessageBuilder.createMessage(message, messageHeaders);
|
||||
/* 确认的回调 确认消息是否到达 Broker 服务器 其实就是是否到达交换器
|
||||
如果发送时候指定的交换器不存在 ack 就是 false 代表消息不可达 */
|
||||
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
|
||||
log.info("correlationData:{} , ack:{}", correlationData.getId(), ack);
|
||||
if (!ack) {
|
||||
System.out.println("进行对应的消息补偿机制");
|
||||
}
|
||||
});
|
||||
/* 消息失败的回调
|
||||
* 例如消息已经到达交换器上,但路由键匹配任何绑定到该交换器的队列,会触发这个回调,此时 replyText: NO_ROUTE
|
||||
*/
|
||||
rabbitTemplate.setReturnCallback((message1, replyCode, replyText, exchange, routingKey) -> {
|
||||
log.info("message:{}; replyCode: {}; replyText: {} ; exchange:{} ; routingKey:{}",
|
||||
message1, replyCode, replyText, exchange, routingKey);
|
||||
});
|
||||
// 在实际中 ID 应该是全局唯一 能够唯一标识消息 消息不可达的时候触发 ConfirmCallback 回调方法时可以获取该值,进行对应的错误处理
|
||||
CorrelationData correlationData = new CorrelationData(messageId);
|
||||
rabbitTemplate.convertAndSend(exchangeName, key, msg, correlationData);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.3 以单元测试的方式发送消息
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class RabbitmqProducerTests {
|
||||
|
||||
@Autowired
|
||||
private RabbitmqProducer producer;
|
||||
|
||||
/***
|
||||
* 发送消息体为简单数据类型的消息
|
||||
*/
|
||||
@Test
|
||||
public void send() {
|
||||
Map<String, Object> heads = new HashMap<>();
|
||||
heads.put("msgInfo", "自定义消息头信息");
|
||||
// 模拟生成消息 ID,在实际中应该是全局唯一的 消息不可达时候可以在 setConfirmCallback 回调中取得,可以进行对应的重发或错误处理
|
||||
String id = String.valueOf(Math.round(Math.random() * 10000));
|
||||
producer.sendSimpleMessage(heads, "hello Spring", id, RabbitInfo.EXCHANGE_NAME, "springboot.simple.abc");
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 发送消息体为 bean 的消息
|
||||
*/
|
||||
@Test
|
||||
public void sendBean() {
|
||||
String id = String.valueOf(Math.round(Math.random() * 10000));
|
||||
Programmer programmer = new Programmer("xiaoMing", 12, 12123.45f, new Date());
|
||||
producer.sendSimpleMessage(null, programmer, id, RabbitBeanInfo.EXCHANGE_NAME, RabbitBeanInfo.ROUTING_KEY);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer 和 producer 模块均依赖公共模块,所以在构建 consumer 和 producer 项目前需要将 common 模块安装到本地仓库,**依次**对**父工程**和**common 模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer 中 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-rabbitmq</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>rabbitmq-consumer</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>rabbitmq-consumer</name>
|
||||
<description>RabbitMQ consumer project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>rabbitmq-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
```
|
||||
|
||||
producer 中 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-boot-rabbitmq</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>rabbitmq-producer</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>rabbitmq-producer</name>
|
||||
<description>RabbitMQ producer project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>rabbitmq-common</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
```
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 redis
|
||||
|
||||
# spring boot 整合 redis
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
|
||||
@@ -34,445 +35,445 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
1. RedisConfig.java实现了redisTemplate 序列化与反序列化的配置;
|
||||
2. RedisOperation和RedisObjectOperation分别封装了对基本类型和对象的操作。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-redis.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<!--redis starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<!--jackson 序列化包 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、整合 Redis
|
||||
|
||||
#### 2.1 在application.yml 中配置redis数据源
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
# 默认采用的也是 0 号数据库 redis官方在4.0之后版本就不推荐采用单节点多数据库(db1-db15)的方式存储数据,如果有需要应该采用集群方式构建
|
||||
database: 0
|
||||
|
||||
# 如果是集群节点 采用如下配置指定节点
|
||||
#spring.redis.cluster.nodes
|
||||
```
|
||||
|
||||
#### 2.2 封装redis基本操作
|
||||
|
||||
需要说明的是spring boot 提供了两个template 用于操作redis:
|
||||
|
||||
- StringRedisTemplate:由于redis在大多数使用情况下都是操作字符串类型的存储,所以spring boot 将对字符串的操作单独封装在StringRedisTemplate ;
|
||||
- RedisTemplate<Object, Object> :redis 用于操作任意类型的template。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : redis 基本操作
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class RedisOperation {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
/***
|
||||
* 操作普通字符串
|
||||
*/
|
||||
public void StringSet(String key, String value) {
|
||||
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
|
||||
valueOperations.set(key, value);
|
||||
}
|
||||
|
||||
/***
|
||||
* 操作列表
|
||||
*/
|
||||
public void ListSet(String key, List<String> values) {
|
||||
ListOperations<String, String> listOperations = redisTemplate.opsForList();
|
||||
values.forEach(value -> listOperations.leftPush(key, value));
|
||||
}
|
||||
|
||||
/***
|
||||
* 操作集合
|
||||
*/
|
||||
public void SetSet(String key, Set<String> values) {
|
||||
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
|
||||
values.forEach(value -> setOperations.add(key, value));
|
||||
}
|
||||
|
||||
/***
|
||||
* 获取字符串
|
||||
*/
|
||||
public String StringGet(String key) {
|
||||
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
|
||||
return valueOperations.get(key);
|
||||
}
|
||||
|
||||
/***
|
||||
* 列表弹出元素
|
||||
*/
|
||||
public String ListLeftPop(String key) {
|
||||
ListOperations<String, String> listOperations = redisTemplate.opsForList();
|
||||
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/***
|
||||
* 集合弹出元素
|
||||
*/
|
||||
public String SetPop(String key) {
|
||||
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
|
||||
return setOperations.pop(key);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : redis 基本操作
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class RedisObjectOperation {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> objectRedisTemplate;
|
||||
|
||||
/***
|
||||
* 操作对象
|
||||
*/
|
||||
public void ObjectSet(Object key, Object value) {
|
||||
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
|
||||
valueOperations.set(key, value);
|
||||
}
|
||||
|
||||
/***
|
||||
* 操作元素为对象列表
|
||||
*/
|
||||
public void ListSet(Object key, List<Object> values) {
|
||||
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
|
||||
values.forEach(value -> listOperations.leftPush(key, value));
|
||||
}
|
||||
|
||||
/***
|
||||
* 操作元素为对象集合
|
||||
*/
|
||||
public void SetSet(Object key, Set<Object> values) {
|
||||
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
|
||||
values.forEach(value -> setOperations.add(key, value));
|
||||
}
|
||||
|
||||
/***
|
||||
* 获取对象
|
||||
*/
|
||||
public Object ObjectGet(Object key) {
|
||||
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
|
||||
return valueOperations.get(key);
|
||||
}
|
||||
|
||||
/***
|
||||
* 列表弹出元素
|
||||
*/
|
||||
public Object ListLeftPop(Object key) {
|
||||
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
|
||||
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/***
|
||||
* 集合弹出元素
|
||||
*/
|
||||
public Object SetPop(Object key) {
|
||||
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
|
||||
return setOperations.pop(key);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 redisTemplate 序列化为json格式与反序列化
|
||||
|
||||
这里需要说明的spring boot 的 redisTemplate 本身是实现了对象的序列化与反序列化的,是支持直接存取对象的。但是这里的序列化默认采用的是JdkSerializationRedisSerializer.serialize()序列化为二进制码,这个本身是不影响redisTemplate 的操作的,因为redisTemplate在取出数据的时候会自动进行反序列化。
|
||||
|
||||
但是如果我们在命令行中使用get命令去获取数据时候,得到的是一串不直观的二进制码,所以我们尽量将其序列化为直观的json格式存储。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义序列化器
|
||||
* 不定义的话默认采用的是serializer.JdkSerializationRedisSerializer.serialize()序列化为二进制字节码 存储在数据库中不直观
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
|
||||
// 使用Jackson2JsonRedisSerialize 需要导入依赖 com.fasterxml.jackson.core jackson-databind
|
||||
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 第一个参数表示: 表示所有访问者都受到影响 包括 字段, getter / isGetter,setter,creator
|
||||
// 第二个参数表示: 所有类型的访问修饰符都是可接受的,不论是公有还有私有表变量都会被序列化
|
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
|
||||
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
|
||||
|
||||
// 设置key,value 序列化规则
|
||||
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class RedisTests {
|
||||
|
||||
@Autowired
|
||||
private RedisOperation redisOperation;
|
||||
|
||||
@Test
|
||||
public void StringOperation() {
|
||||
redisOperation.StringSet("hello", "redis");
|
||||
String s = redisOperation.StringGet("hello");
|
||||
Assert.assertEquals(s, "redis");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ListOperation() {
|
||||
redisOperation.ListSet("skill", Arrays.asList("java", "oracle", "vue"));
|
||||
String s = redisOperation.ListLeftPop("skill");
|
||||
Assert.assertEquals(s, "vue");
|
||||
}
|
||||
|
||||
/*
|
||||
* 需要注意的是Redis的集合(set)不仅不允许有重复元素,并且集合中的元素是无序的,
|
||||
* 不能通过索引下标获取元素。哪怕你在java中传入的集合是有序的newLinkedHashSet,但是实际在Redis存储的还是无序的集合
|
||||
*/
|
||||
@Test
|
||||
public void SetOperation() {
|
||||
redisOperation.SetSet("skillSet", Sets.newLinkedHashSet("java", "oracle", "vue"));
|
||||
String s = redisOperation.SetPop("skillSet");
|
||||
Assert.assertNotNull(s);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
## 附:Redis的数据结构和操作命令
|
||||
|
||||
### 1.1 预备
|
||||
|
||||
#### 1.1.1 全局命令
|
||||
|
||||
1. 查看所有键: **keys \***
|
||||
|
||||
2. 查看键总数:**dbsize**
|
||||
|
||||
3. 检查键是否存在:**exists key**
|
||||
|
||||
4. 删除键:**del key [key ...]** 支持删除多个键
|
||||
|
||||
5. 键过期:**expire key seconds**
|
||||
|
||||
ttl命令会返回键的剩余过期时间, 它有3种返回值:
|
||||
|
||||
- 大于等于0的整数: 键剩余的过期时间。
|
||||
- -1: 键没设置过期时间。
|
||||
- -2: 键不存在
|
||||
|
||||
6. 键的数据结构 **type key**
|
||||
|
||||
#### 1.1.2 数据结构和内部编码
|
||||
|
||||
type命令实际返回的就是当前键的数据结构类型, 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合)
|
||||
|
||||
#### 1.1.3 单线程架构
|
||||
|
||||
1. 纯内存访问, Redis将所有数据放在内存中, 内存的响应时长大约为100纳秒, 这是Redis达到每秒万级别访问的重要基础。
|
||||
2. 非阻塞I/O, Redis使用epoll作为I/O多路复用技术的实现, 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间, 如图2-6所示。
|
||||
3. 单线程避免了线程切换和竞态产生的消耗。
|
||||
|
||||
### 1.2 字符串
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds: 为键设置秒级过期时间。 <br/>px milliseconds: 为键设置毫秒级过期时间。<br/>nx: 键必须不存在, 才可以设置成功, 用于添加。<br/>xx: 与nx相反, 键必须存在, 才可以设置成功, 用于更新。 |
|
||||
| 获取值 | get key | r如果获取的键不存在 ,则返回nil(空) |
|
||||
| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 |
|
||||
| 批量获取值 | mget key [key ...] | mget a b c d |
|
||||
| 计数 | incr key decr key incrby key increment(指定数值自增)<br/>decrby key decrement(指定数值自减)<br/>incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。<br/>键不存在,创建键,并按照值为0自增或自减, 返回结果为1。 |
|
||||
| 追加值 | append key value | 向字符串的默认追加值 |
|
||||
| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
|
||||
| 设置并返回原值 | getset key value | |
|
||||
| 设置指定位置的租字符串 | setrange key offeset value | |
|
||||
| 获取部分字符串 | getrange key start end | |
|
||||
|
||||
### 1.3 哈希
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| 设置值 | hset key field value | hset user:1 name tom<br/>hset user:1 age 12 |
|
||||
| 获取值 | hget key field | hget user:1 name |
|
||||
| 删除field | hdel key field [field ...] | |
|
||||
| 计算field个数 | hlen key | |
|
||||
| 批量设置或获取field-value | hmget key field [field]<br/>hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin<br/>hmget user:1 name city |
|
||||
| 判断field是否存在 | hexists key field | |
|
||||
| 获取所有field | hkeys key | |
|
||||
| 获取所有value | hvals key | |
|
||||
| 获取所有的filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。<br/>获取全部 可以使用hscan命令, 该命令会渐进式遍历哈希类型 |
|
||||
| 计数 | hincrby key field<br/>hincrbyfloat key field | |
|
||||
|
||||
### 1.4 列表
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| 增 | 左侧插入:lpush key value [value ...] 右侧插入:rpush key value [value ...] 某个指定元素前后插入:linsert key before\|after pivot value | |
|
||||
| 查 | 获取指定范围内的元素列表:lrange key start end 获取列表指定索引下标的元素:lindex key index 获取列表指定长度:llen key | lrange listkey 0 -1 |
|
||||
| 删 | 从列表左侧弹出元素:lpop key 从列表右侧弹出元素:rpop key 删除指定元素:lrem key count value 截取列表:ltrim key start end | count>0, 从左到右, 删除最多count个元素。<br/>count<0, 从右到左, 删除最多count绝对值个元素。<br/>count=0, 删除所有 |
|
||||
| 改 | 修改指定索引下标的元素:lset key index newValue | |
|
||||
| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout | key[key...]: 多个列表的键。 timeout: 阻塞时间\|等待时间(单位: 秒) |
|
||||
|
||||
|
||||
|
||||
### 1.5 集合
|
||||
|
||||
集合(set) 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素**, 并且集合中的元素是无序的, **不能通过索引下标获取元素**。
|
||||
|
||||
**集合内操作**:
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| -------------------- | ------------------------------ | ----------------------------------------- |
|
||||
| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 |
|
||||
| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 |
|
||||
| 计算元素个数 | scard key | |
|
||||
| 判断元素是否在集合中 | sismember key element | |
|
||||
| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素,count 默认为1 |
|
||||
| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素,spop 会 |
|
||||
| 获取集合中所有元素 | smembers key | 可用sscan 代替 |
|
||||
|
||||
**集合间操作**:
|
||||
|
||||
| 作用 | 格式 |
|
||||
| ---------------------------- | ------------------------------------------------------------ |
|
||||
| 求多个集合的交集 | sinter key [key ...] |
|
||||
| 求多个集合的并集 | suinon key [key ...] |
|
||||
| 求多个集合的差集 | sdiff key [key ...] |
|
||||
| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...] <br/>suionstore destination key [key ...]<br/>sdiffstore destination key [key ...] |
|
||||
|
||||
### 1.6 有序集合
|
||||
|
||||
有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数(score) 作为排序的依据。
|
||||
|
||||
**集合内操作**:
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| 添加成员 | zadd key score member [score member ...] | nx: member必须不存在, 才可设置成功, 用于添加。<br> xx: member必须存在, 才可以设置成功, 用于更新。<br/>ch: 返回此次操作后, 有序集合元素和分数发生变化的个数<br/>incr: 对score做增加, 相当于后面介绍的zincrby。 |
|
||||
| 计算成员个数 | zcard key | |
|
||||
| 计算某个成员的分数 | zscore key member | |
|
||||
| 计算某个成员的排名 | zrank key member zrevrank key member | zrank是从分数从低到高返回排名, zrevrank反之。 |
|
||||
| 删除成员 | zrem key member [member ...] | |
|
||||
| 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom |
|
||||
| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回, zrevrange反之。 |
|
||||
| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回, zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间(小括号) 和闭区间(中括号) , -inf和+inf分别代表无限小和无限大 |
|
||||
| 删除指定排名内的升序元素 | zremrangerank key start end | |
|
||||
| 删除指定分数范围的成员 | zremrangebyscore key min max | |
|
||||
|
||||
**集合间操作**:
|
||||
|
||||
| 作用 | 格式 |
|
||||
| ---- | ------------------------------------------------------------ |
|
||||
| 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
|
||||
| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
|
||||
|
||||
- destination: 交集计算结果保存到这个键。
|
||||
- numkeys: 需要做交集计算键的个数。
|
||||
- key[key...]: 需要做交集计算的键。
|
||||
- weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重, 每个键的权重默认是1。
|
||||
- aggregate sum|min|max: 计算成员交集后, 分值可以按照sum(和) 、min(最小值) 、 max(最大值) 做汇总, 默认值是sum。
|
||||
|
||||
### 1.7 键管理
|
||||
|
||||
#### 1.7.1 单个键管理
|
||||
|
||||
##### 1.键重命名
|
||||
|
||||
**rename key newkey**
|
||||
|
||||
为了防止被强行rename, Redis提供了renamenx命令, 确保只有newKey不存在时候才被覆盖。
|
||||
|
||||
##### 2. 随机返回键
|
||||
|
||||
**random key**
|
||||
|
||||
##### 3.键过期
|
||||
|
||||
- expire key seconds: 键在seconds秒后过期。
|
||||
- expireat key timestamp: 键在秒级时间戳timestamp后过期。
|
||||
- pexpire key milliseconds: 键在milliseconds毫秒后过期。
|
||||
- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期
|
||||
|
||||
注意:
|
||||
|
||||
1. 如果expire key的键不存在, 返回结果为0
|
||||
2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样
|
||||
3. persist key t命令可以将键的过期时间清除
|
||||
4. 对于字符串类型键, 执行set命令会去掉过期时间, 这个问题很容易在开发中被忽视
|
||||
5. Redis不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
|
||||
6. setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间
|
||||
|
||||
#### 1.7.2 键遍历
|
||||
|
||||
##### 1. 全量键遍历
|
||||
|
||||
**keys pattern**
|
||||
|
||||
##### 2. 渐进式遍历
|
||||
|
||||
scan cursor \[match pattern] \[count number]
|
||||
|
||||
- cursor是必需参数, 实际上cursor是一个游标, 第一次遍历从0开始, 每次scan遍历完都会返回当前游标的值, 直到游标值为0, 表示遍历结束。
|
||||
- match pattern是可选参数, 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。
|
||||
- count number是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是10, 此参数可以适当增大。
|
||||
|
||||
#### 1.7.3 数据库管理
|
||||
|
||||
##### 1.切换数据库
|
||||
|
||||
**select dbIndex**
|
||||
|
||||
##### 2.flushdb/flushall
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
1. RedisConfig.java 实现了 redisTemplate 序列化与反序列化的配置;
|
||||
2. RedisOperation 和 RedisObjectOperation 分别封装了对基本类型和对象的操作。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-redis.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<!--redis starter -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<!--jackson 序列化包 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、整合 Redis
|
||||
|
||||
#### 2.1 在application.yml 中配置redis数据源
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
# 默认采用的也是 0 号数据库 redis 官方在 4.0 之后版本就不推荐采用单节点多数据库 (db1-db15) 的方式存储数据,如果有需要应该采用集群方式构建
|
||||
database: 0
|
||||
|
||||
# 如果是集群节点 采用如下配置指定节点
|
||||
#spring.redis.cluster.nodes
|
||||
```
|
||||
|
||||
#### 2.2 封装redis基本操作
|
||||
|
||||
需要说明的是 spring boot 提供了两个 template 用于操作 redis:
|
||||
|
||||
- StringRedisTemplate:由于 redis 在大多数使用情况下都是操作字符串类型的存储,所以 spring boot 将对字符串的操作单独封装在 StringRedisTemplate ;
|
||||
- RedisTemplate<Object, Object> :redis 用于操作任意类型的 template。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : redis 基本操作
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class RedisOperation {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
/***
|
||||
* 操作普通字符串
|
||||
*/
|
||||
public void StringSet(String key, String value) {
|
||||
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
|
||||
valueOperations.set(key, value);
|
||||
}
|
||||
|
||||
/***
|
||||
* 操作列表
|
||||
*/
|
||||
public void ListSet(String key, List<String> values) {
|
||||
ListOperations<String, String> listOperations = redisTemplate.opsForList();
|
||||
values.forEach(value -> listOperations.leftPush(key, value));
|
||||
}
|
||||
|
||||
/***
|
||||
* 操作集合
|
||||
*/
|
||||
public void SetSet(String key, Set<String> values) {
|
||||
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
|
||||
values.forEach(value -> setOperations.add(key, value));
|
||||
}
|
||||
|
||||
/***
|
||||
* 获取字符串
|
||||
*/
|
||||
public String StringGet(String key) {
|
||||
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
|
||||
return valueOperations.get(key);
|
||||
}
|
||||
|
||||
/***
|
||||
* 列表弹出元素
|
||||
*/
|
||||
public String ListLeftPop(String key) {
|
||||
ListOperations<String, String> listOperations = redisTemplate.opsForList();
|
||||
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/***
|
||||
* 集合弹出元素
|
||||
*/
|
||||
public String SetPop(String key) {
|
||||
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
|
||||
return setOperations.pop(key);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : redis 基本操作
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class RedisObjectOperation {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> objectRedisTemplate;
|
||||
|
||||
/***
|
||||
* 操作对象
|
||||
*/
|
||||
public void ObjectSet(Object key, Object value) {
|
||||
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
|
||||
valueOperations.set(key, value);
|
||||
}
|
||||
|
||||
/***
|
||||
* 操作元素为对象列表
|
||||
*/
|
||||
public void ListSet(Object key, List<Object> values) {
|
||||
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
|
||||
values.forEach(value -> listOperations.leftPush(key, value));
|
||||
}
|
||||
|
||||
/***
|
||||
* 操作元素为对象集合
|
||||
*/
|
||||
public void SetSet(Object key, Set<Object> values) {
|
||||
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
|
||||
values.forEach(value -> setOperations.add(key, value));
|
||||
}
|
||||
|
||||
/***
|
||||
* 获取对象
|
||||
*/
|
||||
public Object ObjectGet(Object key) {
|
||||
ValueOperations<Object, Object> valueOperations = objectRedisTemplate.opsForValue();
|
||||
return valueOperations.get(key);
|
||||
}
|
||||
|
||||
/***
|
||||
* 列表弹出元素
|
||||
*/
|
||||
public Object ListLeftPop(Object key) {
|
||||
ListOperations<Object, Object> listOperations = objectRedisTemplate.opsForList();
|
||||
return listOperations.leftPop(key, 2, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/***
|
||||
* 集合弹出元素
|
||||
*/
|
||||
public Object SetPop(Object key) {
|
||||
SetOperations<Object, Object> setOperations = objectRedisTemplate.opsForSet();
|
||||
return setOperations.pop(key);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 redisTemplate 序列化为json格式与反序列化
|
||||
|
||||
这里需要说明的 spring boot 的 redisTemplate 本身是实现了对象的序列化与反序列化的,是支持直接存取对象的。但是这里的序列化默认采用的是 JdkSerializationRedisSerializer.serialize() 序列化为二进制码,这个本身是不影响 redisTemplate 的操作的,因为 redisTemplate 在取出数据的时候会自动进行反序列化。
|
||||
|
||||
但是如果我们在命令行中使用 get 命令去获取数据时候,得到的是一串不直观的二进制码,所以我们尽量将其序列化为直观的 json 格式存储。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义序列化器
|
||||
* 不定义的话默认采用的是 serializer.JdkSerializationRedisSerializer.serialize() 序列化为二进制字节码 存储在数据库中不直观
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
|
||||
// 使用 Jackson2JsonRedisSerialize 需要导入依赖 com.fasterxml.jackson.core jackson-databind
|
||||
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 第一个参数表示: 表示所有访问者都受到影响 包括 字段, getter / isGetter,setter,creator
|
||||
// 第二个参数表示: 所有类型的访问修饰符都是可接受的,不论是公有还有私有表变量都会被序列化
|
||||
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
|
||||
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
|
||||
|
||||
// 设置 key,value 序列化规则
|
||||
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class RedisTests {
|
||||
|
||||
@Autowired
|
||||
private RedisOperation redisOperation;
|
||||
|
||||
@Test
|
||||
public void StringOperation() {
|
||||
redisOperation.StringSet("hello", "redis");
|
||||
String s = redisOperation.StringGet("hello");
|
||||
Assert.assertEquals(s, "redis");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ListOperation() {
|
||||
redisOperation.ListSet("skill", Arrays.asList("java", "oracle", "vue"));
|
||||
String s = redisOperation.ListLeftPop("skill");
|
||||
Assert.assertEquals(s, "vue");
|
||||
}
|
||||
|
||||
/*
|
||||
* 需要注意的是 Redis 的集合(set)不仅不允许有重复元素,并且集合中的元素是无序的,
|
||||
* 不能通过索引下标获取元素。哪怕你在 java 中传入的集合是有序的 newLinkedHashSet,但是实际在 Redis 存储的还是无序的集合
|
||||
*/
|
||||
@Test
|
||||
public void SetOperation() {
|
||||
redisOperation.SetSet("skillSet", Sets.newLinkedHashSet("java", "oracle", "vue"));
|
||||
String s = redisOperation.SetPop("skillSet");
|
||||
Assert.assertNotNull(s);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
<br/>
|
||||
|
||||
## 附:Redis的数据结构和操作命令
|
||||
|
||||
### 1.1 预备
|
||||
|
||||
#### 1.1.1 全局命令
|
||||
|
||||
1. 查看所有键: **keys \***
|
||||
|
||||
2. 查看键总数:**dbsize**
|
||||
|
||||
3. 检查键是否存在:**exists key**
|
||||
|
||||
4. 删除键:**del key [key ...]** 支持删除多个键
|
||||
|
||||
5. 键过期:**expire key seconds**
|
||||
|
||||
ttl 命令会返回键的剩余过期时间, 它有 3 种返回值:
|
||||
|
||||
- 大于等于 0 的整数: 键剩余的过期时间。
|
||||
- -1: 键没设置过期时间。
|
||||
- -2: 键不存在
|
||||
|
||||
6. 键的数据结构 **type key**
|
||||
|
||||
#### 1.1.2 数据结构和内部编码
|
||||
|
||||
type 命令实际返回的就是当前键的数据结构类型, 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合)
|
||||
|
||||
#### 1.1.3 单线程架构
|
||||
|
||||
1. 纯内存访问, Redis 将所有数据放在内存中, 内存的响应时长大约为 100 纳秒, 这是 Redis 达到每秒万级别访问的重要基础。
|
||||
2. 非阻塞 I/O, Redis 使用 epoll 作为 I/O 多路复用技术的实现, 再加上 Redis 自身的事件处理模型将 epoll 中的连接、 读写、 关闭都转换为事件, 不在网络 I/O 上浪费过多的时间, 如图 2-6 所示。
|
||||
3. 单线程避免了线程切换和竞态产生的消耗。
|
||||
|
||||
### 1.2 字符串
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds: 为键设置秒级过期时间。 <br/>px milliseconds: 为键设置毫秒级过期时间。<br/>nx: 键必须不存在, 才可以设置成功, 用于添加。<br/>xx: 与 nx 相反, 键必须存在, 才可以设置成功, 用于更新。 |
|
||||
| 获取值 | get key | r 如果获取的键不存在 ,则返回 nil(空) |
|
||||
| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 |
|
||||
| 批量获取值 | mget key [key ...] | mget a b c d |
|
||||
| 计数 | incr key decr key incrby key increment(指定数值自增)<br/>decrby key decrement(指定数值自减)<br/>incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。<br/>键不存在,创建键,并按照值为 0 自增或自减, 返回结果为 1。 |
|
||||
| 追加值 | append key value | 向字符串的默认追加值 |
|
||||
| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
|
||||
| 设置并返回原值 | getset key value | |
|
||||
| 设置指定位置的租字符串 | setrange key offeset value | |
|
||||
| 获取部分字符串 | getrange key start end | |
|
||||
|
||||
### 1.3 哈希
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| 设置值 | hset key field value | hset user:1 name tom<br/>hset user:1 age 12 |
|
||||
| 获取值 | hget key field | hget user:1 name |
|
||||
| 删除 field | hdel key field [field ...] | |
|
||||
| 计算 field 个数 | hlen key | |
|
||||
| 批量设置或获取 field-value | hmget key field [field]<br/>hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin<br/>hmget user:1 name city |
|
||||
| 判断 field 是否存在 | hexists key field | |
|
||||
| 获取所有 field | hkeys key | |
|
||||
| 获取所有 value | hvals key | |
|
||||
| 获取所有的 filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞 Redis 的可能。<br/>获取全部 可以使用 hscan 命令, 该命令会渐进式遍历哈希类型 |
|
||||
| 计数 | hincrby key field<br/>hincrbyfloat key field | |
|
||||
|
||||
### 1.4 列表
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| 增 | 左侧插入:lpush key value [value ...] 右侧插入:rpush key value [value ...] 某个指定元素前后插入:linsert key before\|after pivot value | |
|
||||
| 查 | 获取指定范围内的元素列表:lrange key start end 获取列表指定索引下标的元素:lindex key index 获取列表指定长度:llen key | lrange listkey 0 -1 |
|
||||
| 删 | 从列表左侧弹出元素:lpop key 从列表右侧弹出元素:rpop key 删除指定元素:lrem key count value 截取列表:ltrim key start end | count>0, 从左到右, 删除最多 count 个元素。<br/>count<0, 从右到左, 删除最多 count 绝对值个元素。<br/>count=0, 删除所有 |
|
||||
| 改 | 修改指定索引下标的元素:lset key index newValue | |
|
||||
| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout | key[key...]: 多个列表的键。 timeout: 阻塞时间\|等待时间(单位: 秒) |
|
||||
|
||||
|
||||
|
||||
### 1.5 集合
|
||||
|
||||
集合(set) 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素**, 并且集合中的元素是无序的, **不能通过索引下标获取元素**。
|
||||
|
||||
**集合内操作**:
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| -------------------- | ------------------------------ | ----------------------------------------- |
|
||||
| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 |
|
||||
| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 |
|
||||
| 计算元素个数 | scard key | |
|
||||
| 判断元素是否在集合中 | sismember key element | |
|
||||
| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素,count 默认为 1 |
|
||||
| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素,spop 会 |
|
||||
| 获取集合中所有元素 | smembers key | 可用 sscan 代替 |
|
||||
|
||||
**集合间操作**:
|
||||
|
||||
| 作用 | 格式 |
|
||||
| ---------------------------- | ------------------------------------------------------------ |
|
||||
| 求多个集合的交集 | sinter key [key ...] |
|
||||
| 求多个集合的并集 | suinon key [key ...] |
|
||||
| 求多个集合的差集 | sdiff key [key ...] |
|
||||
| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...] <br/>suionstore destination key [key ...]<br/>sdiffstore destination key [key ...] |
|
||||
|
||||
### 1.6 有序集合
|
||||
|
||||
有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数(score) 作为排序的依据。
|
||||
|
||||
**集合内操作**:
|
||||
|
||||
| 作用 | 格式 | 参数或示例 |
|
||||
| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| 添加成员 | zadd key score member [score member ...] | nx: member 必须不存在, 才可设置成功, 用于添加。<br> xx: member 必须存在, 才可以设置成功, 用于更新。<br/>ch: 返回此次操作后, 有序集合元素和分数发生变化的个数<br/>incr: 对 score 做增加, 相当于后面介绍的 zincrby。 |
|
||||
| 计算成员个数 | zcard key | |
|
||||
| 计算某个成员的分数 | zscore key member | |
|
||||
| 计算某个成员的排名 | zrank key member zrevrank key member | zrank 是从分数从低到高返回排名, zrevrank 反之。 |
|
||||
| 删除成员 | zrem key member [member ...] | |
|
||||
| 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom |
|
||||
| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange 是从低到高返回, zrevrange 反之。 |
|
||||
| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中 zrangebyscore 按照分数从低到高返回, zrevrangebyscore 反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时 min 和 max 还支持开区间(小括号) 和闭区间(中括号) , -inf 和 +inf 分别代表无限小和无限大 |
|
||||
| 删除指定排名内的升序元素 | zremrangerank key start end | |
|
||||
| 删除指定分数范围的成员 | zremrangebyscore key min max | |
|
||||
|
||||
**集合间操作**:
|
||||
|
||||
| 作用 | 格式 |
|
||||
| ---- | ------------------------------------------------------------ |
|
||||
| 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
|
||||
| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
|
||||
|
||||
- destination: 交集计算结果保存到这个键。
|
||||
- numkeys: 需要做交集计算键的个数。
|
||||
- key[key...]: 需要做交集计算的键。
|
||||
- weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中的每个 member 会将自己分数乘以这个权重, 每个键的权重默认是 1。
|
||||
- aggregate sum|min|max: 计算成员交集后, 分值可以按照 sum(和) 、min(最小值) 、 max(最大值) 做汇总, 默认值是 sum。
|
||||
|
||||
### 1.7 键管理
|
||||
|
||||
#### 1.7.1 单个键管理
|
||||
|
||||
##### 1.键重命名
|
||||
|
||||
**rename key newkey**
|
||||
|
||||
为了防止被强行 rename, Redis 提供了 renamenx 命令, 确保只有 newKey 不存在时候才被覆盖。
|
||||
|
||||
##### 2. 随机返回键
|
||||
|
||||
**random key**
|
||||
|
||||
##### 3.键过期
|
||||
|
||||
- expire key seconds: 键在 seconds 秒后过期。
|
||||
- expireat key timestamp: 键在秒级时间戳 timestamp 后过期。
|
||||
- pexpire key milliseconds: 键在 milliseconds 毫秒后过期。
|
||||
- pexpireat key milliseconds-timestamp 键在毫秒级时间戳 timestamp 后过期
|
||||
|
||||
注意:
|
||||
|
||||
1. 如果 expire key 的键不存在, 返回结果为 0
|
||||
2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用 del 命令一样
|
||||
3. persist key t 命令可以将键的过期时间清除
|
||||
4. 对于字符串类型键, 执行 set 命令会去掉过期时间, 这个问题很容易在开发中被忽视
|
||||
5. Redis 不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
|
||||
6. setex 命令作为 set+expire 的组合, 不但是原子执行, 同时减少了一次网络通讯的时间
|
||||
|
||||
#### 1.7.2 键遍历
|
||||
|
||||
##### 1. 全量键遍历
|
||||
|
||||
**keys pattern**
|
||||
|
||||
##### 2. 渐进式遍历
|
||||
|
||||
scan cursor \[match pattern] \[count number]
|
||||
|
||||
- cursor 是必需参数, 实际上 cursor 是一个游标, 第一次遍历从 0 开始, 每次 scan 遍历完都会返回当前游标的值, 直到游标值为 0, 表示遍历结束。
|
||||
- match pattern 是可选参数, 它的作用的是做模式的匹配, 这点和 keys 的模式匹配很像。
|
||||
- count number 是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是 10, 此参数可以适当增大。
|
||||
|
||||
#### 1.7.3 数据库管理
|
||||
|
||||
##### 1.切换数据库
|
||||
|
||||
**select dbIndex**
|
||||
|
||||
##### 2.flushdb/flushall
|
||||
|
||||
flushdb/flushall 命令用于清除数据库, 两者的区别的是 flushdb 只清除当前数据库, flushall 会清除所有数据库。
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 servlet
|
||||
|
||||
# spring boot 整合 servlet
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-项目依赖">1.2 项目依赖</a><br/>
|
||||
@@ -14,231 +15,231 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
1. 项目提供与servlet整合的两种方式,一种是servlet3.0 原生的注解方式,一种是采用spring 注册的方式;
|
||||
2. servlet、过滤器、监听器分别位于servlet、filter、listen 下,其中以Annotation命名结尾的代表是servlet注解方式实现,采用spring注册方式则在ServletConfig中进行注册;
|
||||
3. 为了说明外置容器对servlet注解的自动发现机制,项目采用外置容器构建,关于spring boot 整合外置容器的详细说明可以参考[spring-boot-tomcat](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-tomcat)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-servlet.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<!--排除依赖 使用外部tomcat容器启动-->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--servlet api 注解依赖包-->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
## 二、采用spring 注册方式整合 servlet
|
||||
|
||||
#### 2.1 新建过滤器、监听器和servlet
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义过滤器
|
||||
*/
|
||||
|
||||
public class CustomFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
request.setAttribute("filterParam","我是filter传递的参数");
|
||||
chain.doFilter(request,response);
|
||||
response.getWriter().append(" CustomFilter ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义监听器
|
||||
*/
|
||||
public class CustomListen implements ServletContextListener {
|
||||
|
||||
//Web应用程序初始化过程正在启动的通知
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
System.out.println("容器初始化启动");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 通知servlet上下文即将关闭
|
||||
* 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
|
||||
*/
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
System.out.println("容器即将销毁");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义servlet
|
||||
*/
|
||||
public class CustomServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
System.out.println("doGet 执行:" + req.getAttribute("filterParam"));
|
||||
resp.getWriter().append("CustomServlet");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
doGet(req, resp);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 注册过滤器、监听器和servlet
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Configuration
|
||||
public class ServletConfig {
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean registrationBean() {
|
||||
return new ServletRegistrationBean<HttpServlet>(new CustomServlet(), "/servlet");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean filterRegistrationBean() {
|
||||
FilterRegistrationBean bean = new FilterRegistrationBean<Filter>();
|
||||
bean.setFilter(new CustomFilter());
|
||||
bean.addUrlPatterns("/servlet");
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServletListenerRegistrationBean listenerRegistrationBean() {
|
||||
return new ServletListenerRegistrationBean<ServletContextListener>(new CustomListen());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 三、采用注解方式整合 servlet
|
||||
|
||||
#### 3.1 新建过滤器、监听器和servlet,分别使用@WebFilter、@WebListener、@WebServlet注解标注
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义过滤器
|
||||
*/
|
||||
|
||||
@WebFilter(urlPatterns = "/servletAnn")
|
||||
public class CustomFilterAnnotation implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
chain.doFilter(request,response);
|
||||
response.getWriter().append(" CustomFilter Annotation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :自定义监听器
|
||||
*/
|
||||
@WebListener
|
||||
public class CustomListenAnnotation implements ServletContextListener {
|
||||
|
||||
//Web应用程序初始化过程正在启动的通知
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
System.out.println("容器初始化启动 Annotation");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 通知servlet上下文即将关闭
|
||||
* 这个地方如果我们使用的是spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
|
||||
*/
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
System.out.println("容器即将销毁 Annotation");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义servlet
|
||||
*/
|
||||
@WebServlet(urlPatterns = "/servletAnn")
|
||||
public class CustomServletAnnotation extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.getWriter().append("CustomServlet Annotation");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
doGet(req, resp);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 使注解生效
|
||||
|
||||
1. 如果是内置容器,需要在启动类上添加@ServletComponentScan("com.heibaiying.springbootservlet") ,指定扫描的包目录;
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
1. 项目提供与 servlet 整合的两种方式,一种是 servlet3.0 原生的注解方式,一种是采用 spring 注册的方式;
|
||||
2. servlet、过滤器、监听器分别位于 servlet、filter、listen 下,其中以 Annotation 命名结尾的代表是 servlet 注解方式实现,采用 spring 注册方式则在 ServletConfig 中进行注册;
|
||||
3. 为了说明外置容器对 servlet 注解的自动发现机制,项目采用外置容器构建,关于 spring boot 整合外置容器的详细说明可以参考[spring-boot-tomcat](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-tomcat)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-servlet.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<!--排除依赖 使用外部 tomcat 容器启动-->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!--使用外置容器时候 SpringBootServletInitializer 依赖此包 -->
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--servlet api 注解依赖包-->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
## 二、采用spring 注册方式整合 servlet
|
||||
|
||||
#### 2.1 新建过滤器、监听器和servlet
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义过滤器
|
||||
*/
|
||||
|
||||
public class CustomFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
request.setAttribute("filterParam","我是 filter 传递的参数");
|
||||
chain.doFilter(request,response);
|
||||
response.getWriter().append(" CustomFilter ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义监听器
|
||||
*/
|
||||
public class CustomListen implements ServletContextListener {
|
||||
|
||||
//Web 应用程序初始化过程正在启动的通知
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
System.out.println("容器初始化启动");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 通知 servlet 上下文即将关闭
|
||||
* 这个地方如果我们使用的是 spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
|
||||
*/
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
System.out.println("容器即将销毁");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义 servlet
|
||||
*/
|
||||
public class CustomServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
System.out.println("doGet 执行:" + req.getAttribute("filterParam"));
|
||||
resp.getWriter().append("CustomServlet");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
doGet(req, resp);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 注册过滤器、监听器和servlet
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Configuration
|
||||
public class ServletConfig {
|
||||
|
||||
@Bean
|
||||
public ServletRegistrationBean registrationBean() {
|
||||
return new ServletRegistrationBean<HttpServlet>(new CustomServlet(), "/servlet");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean filterRegistrationBean() {
|
||||
FilterRegistrationBean bean = new FilterRegistrationBean<Filter>();
|
||||
bean.setFilter(new CustomFilter());
|
||||
bean.addUrlPatterns("/servlet");
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServletListenerRegistrationBean listenerRegistrationBean() {
|
||||
return new ServletListenerRegistrationBean<ServletContextListener>(new CustomListen());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 三、采用注解方式整合 servlet
|
||||
|
||||
#### 3.1 新建过滤器、监听器和servlet,分别使用@WebFilter、@WebListener、@WebServlet注解标注
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义过滤器
|
||||
*/
|
||||
|
||||
@WebFilter(urlPatterns = "/servletAnn")
|
||||
public class CustomFilterAnnotation implements Filter {
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
chain.doFilter(request,response);
|
||||
response.getWriter().append(" CustomFilter Annotation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :自定义监听器
|
||||
*/
|
||||
@WebListener
|
||||
public class CustomListenAnnotation implements ServletContextListener {
|
||||
|
||||
//Web 应用程序初始化过程正在启动的通知
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
System.out.println("容器初始化启动 Annotation");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 通知 servlet 上下文即将关闭
|
||||
* 这个地方如果我们使用的是 spring boot 内置的容器 是监听不到销毁过程,所以我们使用了外置 tomcat 容器
|
||||
*/
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
System.out.println("容器即将销毁 Annotation");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义 servlet
|
||||
*/
|
||||
@WebServlet(urlPatterns = "/servletAnn")
|
||||
public class CustomServletAnnotation extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
resp.getWriter().append("CustomServlet Annotation");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
doGet(req, resp);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 使注解生效
|
||||
|
||||
1. 如果是内置容器,需要在启动类上添加@ServletComponentScan("com.heibaiying.springbootservlet") ,指定扫描的包目录;
|
||||
2. 如果是外置容器,不需要进行任何配置,依靠容器内建的 discovery 机制自动发现,需要说明的是这里的容器必须支持 servlet3.0(tomcat 从 7.0 版本开始支持 Servlet3.0)。
|
||||
|
||||
@@ -1,267 +1,267 @@
|
||||
# spring-boot 集成 Swagger2 打造在线接口文档
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一Springfox-与-Swagger-简介">一、Springfox 与 Swagger 简介</a><br/>
|
||||
<a href="#11-Springfox">1.1 Springfox</a><br/>
|
||||
<a href="#12-Swagger">1.2 Swagger</a><br/>
|
||||
<a href="#13-OpenApiSwaggerSpringfox的关系">1.3 OpenApi、Swagger、Springfox的关系</a><br/>
|
||||
<a href="#二spring-boot-集成-swagger-20">二、spring boot 集成 swagger 2.0</a><br/>
|
||||
<a href="#21-导入项目相关依赖">2.1 导入项目相关依赖</a><br/>
|
||||
<a href="#22--进行swagger个性化配置并用EnableSwagger2开启Swagger支持">2.2 进行swagger个性化配置、并用@EnableSwagger2开启Swagger支持</a><br/>
|
||||
<a href="#23-swagger注解的使用和说明">2.3 swagger注解的使用和说明</a><br/>
|
||||
<a href="#24-swagger-ui-可视化接口文档">2.4 swagger-ui 可视化接口文档</a><br/>
|
||||
<a href="#25-利用swagger-ui进行接口测试">2.5 利用swagger-ui进行接口测试</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
## 一、Springfox 与 Swagger 简介
|
||||
|
||||
### 1.1 Springfox
|
||||
|
||||
Springfox 是一个开源的API Doc的框架, 它的前身是swagger-springmvc,能够完美的支持springmvc,可以将spring 接口方法自动转换为接口文档。 目前spring fox 正致力于对更多JSON API规范和标准的扩展和支持,例如:[swagger](http://swagger.io/),[RAML](http://raml.org/)和[jsonapi](http://jsonapi.org/)。
|
||||
|
||||
### 1.2 Swagger
|
||||
|
||||
Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务,支持从整个API生命周期(从设计和文档到测试和部署)的开发。
|
||||
|
||||
swagger 是一个综合的开源项目,包含[swagger-core](https://github.com/swagger-api/swagger-core)、[swagger-ui](https://github.com/swagger-api/swagger-ui)、[swagger-codegen](https://github.com/swagger-api/swagger-codegen)、[swagger-editor](https://github.com/swagger-api/swagger-editor)等多个子项目。
|
||||
|
||||
+ **swagger-core**:Swagger Core是OpenAPI规范(以前称为Swagger规范)的**Java实现**。
|
||||
|
||||
+ **swagger-ui**:依据可视化文档,提供与API资源的可视化交互。
|
||||
|
||||
+ **swagger-codegen**:开源的代码生成器,根据Swagger定义的RESTful API可以自动建立服务端和客户端的连接。
|
||||
|
||||
+ **swagger-editor**:开源的api文档编辑器。
|
||||
|
||||
下图为swagger-ui 提供的文档可视化界面示例:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/Swagger_UI.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.3 OpenApi、Swagger、Springfox的关系
|
||||
|
||||
**Swagger Core 是 OpenApi 规范(以前称为Swagger规范)的Java 实现,而 Springfox 提供 Swagger 与 spring 的集成支持**。<br/>
|
||||
|
||||
|
||||
|
||||
## 二、spring boot 集成 swagger 2.0
|
||||
|
||||
### 2.1 导入项目相关依赖
|
||||
|
||||
```xml
|
||||
<!--swagger2-->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
<!--swagger-ui -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 2.2 进行swagger个性化配置、并用@EnableSwagger2开启Swagger支持
|
||||
|
||||
这里需要说明的是swagger虽然是一个非常直观易用的接口调试插件,但是有可能导致接口信息泄露的危险,所以建议在开发环境和测试环境开启,在生产环境关闭。这里一共给出三种Swagger开关切换的方法:
|
||||
|
||||
1. 如下面代码所示,在配置文件中配置自定义的开关参数,并在创建Docket时候,在链式调用的enable()方法中传入;
|
||||
|
||||
2. 在`SwaggerConfig`配置类上添加`@Profile({"dev","test"}) `注解,指明在开发环境和测试环境下激活此配置类,打包或者部署时候使用spring.profiles.active指明环境即可;
|
||||
|
||||
3. 在配置文件中配置自定义的开关参数,并在`SwaggerConfig`配置类上添加`@ConditionalOnProperty(name = "swagger.enable", havingValue = "true") `,指明配置类的生效条件
|
||||
|
||||
注:@ConditionalOnProperty 注解说明
|
||||
|
||||
@ConditionalOnProperty注解能够控制某个@configuration修饰的配置类是否生效。具体操作是通过name和havingValue属性来实现,name对应application.properties(yml)中的某个属性值,如果该值为空,则返回false;如果值不为空,则将该值与havingValue指定的值进行比较,如果一样则返回true;否则返回false。如果返回值为false,则该configuration不生效;为true则生效。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Swagger 配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Value("${swagger.enable}")
|
||||
private boolean swaggerEnable;
|
||||
|
||||
/***
|
||||
* 配置swagger
|
||||
* 开发和测试环境下可以开启swagger辅助进行调试,而生产环境下可以关闭或者进行相应的权限控制,防止接口信息泄露
|
||||
*/
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.enable(swaggerEnable)
|
||||
.apiInfo(apiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.heibaiying.springboot.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.paths(doFilteringRules())
|
||||
.build();
|
||||
}
|
||||
|
||||
/***
|
||||
* 接口文档的描述信息
|
||||
*/
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("spring boot swagger2 用例")
|
||||
.description("描述")
|
||||
.licenseUrl("https://mit-license.org/")
|
||||
.version("1.0")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 可以使用正则定义url过滤规则
|
||||
*/
|
||||
private Predicate<String> doFilteringRules() {
|
||||
return not(
|
||||
regex("/ignore/*")
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
application.properties :
|
||||
|
||||
```properties
|
||||
#swagger启用开关
|
||||
swagger.enable = true
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 2.3 swagger注解的使用和说明
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@Api(value = "产品接口", description = "产品信息接口")
|
||||
@RestController
|
||||
public class ProductController {
|
||||
|
||||
/***
|
||||
* 一个标准的swagger注解
|
||||
*/
|
||||
@ApiOperation(notes = "查询所有产品", value = "产品查询接口")
|
||||
@ApiImplicitParams(
|
||||
@ApiImplicitParam(name = "id", value = "产品编号", paramType = "path", defaultValue = "1")
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(code = 200, message = "请求成功"),
|
||||
@ApiResponse(code = 400, message = "无效的请求"),
|
||||
@ApiResponse(code = 401, message = "未经过授权认证"),
|
||||
@ApiResponse(code = 403, message = "已经过授权认证,但是没有该资源对应的访问权限"),
|
||||
@ApiResponse(code = 404, message = "服务器找不到给定的资源,商品不存在"),
|
||||
@ApiResponse(code = 500, message = "服务器错误")
|
||||
})
|
||||
@GetMapping(value = "/product/{id}", produces = "application/json")
|
||||
public ResponseEntity<Product> getProduct(@PathVariable long id) {
|
||||
Product product = new Product(id, "product" + id, new Date());
|
||||
return ResponseEntity.ok(product);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 如果用实体类接收参数,则用实体类对应的属性名称指定参数
|
||||
*/
|
||||
@ApiOperation(notes = "保存产品", value = "产品保存接口")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "id", value = "产品编号", paramType = "body", defaultValue = "1"),
|
||||
@ApiImplicitParam(name = "name", value = "产品名称", paramType = "body"),
|
||||
@ApiImplicitParam(name = "date", value = "产品生产日期", paramType = "body")
|
||||
}
|
||||
)
|
||||
@PostMapping(value = "/product")
|
||||
public ResponseEntity<Void> saveProduct(@RequestBody Product product) {
|
||||
System.out.println(product);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 在配置类中指明了该接口不被扫描到,可以在配置类中使用正则指定某一类符合规则的接口不被扫描到
|
||||
*/
|
||||
@ApiOperation(notes = "该接口会被忽略", value = "产品保存接口")
|
||||
@PostMapping(value = "/ignore")
|
||||
public ResponseEntity<Product> ignore() {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 不加上任何swagger相关的注解也会被扫描到 如果不希望被扫描到,需要用 @ApiIgnore 修饰
|
||||
*/
|
||||
@PostMapping(value = "/normal")
|
||||
public ResponseEntity<Void> normal() {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@ApiIgnore
|
||||
@PostMapping(value = "/apiIgnore")
|
||||
public ResponseEntity<Void> apiIgnore() {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
swagger 为了最大程度防止对逻辑代码的侵入,基本都是依靠注解来完成文档描述。常用注解如下:
|
||||
|
||||
| Annotation | Attribute | Target Property | Description |
|
||||
| ---------------- | ------------ | ------------------------- | ------------------------------------------------------------ |
|
||||
| RequestHeader | defaultValue | Parameter#defaultValue | e.g. `@RequestHeader(defaultValue="${param1.defaultValue}")` |
|
||||
| ApiModelProperty | value | ModelProperty#description | e.g. `@ApiModelProperty(value="${property1.description}")` |
|
||||
| ApiModelProperty | description | ModelProperty#description | e.g. `@ApiModelProperty(notes="${property1.description}")` |
|
||||
| ApiParam | value | Parameter#description | e.g. `@ApiParam(value="${param1.description}")` |
|
||||
| ApiImplicitParam | value | Parameter#description | e.g. `@ApiImplicitParam(value="${param1.description}")` |
|
||||
| ApiOperation | notes | Operation#notes | e.g. `@ApiOperation(notes="${operation1.description}")` |
|
||||
| ApiOperation | summary | Operation#summary | e.g. `@ApiOperation(value="${operation1.summary}")` |
|
||||
| RequestParam | defaultValue | Parameter#defaultValue | e.g. `@RequestParam(defaultValue="${param1.defaultValue}")` |
|
||||
|
||||
1. `@Api`:Api 用在类上,说明该类的作用;
|
||||
|
||||
2. `@ApiOperation`:用在方法上,说明方法的作用;
|
||||
|
||||
3. `@ApiParam`:用在参数上,说明参数的作用;
|
||||
|
||||
4. `@ApiImplicitParams`:用在方法上说明方法参数的作用;
|
||||
|
||||
5. `@ApiImplicitParam`:用在@ApiImplicitParams注解中,描述每个具体参数;
|
||||
|
||||
6. `@ApiResponses`:一组@ApiResponse的配置;
|
||||
|
||||
7. `@ApiResponse`:请求返回的配置;
|
||||
|
||||
8. `@ResponseHeader`:响应头的配置;
|
||||
|
||||
9. `@ApiModel`:描述一个Model的信息(一般用在post创建的时候,使用@RequestBody接收参数的场景);
|
||||
|
||||
10. `@ApiModelProperty`:描述model的属性。
|
||||
|
||||
11. `@ApiIgnore`:可以用于类、方法、属性,代表该方法、类、属性不被swagger的文档所管理。
|
||||
|
||||
|
||||
|
||||
### 2.4 swagger-ui 可视化接口文档
|
||||
|
||||
接口文档访问地址:http://localhost:8080/swagger-ui.html ,文档主界面如下:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/swagger-ui-index.png"/> </div>
|
||||
|
||||
### 2.5 利用swagger-ui进行接口测试
|
||||
|
||||
|
||||
点击对应接口的`try it out`按钮可以进行接口测试,此时可以输入对应的参数的值,然后点击下方的`Execute`按钮发送请求。<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/swagger-try-it.png"/> </div>
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/swagger-execute.png"/> </div>
|
||||
|
||||
post方法可以直接修改model 对应的 json数据 ,然后点击下方的`Execute`按钮发送请求。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/swagger-post-try.png"/> </div>
|
||||
# spring-boot 集成 Swagger2 打造在线接口文档
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一Springfox-与-Swagger-简介">一、Springfox 与 Swagger 简介</a><br/>
|
||||
<a href="#11-Springfox">1.1 Springfox</a><br/>
|
||||
<a href="#12-Swagger">1.2 Swagger</a><br/>
|
||||
<a href="#13-OpenApiSwaggerSpringfox的关系">1.3 OpenApi、Swagger、Springfox的关系</a><br/>
|
||||
<a href="#二spring-boot-集成-swagger-20">二、spring boot 集成 swagger 2.0</a><br/>
|
||||
<a href="#21-导入项目相关依赖">2.1 导入项目相关依赖</a><br/>
|
||||
<a href="#22--进行swagger个性化配置并用EnableSwagger2开启Swagger支持">2.2 进行swagger个性化配置、并用@EnableSwagger2开启Swagger支持</a><br/>
|
||||
<a href="#23-swagger注解的使用和说明">2.3 swagger注解的使用和说明</a><br/>
|
||||
<a href="#24-swagger-ui-可视化接口文档">2.4 swagger-ui 可视化接口文档</a><br/>
|
||||
<a href="#25-利用swagger-ui进行接口测试">2.5 利用swagger-ui进行接口测试</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
## 一、Springfox 与 Swagger 简介
|
||||
|
||||
### 1.1 Springfox
|
||||
|
||||
Springfox 是一个开源的 API Doc 的框架, 它的前身是 swagger-springmvc,能够完美的支持 springmvc,可以将 spring 接口方法自动转换为接口文档。 目前 spring fox 正致力于对更多 JSON API 规范和标准的扩展和支持,例如:[swagger](http://swagger.io/),[RAML](http://raml.org/) 和[jsonapi](http://jsonapi.org/)。
|
||||
|
||||
### 1.2 Swagger
|
||||
|
||||
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务,支持从整个 API 生命周期(从设计和文档到测试和部署)的开发。
|
||||
|
||||
swagger 是一个综合的开源项目,包含[swagger-core](https://github.com/swagger-api/swagger-core)、[swagger-ui](https://github.com/swagger-api/swagger-ui)、[swagger-codegen](https://github.com/swagger-api/swagger-codegen)、[swagger-editor](https://github.com/swagger-api/swagger-editor) 等多个子项目。
|
||||
|
||||
+ **swagger-core**:Swagger Core 是 OpenAPI 规范(以前称为 Swagger 规范)的**Java 实现**。
|
||||
|
||||
+ **swagger-ui**:依据可视化文档,提供与 API 资源的可视化交互。
|
||||
|
||||
+ **swagger-codegen**:开源的代码生成器,根据 Swagger 定义的 RESTful API 可以自动建立服务端和客户端的连接。
|
||||
|
||||
+ **swagger-editor**:开源的 api 文档编辑器。
|
||||
|
||||
下图为 swagger-ui 提供的文档可视化界面示例:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/Swagger_UI.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.3 OpenApi、Swagger、Springfox的关系
|
||||
|
||||
**Swagger Core 是 OpenApi 规范(以前称为 Swagger 规范)的 Java 实现,而 Springfox 提供 Swagger 与 spring 的集成支持**。<br/>
|
||||
|
||||
|
||||
|
||||
## 二、spring boot 集成 swagger 2.0
|
||||
|
||||
### 2.1 导入项目相关依赖
|
||||
|
||||
```xml
|
||||
<!--swagger2-->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
<!--swagger-ui -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 2.2 进行swagger个性化配置、并用@EnableSwagger2开启Swagger支持
|
||||
|
||||
这里需要说明的是 swagger 虽然是一个非常直观易用的接口调试插件,但是有可能导致接口信息泄露的危险,所以建议在开发环境和测试环境开启,在生产环境关闭。这里一共给出三种 Swagger 开关切换的方法:
|
||||
|
||||
1. 如下面代码所示,在配置文件中配置自定义的开关参数,并在创建 Docket 时候,在链式调用的 enable() 方法中传入;
|
||||
|
||||
2. 在 `SwaggerConfig` 配置类上添加 `@Profile({"dev","test"}) ` 注解,指明在开发环境和测试环境下激活此配置类,打包或者部署时候使用 spring.profiles.active 指明环境即可;
|
||||
|
||||
3. 在配置文件中配置自定义的开关参数,并在 `SwaggerConfig` 配置类上添加 `@ConditionalOnProperty(name = "swagger.enable", havingValue = "true") `,指明配置类的生效条件
|
||||
|
||||
注:@ConditionalOnProperty 注解说明
|
||||
|
||||
@ConditionalOnProperty 注解能够控制某个@configuration 修饰的配置类是否生效。具体操作是通过 name 和 havingValue 属性来实现,name 对应 application.properties(yml)中的某个属性值,如果该值为空,则返回 false;如果值不为空,则将该值与 havingValue 指定的值进行比较,如果一样则返回 true;否则返回 false。如果返回值为 false,则该 configuration 不生效;为 true 则生效。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Swagger 配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Value("${swagger.enable}")
|
||||
private boolean swaggerEnable;
|
||||
|
||||
/***
|
||||
* 配置 swagger
|
||||
* 开发和测试环境下可以开启 swagger 辅助进行调试,而生产环境下可以关闭或者进行相应的权限控制,防止接口信息泄露
|
||||
*/
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.enable(swaggerEnable)
|
||||
.apiInfo(apiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.heibaiying.springboot.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.paths(doFilteringRules())
|
||||
.build();
|
||||
}
|
||||
|
||||
/***
|
||||
* 接口文档的描述信息
|
||||
*/
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("spring boot swagger2 用例")
|
||||
.description("描述")
|
||||
.licenseUrl("https://mit-license.org/")
|
||||
.version("1.0")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 可以使用正则定义 url 过滤规则
|
||||
*/
|
||||
private Predicate<String> doFilteringRules() {
|
||||
return not(
|
||||
regex("/ignore/*")
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
application.properties :
|
||||
|
||||
```properties
|
||||
#swagger启用开关
|
||||
swagger.enable = true
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 2.3 swagger注解的使用和说明
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@Api(value = "产品接口", description = "产品信息接口")
|
||||
@RestController
|
||||
public class ProductController {
|
||||
|
||||
/***
|
||||
* 一个标准的 swagger 注解
|
||||
*/
|
||||
@ApiOperation(notes = "查询所有产品", value = "产品查询接口")
|
||||
@ApiImplicitParams(
|
||||
@ApiImplicitParam(name = "id", value = "产品编号", paramType = "path", defaultValue = "1")
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(code = 200, message = "请求成功"),
|
||||
@ApiResponse(code = 400, message = "无效的请求"),
|
||||
@ApiResponse(code = 401, message = "未经过授权认证"),
|
||||
@ApiResponse(code = 403, message = "已经过授权认证,但是没有该资源对应的访问权限"),
|
||||
@ApiResponse(code = 404, message = "服务器找不到给定的资源,商品不存在"),
|
||||
@ApiResponse(code = 500, message = "服务器错误")
|
||||
})
|
||||
@GetMapping(value = "/product/{id}", produces = "application/json")
|
||||
public ResponseEntity<Product> getProduct(@PathVariable long id) {
|
||||
Product product = new Product(id, "product" + id, new Date());
|
||||
return ResponseEntity.ok(product);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 如果用实体类接收参数,则用实体类对应的属性名称指定参数
|
||||
*/
|
||||
@ApiOperation(notes = "保存产品", value = "产品保存接口")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "id", value = "产品编号", paramType = "body", defaultValue = "1"),
|
||||
@ApiImplicitParam(name = "name", value = "产品名称", paramType = "body"),
|
||||
@ApiImplicitParam(name = "date", value = "产品生产日期", paramType = "body")
|
||||
}
|
||||
)
|
||||
@PostMapping(value = "/product")
|
||||
public ResponseEntity<Void> saveProduct(@RequestBody Product product) {
|
||||
System.out.println(product);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 在配置类中指明了该接口不被扫描到,可以在配置类中使用正则指定某一类符合规则的接口不被扫描到
|
||||
*/
|
||||
@ApiOperation(notes = "该接口会被忽略", value = "产品保存接口")
|
||||
@PostMapping(value = "/ignore")
|
||||
public ResponseEntity<Product> ignore() {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 不加上任何 swagger 相关的注解也会被扫描到 如果不希望被扫描到,需要用 @ApiIgnore 修饰
|
||||
*/
|
||||
@PostMapping(value = "/normal")
|
||||
public ResponseEntity<Void> normal() {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@ApiIgnore
|
||||
@PostMapping(value = "/apiIgnore")
|
||||
public ResponseEntity<Void> apiIgnore() {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
swagger 为了最大程度防止对逻辑代码的侵入,基本都是依靠注解来完成文档描述。常用注解如下:
|
||||
|
||||
| Annotation | Attribute | Target Property | Description |
|
||||
| ---------------- | ------------ | ------------------------- | ------------------------------------------------------------ |
|
||||
| RequestHeader | defaultValue | Parameter#defaultValue | e.g. `@RequestHeader(defaultValue="${param1.defaultValue}")` |
|
||||
| ApiModelProperty | value | ModelProperty#description | e.g. `@ApiModelProperty(value="${property1.description}")` |
|
||||
| ApiModelProperty | description | ModelProperty#description | e.g. `@ApiModelProperty(notes="${property1.description}")` |
|
||||
| ApiParam | value | Parameter#description | e.g. `@ApiParam(value="${param1.description}")` |
|
||||
| ApiImplicitParam | value | Parameter#description | e.g. `@ApiImplicitParam(value="${param1.description}")` |
|
||||
| ApiOperation | notes | Operation#notes | e.g. `@ApiOperation(notes="${operation1.description}")` |
|
||||
| ApiOperation | summary | Operation#summary | e.g. `@ApiOperation(value="${operation1.summary}")` |
|
||||
| RequestParam | defaultValue | Parameter#defaultValue | e.g. `@RequestParam(defaultValue="${param1.defaultValue}")` |
|
||||
|
||||
1. `@Api`:Api 用在类上,说明该类的作用;
|
||||
|
||||
2. `@ApiOperation`:用在方法上,说明方法的作用;
|
||||
|
||||
3. `@ApiParam`:用在参数上,说明参数的作用;
|
||||
|
||||
4. `@ApiImplicitParams`:用在方法上说明方法参数的作用;
|
||||
|
||||
5. `@ApiImplicitParam`:用在@ApiImplicitParams 注解中,描述每个具体参数;
|
||||
|
||||
6. `@ApiResponses`:一组@ApiResponse 的配置;
|
||||
|
||||
7. `@ApiResponse`:请求返回的配置;
|
||||
|
||||
8. `@ResponseHeader`:响应头的配置;
|
||||
|
||||
9. `@ApiModel`:描述一个 Model 的信息(一般用在 post 创建的时候,使用@RequestBody 接收参数的场景);
|
||||
|
||||
10. `@ApiModelProperty`:描述 model 的属性。
|
||||
|
||||
11. `@ApiIgnore`:可以用于类、方法、属性,代表该方法、类、属性不被 swagger 的文档所管理。
|
||||
|
||||
|
||||
|
||||
### 2.4 swagger-ui 可视化接口文档
|
||||
|
||||
接口文档访问地址:http://localhost:8080/swagger-ui.html ,文档主界面如下:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/swagger-ui-index.png"/> </div>
|
||||
|
||||
### 2.5 利用swagger-ui进行接口测试
|
||||
|
||||
|
||||
点击对应接口的 `try it out` 按钮可以进行接口测试,此时可以输入对应的参数的值,然后点击下方的 `Execute` 按钮发送请求。<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/swagger-try-it.png"/> </div>
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/swagger-execute.png"/> </div>
|
||||
|
||||
post 方法可以直接修改 model 对应的 json 数据 ,然后点击下方的 `Execute` 按钮发送请求。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/swagger-post-try.png"/> </div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring boot 整合 tomcat
|
||||
|
||||
# spring boot 整合 tomcat
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-项目主要依赖">1.2 项目主要依赖</a><br/>
|
||||
@@ -11,102 +12,102 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
spring boot 整合 tomcat 后支持jsp 的使用(内置容器默认是不支持jsp),所以项目整合后采用jspController 跳转到show.jsp测试整合是否成功。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-tomcat.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<!--排除内置容器依赖 使用外部tomcat容器启动-->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!--使用外置容器时候SpringBootServletInitializer 依赖此包 -->
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、整合 tomcat
|
||||
|
||||
#### 2.1 修改启动类,继承自SpringBootServletInitializer,并覆盖重写其中configure方法
|
||||
|
||||
```java
|
||||
/**
|
||||
* 如果用外置tomcat,启动报错java.lang.NoClassDefFoundError: javax/el/ELManager
|
||||
* 是因为tomcat 7.0 el-api包中没有ELManager类 , 切换tomcat 为8.0 以上版本即可
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SpringBootTomcatApplication extends SpringBootServletInitializer {
|
||||
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
//传入SpringBoot应用的主程序
|
||||
return application.sources(SpringBootTomcatApplication.class);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootTomcatApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀
|
||||
|
||||
```yml
|
||||
spring:
|
||||
mvc:
|
||||
view:
|
||||
prefix: /WEB-INF/jsp/
|
||||
suffix: .jsp
|
||||
```
|
||||
|
||||
#### 2.3 新建controller和show.jsp 测试整合是否成功
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@RequestMapping("index")
|
||||
public class JspController {
|
||||
|
||||
@RequestMapping
|
||||
public String jsp(Model model){
|
||||
Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
|
||||
model.addAttribute("programmer",programmer);
|
||||
return "show";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>programmer</title>
|
||||
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>姓名: ${programmer.name}</li>
|
||||
<li>年龄: ${programmer.age}</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构说明
|
||||
|
||||
spring boot 整合 tomcat 后支持 jsp 的使用(内置容器默认是不支持 jsp),所以项目整合后采用 jspController 跳转到 show.jsp 测试整合是否成功。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-tomcat.png"/> </div>
|
||||
|
||||
#### 1.2 项目主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<!--排除内置容器依赖 使用外部 tomcat 容器启动-->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!--使用外置容器时候 SpringBootServletInitializer 依赖此包 -->
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>2.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、整合 tomcat
|
||||
|
||||
#### 2.1 修改启动类,继承自SpringBootServletInitializer,并覆盖重写其中configure方法
|
||||
|
||||
```java
|
||||
/**
|
||||
* 如果用外置 tomcat,启动报错 java.lang.NoClassDefFoundError: javax/el/ELManager
|
||||
* 是因为 tomcat 7.0 el-api 包中没有 ELManager 类 , 切换 tomcat 为 8.0 以上版本即可
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SpringBootTomcatApplication extends SpringBootServletInitializer {
|
||||
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
//传入 SpringBoot 应用的主程序
|
||||
return application.sources(SpringBootTomcatApplication.class);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootTomcatApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 在application.yml 中指定访问视图文件的前缀和后缀
|
||||
|
||||
```yml
|
||||
spring:
|
||||
mvc:
|
||||
view:
|
||||
prefix: /WEB-INF/jsp/
|
||||
suffix: .jsp
|
||||
```
|
||||
|
||||
#### 2.3 新建controller和show.jsp 测试整合是否成功
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@RequestMapping("index")
|
||||
public class JspController {
|
||||
|
||||
@RequestMapping
|
||||
public String jsp(Model model){
|
||||
Programmer programmer = new Programmer("heibai", 21, 1298.31f, LocalDate.now());
|
||||
model.addAttribute("programmer",programmer);
|
||||
return "show";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>programmer</title>
|
||||
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/show.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>姓名: ${programmer.name}</li>
|
||||
<li>年龄: ${programmer.age}</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
@@ -1,196 +1,196 @@
|
||||
# spring boot websocket
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-主要依赖">1.2 主要依赖</a><br/>
|
||||
<a href="#二spring-boot-websocket">二、spring boot websocket</a><br/>
|
||||
<a href="#21-创建消息处理类ChatSocket使用ServerEndpoint声明websocket服务">2.1 创建消息处理类ChatSocket,使用@ServerEndpoint声明websocket服务</a><br/>
|
||||
<a href="#22-配置ServerEndpointExporterServerEndpointExporter会在运行时候自动注册我们用ServerEndpoint声明的websocket服务">2.2 配置ServerEndpointExporter,ServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务</a><br/>
|
||||
<a href="#23-前端websocket的实现">2.3 前端websocket的实现</a><br/>
|
||||
<a href="#24-简单登录的实现">2.4 简单登录的实现</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
|
||||
2. 关于websocket的主要配置在websocket文件夹下;
|
||||
3. 模板引擎采用freemaker;
|
||||
4. 项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-websocket.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--spring boot webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring boot websocket
|
||||
|
||||
#### 2.1 创建消息处理类ChatSocket,使用@ServerEndpoint声明websocket服务
|
||||
|
||||
```java
|
||||
@ServerEndpoint(value = "/socket/{username}")
|
||||
@Component
|
||||
public class ChatSocket {
|
||||
|
||||
/**
|
||||
* 建立连接时候触发
|
||||
*/
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam("username") String username) {
|
||||
// 这个方法是线程不安全的
|
||||
Constant.nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 关闭连接时候触发
|
||||
*/
|
||||
@OnClose
|
||||
public void onClose(Session session, @PathParam("username") String username) {
|
||||
Constant.nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息
|
||||
*/
|
||||
@OnMessage
|
||||
public void onMessage(Session session, String message, @PathParam("username") String username) throws UnsupportedEncodingException {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message, "utf-8");
|
||||
// 简单模拟群发消息
|
||||
Constant.nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.getBasicRemote().sendText(username + " : " + msg);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置ServerEndpointExporter,ServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务。
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class WebSocketConfig {
|
||||
|
||||
/***
|
||||
* 检测{@link javax.websocket.server.ServerEndpointConfig}和{@link ServerEndpoint} 类型的bean,
|
||||
* 并在运行时使用标准Java WebSocket时注册。
|
||||
* 我们在{@link com.heibaiying.springboot.websocket.WebSocketConfig}中就是使用@ServerEndpoint去声明websocket服务
|
||||
*/
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 前端websocket的实现
|
||||
|
||||
```jsp
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>${Session["username"]}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h5>${Session["username"]}您好!欢迎进入群聊大厅!</h5>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket/${Session["username"]}");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭ws
|
||||
window.addEventListener("beforeunload", function (event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 简单登录的实现
|
||||
|
||||
```java
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @description : 简单登录
|
||||
*/
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session) {
|
||||
session.setAttribute(Constant.USER_NAME, username);
|
||||
return "chat";
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# spring boot websocket
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-主要依赖">1.2 主要依赖</a><br/>
|
||||
<a href="#二spring-boot-websocket">二、spring boot websocket</a><br/>
|
||||
<a href="#21-创建消息处理类ChatSocket使用ServerEndpoint声明websocket服务">2.1 创建消息处理类ChatSocket,使用@ServerEndpoint声明websocket服务</a><br/>
|
||||
<a href="#22-配置ServerEndpointExporterServerEndpointExporter会在运行时候自动注册我们用ServerEndpoint声明的websocket服务">2.2 配置ServerEndpointExporter,ServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务</a><br/>
|
||||
<a href="#23-前端websocket的实现">2.3 前端websocket的实现</a><br/>
|
||||
<a href="#24-简单登录的实现">2.4 简单登录的实现</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在 session 当中;
|
||||
2. 关于 websocket 的主要配置在 websocket 文件夹下;
|
||||
3. 模板引擎采用 freemaker;
|
||||
4. 项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-websocket.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 主要依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--spring boot webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-freemarker</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring boot websocket
|
||||
|
||||
#### 2.1 创建消息处理类ChatSocket,使用@ServerEndpoint声明websocket服务
|
||||
|
||||
```java
|
||||
@ServerEndpoint(value = "/socket/{username}")
|
||||
@Component
|
||||
public class ChatSocket {
|
||||
|
||||
/**
|
||||
* 建立连接时候触发
|
||||
*/
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam("username") String username) {
|
||||
// 这个方法是线程不安全的
|
||||
Constant.nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 关闭连接时候触发
|
||||
*/
|
||||
@OnClose
|
||||
public void onClose(Session session, @PathParam("username") String username) {
|
||||
Constant.nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息
|
||||
*/
|
||||
@OnMessage
|
||||
public void onMessage(Session session, String message, @PathParam("username") String username) throws UnsupportedEncodingException {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message, "utf-8");
|
||||
// 简单模拟群发消息
|
||||
Constant.nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.getBasicRemote().sendText(username + " : " + msg);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置ServerEndpointExporter,ServerEndpointExporter会在运行时候自动注册我们用@ServerEndpoint声明的websocket服务。
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class WebSocketConfig {
|
||||
|
||||
/***
|
||||
* 检测{@link javax.websocket.server.ServerEndpointConfig}和{@link ServerEndpoint} 类型的 bean,
|
||||
* 并在运行时使用标准 Java WebSocket 时注册。
|
||||
* 我们在{@link com.heibaiying.springboot.websocket.WebSocketConfig}中就是使用@ServerEndpoint 去声明 websocket 服务
|
||||
*/
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 前端websocket的实现
|
||||
|
||||
```jsp
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>${Session["username"]}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<h5>${Session["username"]}您好!欢迎进入群聊大厅!</h5>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket/${Session["username"]}");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭 ws
|
||||
window.addEventListener("beforeunload", function (event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 简单登录的实现
|
||||
|
||||
```java
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @description : 简单登录
|
||||
*/
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session) {
|
||||
session.setAttribute(Constant.USER_NAME, username);
|
||||
return "chat";
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring-boot-yml-profile
|
||||
|
||||
# spring-boot-yml-profile
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一项目结构">一、项目结构</a><br/>
|
||||
<a href="#二常用-yaml-语法讲解">二、常用 yaml 语法讲解</a><br/>
|
||||
<a href="#21-基本规则">2.1 基本规则</a><br/>
|
||||
@@ -16,178 +17,178 @@
|
||||
<a href="#六优先级的说明">六、优先级的说明</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
## 一、项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-yml-profile.png"/> </div>
|
||||
|
||||
## 二、常用 yaml 语法讲解
|
||||
|
||||
项目中的yml配置文件如下:
|
||||
|
||||
```yaml
|
||||
programmer:
|
||||
name: xiaoming-DEV
|
||||
married: false
|
||||
hireDate: 2018/12/23
|
||||
salary: 66666.88
|
||||
random: ${random.int[1024,65536]}
|
||||
skill: {java: master, jquery: proficiency}
|
||||
company: [baidu,tengxun,alibaba]
|
||||
school:
|
||||
name: unviersity
|
||||
location: shanghai
|
||||
```
|
||||
|
||||
#### 2.1 基本规则
|
||||
|
||||
1. 大小写敏感
|
||||
2. 使用缩进表示层级关系
|
||||
3. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。
|
||||
4. 使用#表示注释
|
||||
5. 字符串默认不用加单双引号,但单引号和双引号都可以使用,双引号不会对特殊字符转义。
|
||||
6. YAML中提供了多种常量结构,包括:整数,浮点数,字符串,NULL,日期,布尔,时间。
|
||||
|
||||
#### 2.2 对象的写法
|
||||
|
||||
```yaml
|
||||
key: value
|
||||
```
|
||||
|
||||
#### 2.3 map的写法
|
||||
|
||||
```yaml
|
||||
# 写法一 同一缩进的所有键值对属于一个map
|
||||
key:
|
||||
key1: value1
|
||||
key2: value2
|
||||
|
||||
# 写法二
|
||||
{key1: value1, key2: value2}
|
||||
```
|
||||
|
||||
#### 2.3 数组的写法
|
||||
|
||||
```yaml
|
||||
# 写法一 使用一个短横线加一个空格代表一个数组项
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
|
||||
# 写法二
|
||||
[a,b,c]
|
||||
```
|
||||
|
||||
#### 2.5 单双引号
|
||||
|
||||
单引号和双引号都可以使用,双引号不会对特殊字符转义。
|
||||
|
||||
```yaml
|
||||
s1: '内容\n字符串'
|
||||
s2: "内容\n字符串"
|
||||
|
||||
转换后:
|
||||
{ s1: '内容\\n字符串', s2: '内容\n字符串' }
|
||||
```
|
||||
|
||||
#### 2.6 特殊符号
|
||||
|
||||
--- YAML可以在同一个文件中,使用---表示一个文档的开始。
|
||||
|
||||
|
||||
|
||||
## 三、spring boot 与 yaml
|
||||
|
||||
#### 3.1 spring boot 支持使用 ${app.name} 引用预先定义的值
|
||||
|
||||
```properties
|
||||
appName: MyApp
|
||||
appDescription: ${app.name} is a Spring Boot application
|
||||
```
|
||||
|
||||
#### 3.2 spring boot 支持使用 ${random.xxx} 配置随机值
|
||||
|
||||
```properties
|
||||
my.secret: ${random.value}
|
||||
my.number: ${random.int}
|
||||
my.bignumber: ${random.long}
|
||||
my.number.less.than.ten: ${random.int(10)}
|
||||
my.number.in.range: ${random.int[1024,65536]}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、@ConfigurationProperties实现属性绑定
|
||||
|
||||
```java
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "programmer")
|
||||
@Data
|
||||
@ToString
|
||||
public class Programmer {
|
||||
|
||||
private String name;
|
||||
private int age;
|
||||
private boolean married;
|
||||
private Date hireDate;
|
||||
private float salary;
|
||||
private int random;
|
||||
private Map<String, String> skill;
|
||||
private List company;
|
||||
private School school;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Spring Boot将环境属性绑定到@ConfigurationProperties beans时会使用一些宽松的规则,称之为松散绑定。所以Environment属性名和bean属性名不需要精确匹配。常见的示例中有用的包括虚线分割(比如,context-path绑定到contextPath),将环境属性转为大写字母(比如,PORT绑定port)。
|
||||
|
||||
需要注意的是`@Value`是不支持松散绑定的,所以建议除非有特殊的需求,否则在`ConfigurationProperties`和`value` 配置属性的时候最好都保持属性和变量的一致,以免造成不必要的勿扰。
|
||||
|
||||
|
||||
|
||||
## 五、多配置文件
|
||||
|
||||
多配置文件可以在同一个yml中使用 --- 分割为多个配置,或者遵循application-xxx.yml 的方式命名拆分为多个文件,并在主配置文件application.yml 中确定激活哪个配置文件,当然也可在命令行中确定,命令行的优先级大于配置文件。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/profile.png"/> </div>
|
||||
|
||||
```yaml
|
||||
# 配置文件中激活配置
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
```
|
||||
|
||||
```shell
|
||||
# 命令行参数激活配置
|
||||
--spring.profiles.active=dev
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 六、优先级的说明
|
||||
|
||||
Spring Boot设计了一个非常特别的PropertySource顺序,以允许对属性值进行合理的覆盖,属性会以如下的顺序进行设值:
|
||||
|
||||
1. home目录下的devtools全局设置属性(~/.spring-boot-devtools.properties,如果devtools激活)。
|
||||
2. 测试用例上的@TestPropertySource注解。
|
||||
3. 测试用例上的@SpringBootTest#properties注解。
|
||||
4. 命令行参数
|
||||
5. 来自SPRING_APPLICATION_JSON的属性(环境变量或系统属性中内嵌的内联JSON)。
|
||||
6. ServletConfig初始化参数。
|
||||
7. ServletContext初始化参数。
|
||||
8. 来自于java:comp/env的JNDI属性。
|
||||
9. Java系统属性(System.getProperties())。
|
||||
10. 操作系统环境变量。
|
||||
11. RandomValuePropertySource,只包含random.*中的属性。
|
||||
12. 没有打进jar包的Profile-specific应用属性(application-{profile}.properties和YAML变量)
|
||||
13. 打进jar包中的Profile-specific应用属性(application-{profile}.properties和YAML变量)。
|
||||
14. 没有打进jar包的应用配置(application.properties和YAML变量)。
|
||||
15. 打进jar包中的应用配置(application.properties和YAML变量)。
|
||||
16. @Configuration类上的@PropertySource注解。
|
||||
17. 默认属性(使用SpringApplication.setDefaultProperties指定)。
|
||||
|
||||
这里做一下说明,上文第12,14 点没有打进jar包的文件指的是在启动时候通过`spring.config.location`参数指定的外部配置文件,外部配置文件的优先级应该是大于jar中的配置文件。
|
||||
|
||||
对上面的配置中常用的规则可以精简如下:
|
||||
|
||||
## 一、项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-boot-yml-profile.png"/> </div>
|
||||
|
||||
## 二、常用 yaml 语法讲解
|
||||
|
||||
项目中的 yml 配置文件如下:
|
||||
|
||||
```yaml
|
||||
programmer:
|
||||
name: xiaoming-DEV
|
||||
married: false
|
||||
hireDate: 2018/12/23
|
||||
salary: 66666.88
|
||||
random: ${random.int[1024,65536]}
|
||||
skill: {java: master, jquery: proficiency}
|
||||
company: [baidu,tengxun,alibaba]
|
||||
school:
|
||||
name: unviersity
|
||||
location: shanghai
|
||||
```
|
||||
|
||||
#### 2.1 基本规则
|
||||
|
||||
1. 大小写敏感
|
||||
2. 使用缩进表示层级关系
|
||||
3. 缩进长度没有限制,只要元素对齐就表示这些元素属于一个层级。
|
||||
4. 使用#表示注释
|
||||
5. 字符串默认不用加单双引号,但单引号和双引号都可以使用,双引号不会对特殊字符转义。
|
||||
6. YAML 中提供了多种常量结构,包括:整数,浮点数,字符串,NULL,日期,布尔,时间。
|
||||
|
||||
#### 2.2 对象的写法
|
||||
|
||||
```yaml
|
||||
key: value
|
||||
```
|
||||
|
||||
#### 2.3 map的写法
|
||||
|
||||
```yaml
|
||||
# 写法一 同一缩进的所有键值对属于一个map
|
||||
key:
|
||||
key1: value1
|
||||
key2: value2
|
||||
|
||||
# 写法二
|
||||
{key1: value1, key2: value2}
|
||||
```
|
||||
|
||||
#### 2.3 数组的写法
|
||||
|
||||
```yaml
|
||||
# 写法一 使用一个短横线加一个空格代表一个数组项
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
|
||||
# 写法二
|
||||
[a,b,c]
|
||||
```
|
||||
|
||||
#### 2.5 单双引号
|
||||
|
||||
单引号和双引号都可以使用,双引号不会对特殊字符转义。
|
||||
|
||||
```yaml
|
||||
s1: '内容\n 字符串'
|
||||
s2: "内容\n 字符串"
|
||||
|
||||
转换后:
|
||||
{ s1: '内容\\n 字符串', s2: '内容\n 字符串' }
|
||||
```
|
||||
|
||||
#### 2.6 特殊符号
|
||||
|
||||
--- YAML 可以在同一个文件中,使用---表示一个文档的开始。
|
||||
|
||||
|
||||
|
||||
## 三、spring boot 与 yaml
|
||||
|
||||
#### 3.1 spring boot 支持使用 ${app.name} 引用预先定义的值
|
||||
|
||||
```properties
|
||||
appName: MyApp
|
||||
appDescription: ${app.name} is a Spring Boot application
|
||||
```
|
||||
|
||||
#### 3.2 spring boot 支持使用 ${random.xxx} 配置随机值
|
||||
|
||||
```properties
|
||||
my.secret: ${random.value}
|
||||
my.number: ${random.int}
|
||||
my.bignumber: ${random.long}
|
||||
my.number.less.than.ten: ${random.int(10)}
|
||||
my.number.in.range: ${random.int[1024,65536]}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、@ConfigurationProperties实现属性绑定
|
||||
|
||||
```java
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "programmer")
|
||||
@Data
|
||||
@ToString
|
||||
public class Programmer {
|
||||
|
||||
private String name;
|
||||
private int age;
|
||||
private boolean married;
|
||||
private Date hireDate;
|
||||
private float salary;
|
||||
private int random;
|
||||
private Map<String, String> skill;
|
||||
private List company;
|
||||
private School school;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Spring Boot 将环境属性绑定到@ConfigurationProperties beans 时会使用一些宽松的规则,称之为松散绑定。所以 Environment 属性名和 bean 属性名不需要精确匹配。常见的示例中有用的包括虚线分割(比如,context-path 绑定到 contextPath),将环境属性转为大写字母(比如,PORT 绑定 port)。
|
||||
|
||||
需要注意的是 `@Value` 是不支持松散绑定的,所以建议除非有特殊的需求,否则在 `ConfigurationProperties` 和 `value` 配置属性的时候最好都保持属性和变量的一致,以免造成不必要的勿扰。
|
||||
|
||||
|
||||
|
||||
## 五、多配置文件
|
||||
|
||||
多配置文件可以在同一个 yml 中使用 --- 分割为多个配置,或者遵循 application-xxx.yml 的方式命名拆分为多个文件,并在主配置文件 application.yml 中确定激活哪个配置文件,当然也可在命令行中确定,命令行的优先级大于配置文件。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/profile.png"/> </div>
|
||||
|
||||
```yaml
|
||||
# 配置文件中激活配置
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
```
|
||||
|
||||
```shell
|
||||
# 命令行参数激活配置
|
||||
--spring.profiles.active=dev
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 六、优先级的说明
|
||||
|
||||
Spring Boot 设计了一个非常特别的 PropertySource 顺序,以允许对属性值进行合理的覆盖,属性会以如下的顺序进行设值:
|
||||
|
||||
1. home 目录下的 devtools 全局设置属性(~/.spring-boot-devtools.properties,如果 devtools 激活)。
|
||||
2. 测试用例上的@TestPropertySource 注解。
|
||||
3. 测试用例上的@SpringBootTest#properties 注解。
|
||||
4. 命令行参数
|
||||
5. 来自 SPRING_APPLICATION_JSON 的属性(环境变量或系统属性中内嵌的内联 JSON)。
|
||||
6. ServletConfig 初始化参数。
|
||||
7. ServletContext 初始化参数。
|
||||
8. 来自于 java:comp/env 的 JNDI 属性。
|
||||
9. Java 系统属性(System.getProperties())。
|
||||
10. 操作系统环境变量。
|
||||
11. RandomValuePropertySource,只包含 random.*中的属性。
|
||||
12. 没有打进 jar 包的 Profile-specific 应用属性(application-{profile}.properties 和 YAML 变量)
|
||||
13. 打进 jar 包中的 Profile-specific 应用属性(application-{profile}.properties 和 YAML 变量)。
|
||||
14. 没有打进 jar 包的应用配置(application.properties 和 YAML 变量)。
|
||||
15. 打进 jar 包中的应用配置(application.properties 和 YAML 变量)。
|
||||
16. @Configuration 类上的@PropertySource 注解。
|
||||
17. 默认属性(使用 SpringApplication.setDefaultProperties 指定)。
|
||||
|
||||
这里做一下说明,上文第 12,14 点没有打进 jar 包的文件指的是在启动时候通过 `spring.config.location` 参数指定的外部配置文件,外部配置文件的优先级应该是大于 jar 中的配置文件。
|
||||
|
||||
对上面的配置中常用的规则可以精简如下:
|
||||
|
||||
**命令行 > application-{profile}.yml > application.yml > 默认属性**
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
### 1.1 项目说明
|
||||
|
||||
本用例基于spring boot + druid + mybatis 配置多数据源,并采用 **JTA 实现分布式事务**。
|
||||
本用例基于 spring boot + druid + mybatis 配置多数据源,并采用 **JTA 实现分布式事务**。
|
||||
|
||||
### 1.2 项目结构
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
|
||||
### 2.1 导入基本依赖
|
||||
|
||||
除了mybatis 、durid 等依赖外,我们依靠切面来实现动态数据源的切换,所以还需要导入aop依赖。
|
||||
除了 mybatis 、durid 等依赖外,我们依靠切面来实现动态数据源的切换,所以还需要导入 aop 依赖。
|
||||
|
||||
最主要的是还要导入spring-boot-starter-jta-atomikos,Spring Boot通过[Atomkos](http://www.atomikos.com/)或[Bitronix](http://docs.codehaus.org/display/BTM/Home)的内嵌事务管理器支持跨多个XA资源的分布式JTA事务,当发现JTA环境时,Spring Boot将使用Spring的JtaTransactionManager来管理事务。自动配置的JMS,DataSource和JPA beans将被升级以支持XA事务。
|
||||
最主要的是还要导入 spring-boot-starter-jta-atomikos,Spring Boot 通过[Atomkos](http://www.atomikos.com/) 或[Bitronix](http://docs.codehaus.org/display/BTM/Home) 的内嵌事务管理器支持跨多个 XA 资源的分布式 JTA 事务,当发现 JTA 环境时,Spring Boot 将使用 Spring 的 JtaTransactionManager 来管理事务。自动配置的 JMS,DataSource 和 JPA beans 将被升级以支持 XA 事务。
|
||||
|
||||
```xml
|
||||
<!--mybatis starter-->
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
**注意**:Spring Boot 2.X 版本不再支持配置继承,多数据源的话每个数据源的所有配置都需要单独配置,否则配置不会生效。
|
||||
|
||||
这里我用的本地数据库mysql和mysql02,配置如下:
|
||||
这里我用的本地数据库 mysql 和 mysql02,配置如下:
|
||||
|
||||
```yml
|
||||
spring:
|
||||
@@ -92,29 +92,29 @@ spring:
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
|
||||
# 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时
|
||||
initialSize: 5
|
||||
# 最小连接池数量
|
||||
minIdle: 5
|
||||
# 最大连接池数量
|
||||
maxActive: 10
|
||||
# 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
|
||||
# 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
|
||||
maxWait: 60000
|
||||
# Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
|
||||
# Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
# 连接保持空闲而不被驱逐的最小时间
|
||||
minEvictableIdleTimeMillis: 300000
|
||||
# 用来检测连接是否有效的sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
|
||||
# 用来检测连接是否有效的 sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
|
||||
validationQuery: SELECT 1
|
||||
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
|
||||
# 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
|
||||
testWhileIdle: true
|
||||
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
|
||||
# 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
|
||||
testOnBorrow: false
|
||||
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
|
||||
# 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
|
||||
testOnReturn: false
|
||||
# 是否自动回收超时连接
|
||||
removeAbandoned: true
|
||||
# 超时时间(以秒数为单位)
|
||||
# 超时时间 (以秒数为单位)
|
||||
remove-abandoned-timeout: 1800
|
||||
|
||||
db2:
|
||||
@@ -123,45 +123,45 @@ spring:
|
||||
password: root
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
|
||||
# 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时
|
||||
initialSize: 6
|
||||
# 最小连接池数量
|
||||
minIdle: 6
|
||||
# 最大连接池数量
|
||||
maxActive: 10
|
||||
# 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
|
||||
# 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
|
||||
maxWait: 60000
|
||||
# Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
|
||||
# Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
# 连接保持空闲而不被驱逐的最小时间
|
||||
minEvictableIdleTimeMillis: 300000
|
||||
# 用来检测连接是否有效的sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
|
||||
# 用来检测连接是否有效的 sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
|
||||
validationQuery: SELECT 1
|
||||
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
|
||||
# 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
|
||||
testWhileIdle: true
|
||||
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
|
||||
# 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
|
||||
testOnBorrow: false
|
||||
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
|
||||
# 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
|
||||
testOnReturn: false
|
||||
# 是否自动回收超时连接
|
||||
removeAbandoned: true
|
||||
# 超时时间(以秒数为单位)
|
||||
# 超时时间 (以秒数为单位)
|
||||
remove-abandoned-timeout: 1800
|
||||
|
||||
# WebStatFilter用于采集web-jdbc关联监控的数据。
|
||||
# WebStatFilter 用于采集 web-jdbc 关联监控的数据。
|
||||
web-stat-filter:
|
||||
# 是否开启 WebStatFilter 默认是true
|
||||
# 是否开启 WebStatFilter 默认是 true
|
||||
enabled: true
|
||||
# 需要拦截的url
|
||||
# 需要拦截的 url
|
||||
url-pattern: /*
|
||||
# 排除静态资源的请求
|
||||
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
|
||||
|
||||
# Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。
|
||||
# Druid 内置提供了一个 StatViewServlet 用于展示 Druid 的统计信息。
|
||||
stat-view-servlet:
|
||||
#是否启用StatViewServlet 默认值true
|
||||
#是否启用 StatViewServlet 默认值 true
|
||||
enabled: true
|
||||
# 需要拦截的url
|
||||
# 需要拦截的 url
|
||||
url-pattern: /druid/*
|
||||
# 允许清空统计数据
|
||||
reset-enable: true
|
||||
@@ -188,9 +188,9 @@ public class DruidMybatisMultiApplication {
|
||||
|
||||
#### 2. 创建多数据源配置类`DataSourceFactory.java`, 手动配置多数据源
|
||||
|
||||
+ 这里我们创建druid数据源的时候,创建的是`DruidXADataSource`,它继承自`DruidDataSource `并支持XA分布式事务;
|
||||
+ 使用 `AtomikosDataSourceBean` 包装我们创建的`DruidXADataSource`,使得数据源能够被 JTA 事务管理器管理;
|
||||
+ 这里我们使用的sqlSessionTemplate是我们重写的`CustomSqlSessionTemplate`,原生的sqlSessionTemplate是不能实现在一个事务中实现数据源切换的。(为了不占用篇幅,我会在后文再给出详细的原因分析)
|
||||
+ 这里我们创建 druid 数据源的时候,创建的是 `DruidXADataSource`,它继承自 `DruidDataSource ` 并支持 XA 分布式事务;
|
||||
+ 使用 `AtomikosDataSourceBean` 包装我们创建的 `DruidXADataSource`,使得数据源能够被 JTA 事务管理器管理;
|
||||
+ 这里我们使用的 sqlSessionTemplate 是我们重写的 `CustomSqlSessionTemplate`,原生的 sqlSessionTemplate 是不能实现在一个事务中实现数据源切换的。(为了不占用篇幅,我会在后文再给出详细的原因分析)
|
||||
|
||||
```java
|
||||
/**
|
||||
@@ -207,7 +207,7 @@ public class DataSourceFactory {
|
||||
|
||||
|
||||
/***
|
||||
* 创建 DruidXADataSource 1 用@ConfigurationProperties自动配置属性
|
||||
* 创建 DruidXADataSource 1 用@ConfigurationProperties 自动配置属性
|
||||
*/
|
||||
@Bean
|
||||
@ConfigurationProperties("spring.datasource.druid.db1")
|
||||
@@ -225,7 +225,7 @@ public class DataSourceFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建支持XA事务的Atomikos数据源1
|
||||
* 创建支持 XA 事务的 Atomikos 数据源 1
|
||||
*/
|
||||
@Bean
|
||||
public DataSource dataSourceOne(DataSource druidDataSourceOne) {
|
||||
@@ -237,7 +237,7 @@ public class DataSourceFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建支持XA事务的Atomikos数据源2
|
||||
* 创建支持 XA 事务的 Atomikos 数据源 2
|
||||
*/
|
||||
@Bean
|
||||
public DataSource dataSourceTwo(DataSource druidDataSourceTwo) {
|
||||
@@ -249,8 +249,8 @@ public class DataSourceFactory {
|
||||
|
||||
|
||||
/**
|
||||
* @param dataSourceOne 数据源1
|
||||
* @return 数据源1的会话工厂
|
||||
* @param dataSourceOne 数据源 1
|
||||
* @return 数据源 1 的会话工厂
|
||||
*/
|
||||
@Bean
|
||||
public SqlSessionFactory sqlSessionFactoryOne(DataSource dataSourceOne)
|
||||
@@ -260,8 +260,8 @@ public class DataSourceFactory {
|
||||
|
||||
|
||||
/**
|
||||
* @param dataSourceTwo 数据源2
|
||||
* @return 数据源2的会话工厂
|
||||
* @param dataSourceTwo 数据源 2
|
||||
* @return 数据源 2 的会话工厂
|
||||
*/
|
||||
@Bean
|
||||
public SqlSessionFactory sqlSessionFactoryTwo(DataSource dataSourceTwo)
|
||||
@@ -271,10 +271,10 @@ public class DataSourceFactory {
|
||||
|
||||
|
||||
/***
|
||||
* sqlSessionTemplate与Spring事务管理一起使用,以确保使用的实际SqlSession是与当前Spring事务关联的,
|
||||
* 此外它还管理会话生命周期,包括根据Spring事务配置根据需要关闭,提交或回滚会话
|
||||
* @param sqlSessionFactoryOne 数据源1
|
||||
* @param sqlSessionFactoryTwo 数据源2
|
||||
* sqlSessionTemplate 与 Spring 事务管理一起使用,以确保使用的实际 SqlSession 是与当前 Spring 事务关联的,
|
||||
* 此外它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭,提交或回滚会话
|
||||
* @param sqlSessionFactoryOne 数据源 1
|
||||
* @param sqlSessionFactoryTwo 数据源 2
|
||||
*/
|
||||
@Bean
|
||||
public CustomSqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactoryOne,
|
||||
@@ -297,13 +297,13 @@ public class DataSourceFactory {
|
||||
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
|
||||
factoryBean.setDataSource(dataSource);
|
||||
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
|
||||
// 其他可配置项(包括是否打印sql,是否开启驼峰命名等)
|
||||
// 其他可配置项 (包括是否打印 sql,是否开启驼峰命名等)
|
||||
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
|
||||
configuration.setMapUnderscoreToCamelCase(true);
|
||||
configuration.setLogImpl(StdOutImpl.class);
|
||||
factoryBean.setConfiguration(configuration);
|
||||
/* *
|
||||
* 采用个如下方式配置属性的时候一定要保证已经进行数据源的配置(setDataSource)和数据源和MapperLocation配置(setMapperLocations)
|
||||
* 采用个如下方式配置属性的时候一定要保证已经进行数据源的配置 (setDataSource) 和数据源和 MapperLocation 配置 (setMapperLocations)
|
||||
* factoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
|
||||
* factoryBean.getObject().getConfiguration().setLogImpl(StdOutImpl.class);
|
||||
**/
|
||||
@@ -314,7 +314,7 @@ public class DataSourceFactory {
|
||||
|
||||
#### 3. 自定义sqlSessionTemplate的主要实现逻辑
|
||||
|
||||
这里主要覆盖重写了sqlSessionTemplate的getSqlSessionFactory,从ThreadLocal去获取实际使用的数据源(AOP切面将实际使用的数据源存入ThreadLocal)。
|
||||
这里主要覆盖重写了 sqlSessionTemplate 的 getSqlSessionFactory,从 ThreadLocal 去获取实际使用的数据源(AOP 切面将实际使用的数据源存入 ThreadLocal)。
|
||||
|
||||
```java
|
||||
/***
|
||||
@@ -340,12 +340,12 @@ public SqlSessionFactory getSqlSessionFactory() {
|
||||
|
||||
|
||||
/**
|
||||
* 这个方法的实现和父类的实现是基本一致的,唯一不同的就是在getSqlSession方法传参中获取会话工厂的方式
|
||||
* 这个方法的实现和父类的实现是基本一致的,唯一不同的就是在 getSqlSession 方法传参中获取会话工厂的方式
|
||||
*/
|
||||
|
||||
private class SqlSessionInterceptor implements InvocationHandler {
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
//在getSqlSession传参时候,用我们重写的getSqlSessionFactory获取当前数据源对应的会话工厂
|
||||
//在 getSqlSession 传参时候,用我们重写的 getSqlSessionFactory 获取当前数据源对应的会话工厂
|
||||
final SqlSession sqlSession = getSqlSession(
|
||||
CustomSqlSessionTemplate.this.getSqlSessionFactory(),
|
||||
CustomSqlSessionTemplate.this.executorType,
|
||||
@@ -375,7 +375,7 @@ private class SqlSessionInterceptor implements InvocationHandler {
|
||||
|
||||
#### 4. 使用AOP动态切换数据源,将当前使用的数据源名称保存到线程隔离的ThreadLocal中
|
||||
|
||||
这里我们直接对dao层接口进行切面,如果第一个参数指明需要使用哪一个数据源,就使用对应的数据源,如果没有指定,就使用默认的数据源。
|
||||
这里我们直接对 dao 层接口进行切面,如果第一个参数指明需要使用哪一个数据源,就使用对应的数据源,如果没有指定,就使用默认的数据源。
|
||||
|
||||
注:使用切面来切换数据源是一种实现思路,而具体如何定义切入点可以按照自己的实际情况来定,你可以使用第一个参数指明数据源,也可以自定义注解来指定数据源,这个按照自己的实际使用方便来实现即可。
|
||||
|
||||
@@ -436,7 +436,7 @@ public class DataSourceContextHolder {
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : JTA事务配置
|
||||
* @description : JTA 事务配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@@ -474,9 +474,9 @@ public class XATransactionManagerConfig {
|
||||
|
||||
### 3.1 测试数据库整合结果
|
||||
|
||||
这里我在mysql和mysql02的表中分别插入了一条数据:
|
||||
这里我在 mysql 和 mysql02 的表中分别插入了一条数据:
|
||||
|
||||
mysql数据库:
|
||||
mysql 数据库:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/mysql01.png"/> </div>
|
||||
|
||||
@@ -494,7 +494,7 @@ mysql02 数据库:
|
||||
|
||||
### 3.2 测试单数据库事务
|
||||
|
||||
这里因为没有复杂的业务逻辑,我直接将@Transactional加载controller层,实际中最好加到service层
|
||||
这里因为没有复杂的业务逻辑,我直接将@Transactional 加载 controller 层,实际中最好加到 service 层
|
||||
|
||||
```java
|
||||
/**
|
||||
@@ -567,15 +567,15 @@ public class XATransactionController {
|
||||
|
||||
### 3.4 测试druid数据源是否整合成功
|
||||
|
||||
访问 http://localhost:8080/druid/index.html ,可以在数据源监控页面看到两个数据源已配置成功,同时配置都与我们在yml配置文件中的一致。
|
||||
访问 http://localhost:8080/druid/index.html ,可以在数据源监控页面看到两个数据源已配置成功,同时配置都与我们在 yml 配置文件中的一致。
|
||||
|
||||
数据源1:
|
||||
数据源 1:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/druid-mysql01.png"/> </div>
|
||||
|
||||
|
||||
|
||||
数据源2:
|
||||
数据源 2:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/durud-mysql02.png"/> </div>
|
||||
|
||||
@@ -593,11 +593,11 @@ url 监控情况:
|
||||
|
||||
### 4.1 XA 与 JTA
|
||||
|
||||
XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。XA之所以需要引入事务管理器是因为,在分布式系统中,从理论上讲,两台机器理论上无 法达到一致的状态,需要引入一个单点进行协调。
|
||||
事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或 JMS队列)。
|
||||
XA 是由 X/Open 组织提出的分布式事务的规范。XA 规范主要定义了 (全局) 事务管理器 (Transaction Manager) 和 (局部) 资源管理器 (Resource Manager) 之间的接口。XA 接口是双向的系统接口,在事务管理器(Transaction Manager)以及一个或多个资源管理器(Resource Manager)之间形成通信桥梁。XA 之所以需要引入事务管理器是因为,在分布式系统中,从理论上讲,两台机器理论上无 法达到一致的状态,需要引入一个单点进行协调。
|
||||
事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或 JMS 队列)。
|
||||
下图说明了事务管理器、资源管理器,与应用程序之间的关系。
|
||||
|
||||
**而 JTA 就是 XA 规范在java语言上的实现。JTA 采用两阶段提交实现分布式事务。**
|
||||
**而 JTA 就是 XA 规范在 java 语言上的实现。JTA 采用两阶段提交实现分布式事务。**
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/XA.gif"/> </div>
|
||||
|
||||
@@ -605,16 +605,16 @@ XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(
|
||||
|
||||
### 4.2 两阶段提交
|
||||
|
||||
分布式事务必须满足传统事务的特性,即原子性,一致性,分离性和持久性。但是分布式事务处理过程中,某些节点(Server)可能发生故障,或 者由于网络发生故障而无法访问到某些节点。为了防止分布式系统部分失败时产生数据的不一致性。在分布式事务的控制中采用了两阶段提交协议(Two- Phase Commit Protocol)。即事务的提交分为两个阶段:
|
||||
分布式事务必须满足传统事务的特性,即原子性,一致性,分离性和持久性。但是分布式事务处理过程中,某些节点 (Server) 可能发生故障,或 者由于网络发生故障而无法访问到某些节点。为了防止分布式系统部分失败时产生数据的不一致性。在分布式事务的控制中采用了两阶段提交协议(Two- Phase Commit Protocol)。即事务的提交分为两个阶段:
|
||||
|
||||
+ 预提交阶段(Pre-Commit Phase)
|
||||
+ 预提交阶段 (Pre-Commit Phase)
|
||||
+ 决策后阶段(Post-Decision Phase)
|
||||
|
||||
两阶段提交用来协调参与一个更新中的多个服务器的活动,以防止分布式系统部分失败时产生数据的不一致性。例如,如果一个更新操作要求位于三个不同结点上的记录被改变,且其中只要有一个结点失败,另外两个结点必须检测到这个失败并取消它们所做的改变。为了支持两阶段提交,一个分布式更新事务中涉及到的服务器必须能够相互通信。一般来说一个服务器会被指定为"控制"或"提交"服务器并监控来自其它服务器的信息。
|
||||
|
||||
在分布式更新期间,各服务器首先标志它们已经完成(但未提交)指定给它们的分布式事务的那一部分,并准备提交(以使它们的更新部分成为永久性的)。这是 两阶段提交的第一阶段。如果有一结点不能响应,那么控制服务器要指示其它结点撤消分布式事务的各个部分的影响。如果所有结点都回答准备好提交,控制服务器 则指示它们提交并等待它们的响应。等待确认信息阶段是第二阶段。在接收到可以提交指示后,每个服务器提交分布式事务中属于自己的那一部分,并给控制服务器 发回提交完成信息。
|
||||
|
||||
在一个分布式事务中,必须有一个场地的Server作为协调者(coordinator),它能向 其它场地的Server发出请求,并对它们的回答作出响应,由它来控制一个分布式事务的提交或撤消。该分布式事务中涉及到的其它场地的Server称为参与者(Participant)。
|
||||
在一个分布式事务中,必须有一个场地的 Server 作为协调者 (coordinator),它能向 其它场地的 Server 发出请求,并对它们的回答作出响应,由它来控制一个分布式事务的提交或撤消。该分布式事务中涉及到的其它场地的 Server 称为参与者 (Participant)。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/commit.png"/> </div>
|
||||
|
||||
@@ -626,7 +626,7 @@ XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(
|
||||
|
||||
(1) 协调者准备局部(即在本地)提交并在日志中写入"预提交"日志项,并包含有该事务的所有参与者的名字。
|
||||
|
||||
(2) 协调者询问参与者能否提交该事务。一个参与者可能由于多种原因不能提交。例如,该Server提供的约束条(Constraints)的延迟检查不符合 限制条件时,不能提交;参与者本身的Server进程或硬件发生故障,不能提交;或者协调者访问不到某参与者(网络故障),这时协调者都认为是收到了一个 否定的回答。
|
||||
(2) 协调者询问参与者能否提交该事务。一个参与者可能由于多种原因不能提交。例如,该 Server 提供的约束条 (Constraints) 的延迟检查不符合 限制条件时,不能提交;参与者本身的 Server 进程或硬件发生故障,不能提交;或者协调者访问不到某参与者(网络故障),这时协调者都认为是收到了一个 否定的回答。
|
||||
|
||||
(3) 如果参与者能够提交,则在其本身的日志中写入"准备提交"日志项,该日志项立即写入硬盘,然后给协调者发回,已准备好提交"的回答。
|
||||
|
||||
@@ -652,19 +652,19 @@ XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(
|
||||
|
||||
### 5.1 事务下多数据源无法切换
|
||||
|
||||
这里是主要是对上文提到为什么不重写sqlSessionTemplate会导致在事务下数据源切换失败的补充,我们先看看sqlSessionTemplate源码中关于该类的定义:
|
||||
这里是主要是对上文提到为什么不重写 sqlSessionTemplate 会导致在事务下数据源切换失败的补充,我们先看看 sqlSessionTemplate 源码中关于该类的定义:
|
||||
|
||||
> sqlSessionTemplate与Spring事务管理一起使用,以确保使用的实际SqlSession是与当前Spring事务关联的,此外它还管理会话生命周期,包括根据Spring事务配置根据需要关闭,提交或回滚会话
|
||||
> sqlSessionTemplate 与 Spring 事务管理一起使用,以确保使用的实际 SqlSession 是与当前 Spring 事务关联的,此外它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭,提交或回滚会话
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/sqlSessionTemplate.png"/> </div>
|
||||
|
||||
这里最主要的是说明sqlSession是与当前是spring 事务是关联的。
|
||||
这里最主要的是说明 sqlSession 是与当前是 spring 事务是关联的。
|
||||
|
||||
#### 1. sqlSession与事务关联导致问题:
|
||||
|
||||
对于mybatis 来说,是默认开启一级缓存的,一级缓存是session级别的,对于同一个session如果是相同的查询语句并且查询参数都相同,第二次的查询就直接从一级缓存中获取。
|
||||
对于 mybatis 来说,是默认开启一级缓存的,一级缓存是 session 级别的,对于同一个 session 如果是相同的查询语句并且查询参数都相同,第二次的查询就直接从一级缓存中获取。
|
||||
|
||||
这也就是说,对于如下的情况,由于sqlSession是与事务绑定的,如果使用原生sqlSessionTemplate,则第一次查询和第二次查询都是用的同一个sqlSession,那么第二个查询数据库2的查询语句根本不会执行,会直接从一级缓存中获取查询结果。两次查询得到都是第一次查询的结果。
|
||||
这也就是说,对于如下的情况,由于 sqlSession 是与事务绑定的,如果使用原生 sqlSessionTemplate,则第一次查询和第二次查询都是用的同一个 sqlSession,那么第二个查询数据库 2 的查询语句根本不会执行,会直接从一级缓存中获取查询结果。两次查询得到都是第一次查询的结果。
|
||||
|
||||
```java
|
||||
@GetMapping("ts/db/programmers")
|
||||
@@ -682,7 +682,7 @@ public List<Programmer> getAllProgrammers() {
|
||||
|
||||
先说一下为什么会出现连接的复用:
|
||||
|
||||
我们可以在spring的源码中看到spring在通过`DataSourceUtils`类中去获取新的连接`doGetConnection`的时候,会通过`TransactionSynchronizationManager.getResource(dataSource)`方法去判断当前数据源是否有可用的连接,如果有就直接返回,如果没有就通过`fetchConnection`方法去获取。
|
||||
我们可以在 spring 的源码中看到 spring 在通过 `DataSourceUtils` 类中去获取新的连接 `doGetConnection` 的时候,会通过 `TransactionSynchronizationManager.getResource(dataSource)` 方法去判断当前数据源是否有可用的连接,如果有就直接返回,如果没有就通过 `fetchConnection` 方法去获取。
|
||||
|
||||
```java
|
||||
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
|
||||
@@ -734,37 +734,37 @@ public static Connection doGetConnection(DataSource dataSource) throws SQLExcept
|
||||
}
|
||||
```
|
||||
|
||||
这里主要的问题是`TransactionSynchronizationManager.getResource(dataSource)`中dataSource参数是在哪里进行注入的,这里可以沿着调用堆栈往上寻找,可以看到是在这个参数是`SpringManagedTransaction`类中获取连接的时候传入的。
|
||||
这里主要的问题是 `TransactionSynchronizationManager.getResource(dataSource)` 中 dataSource 参数是在哪里进行注入的,这里可以沿着调用堆栈往上寻找,可以看到是在这个参数是 `SpringManagedTransaction` 类中获取连接的时候传入的。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/opneConnection.png"/> </div>
|
||||
|
||||
而`SpringManagedTransaction`这类中的dataSource是如何得到赋值的,这里可以进入这个类中查看,只有在创建这个类的时候通过构造器为dataSource赋值,那么是哪个方法创建了`SpringManagedTransaction`?
|
||||
而 `SpringManagedTransaction` 这类中的 dataSource 是如何得到赋值的,这里可以进入这个类中查看,只有在创建这个类的时候通过构造器为 dataSource 赋值,那么是哪个方法创建了 `SpringManagedTransaction`?
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/springManagerTransaction.png"/> </div>
|
||||
|
||||
在构造器上打一个断点,沿着调用的堆栈往上寻找可以看到是`DefaultSqlSessionFactory`在创建`SpringManagedTransaction`中传入的,**这个数据源就是创建sqlSession的`sqlSessionFactory`中数据源**。
|
||||
在构造器上打一个断点,沿着调用的堆栈往上寻找可以看到是 `DefaultSqlSessionFactory` 在创建 `SpringManagedTransaction` 中传入的,**这个数据源就是创建 sqlSession 的 `sqlSessionFactory` 中数据源**。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/DefaultSqlSessionFactory.png"/> </div>
|
||||
|
||||
|
||||
|
||||
**这里说明连接的复用是与我们创建sqlSession时候传入的sqlSessionFactory是否是同一个有关**。
|
||||
**这里说明连接的复用是与我们创建 sqlSession 时候传入的 sqlSessionFactory 是否是同一个有关**。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/getsqlSession.png"/> </div>
|
||||
|
||||
所以我们才重写了sqlSessionTemplate中的`getSqlSession`方法,获取sqlSession时候传入正在使用的数据源对应的`sqlSessionFactory`,这样即便在同一个的事务中,由于传入的`sqlSessionFactory`中不同,就不会出现连接复用。
|
||||
所以我们才重写了 sqlSessionTemplate 中的 `getSqlSession` 方法,获取 sqlSession 时候传入正在使用的数据源对应的 `sqlSessionFactory`,这样即便在同一个的事务中,由于传入的 `sqlSessionFactory` 中不同,就不会出现连接复用。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/customSqlSessionTemplate.png"/> </div>
|
||||
|
||||
|
||||
|
||||
关于mybati-spring 的更多事务处理机制,推荐阅读博客[mybatis-spring事务处理机制分析](https://my.oschina.net/fifadxj/blog/785621)
|
||||
关于 mybati-spring 的更多事务处理机制,推荐阅读博客[mybatis-spring 事务处理机制分析](https://my.oschina.net/fifadxj/blog/785621)
|
||||
|
||||
|
||||
|
||||
### 5.2 出现org.apache.ibatis.binding.BindingExceptionInvalid bound statement (not found)异常
|
||||
|
||||
出现这个异常的的原因是在创建SqlSessionFactory的时候,在`setMapperLocations`配置好之前调用了`factoryBean.getObject()`方法
|
||||
出现这个异常的的原因是在创建 SqlSessionFactory 的时候,在 `setMapperLocations` 配置好之前调用了 `factoryBean.getObject()` 方法
|
||||
|
||||
```java
|
||||
//一个错误的示范
|
||||
@@ -779,7 +779,7 @@ private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws
|
||||
}
|
||||
```
|
||||
|
||||
上面这段代码没有任何编译问题,导致这个错误不容易发现,但是在调用sql时候就会出现异常。原因是`factoryBean.getObject()`方法被调用时就已经创建了SqlSessionFactory,并且SqlSessionFactory只会被创建一次。此时还没有指定sql 文件的位置,导致mybatis无法将接口与xml中的sql语句进行绑定,所以出现BindingExceptionInvalid 绑定异常。
|
||||
上面这段代码没有任何编译问题,导致这个错误不容易发现,但是在调用 sql 时候就会出现异常。原因是 `factoryBean.getObject()` 方法被调用时就已经创建了 SqlSessionFactory,并且 SqlSessionFactory 只会被创建一次。此时还没有指定 sql 文件的位置,导致 mybatis 无法将接口与 xml 中的 sql 语句进行绑定,所以出现 BindingExceptionInvalid 绑定异常。
|
||||
|
||||
```java
|
||||
@Override
|
||||
@@ -792,7 +792,7 @@ private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws
|
||||
}
|
||||
```
|
||||
|
||||
正常绑定的情况下,我们是可以在sqlSessionFactory中查看到绑定好的查询接口:
|
||||
正常绑定的情况下,我们是可以在 sqlSessionFactory 中查看到绑定好的查询接口:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/sqlSessionFactory.png"/> </div>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
## 一、config 简介
|
||||
|
||||
spring cloud config 分为服务端和客户端,服务端称为分布式配置中心,集中管理配置文件,客户端为各个业务单元,它们从配置中心获取相关配置,同时config 还实现了配置热更新,在服务不停机的情况下刷新配置。
|
||||
spring cloud config 分为服务端和客户端,服务端称为分布式配置中心,集中管理配置文件,客户端为各个业务单元,它们从配置中心获取相关配置,同时 config 还实现了配置热更新,在服务不停机的情况下刷新配置。
|
||||
|
||||
|
||||
|
||||
@@ -117,13 +117,13 @@ spring:
|
||||
password:
|
||||
# 指定拉取的配置文件的存放位置,配置文件最后存储的目录为 basedir + search-paths
|
||||
# 这个地方还需要注意的是,配置文件的仓库最好只放配置文件
|
||||
# 因为配置中心不仅会拉取search-paths下的文件,还会把uri指定仓库中的全部文件拉取到basedir下
|
||||
# 因为配置中心不仅会拉取 search-paths 下的文件,还会把 uri 指定仓库中的全部文件拉取到 basedir 下
|
||||
basedir: D:\git-config
|
||||
# 指定分支
|
||||
label: master
|
||||
```
|
||||
|
||||
这里的git 仓库就是本用例的仓库,是公开的仓库,所以不用配置用户名和密码,配置文件如下
|
||||
这里的 git 仓库就是本用例的仓库,是公开的仓库,所以不用配置用户名和密码,配置文件如下
|
||||
|
||||
- application.yml 为主配置;
|
||||
- application-dev.yml 为开发环境配置。
|
||||
@@ -136,19 +136,19 @@ spring:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-application-dev.png"/> </div>
|
||||
|
||||
这里需要注意的拉取配置的时候,我们此时指定拉取的是dev配置,application.yml实际 配置如下:
|
||||
这里需要注意的拉取配置的时候,我们此时指定拉取的是 dev 配置,application.yml 实际 配置如下:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-dev.png"/> </div>
|
||||
|
||||
这说明在用配置中心拉取配置的时候,和我们在本地开发的时候是一致的,配置是互补的,即dev中的实际配置应该是主配置和dev配置的结合,且遵循同名属性精确优先的原则。
|
||||
这说明在用配置中心拉取配置的时候,和我们在本地开发的时候是一致的,配置是互补的,即 dev 中的实际配置应该是主配置和 dev 配置的结合,且遵循同名属性精确优先的原则。
|
||||
|
||||
|
||||
|
||||
#### 3.5 http请求地址和资源文件映射
|
||||
|
||||
在本用例中如果我们想要直接访问主配置,用以下路径 http://localhost:8020/application.yml 是不行的,会得到错误页面。如果想要访问主配置,,可以用http://localhost:8020/application-X.yml,其中可以是任意字符,原因是:
|
||||
在本用例中如果我们想要直接访问主配置,用以下路径 http://localhost:8020/application.yml 是不行的,会得到错误页面。如果想要访问主配置,,可以用 http://localhost:8020/application-X.yml,其中可以是任意字符,原因是:
|
||||
|
||||
请求地址和实际的配置文件应该遵循以下规则,application为配置文件名,profile 为环境,label为分支(如果不指定默认就是master分支)。
|
||||
请求地址和实际的配置文件应该遵循以下规则,application 为配置文件名,profile 为环境,label 为分支(如果不指定默认就是 master 分支)。
|
||||
|
||||
- /{application}/{profile}[/{label}]
|
||||
- /{application}-{profile}.yml
|
||||
@@ -212,9 +212,9 @@ spring:
|
||||
|
||||
#### 4.2 新建 `bootstrap.yml`配置文件,指定注册中心地址和配置中心服务名,并在启动类上开启自动注册@EnableDiscoveryClient
|
||||
|
||||
这里需要特别说明的是,在之前的所有项目中我们采用的配置文件都是application.yml,但是这里**一定要采用bootstrap.yml**。
|
||||
这里需要特别说明的是,在之前的所有项目中我们采用的配置文件都是 application.yml,但是这里**一定要采用 bootstrap.yml**。
|
||||
|
||||
假设我们的数据库配置是放在远程配置中心的,那么我们应该先去远程配置中心拉取配置,然后再去进行数据库的自动化配置,反之如果我们先进行了数据库的自动化配置,那么就会因为找不到url或驱动而抛出异常。
|
||||
假设我们的数据库配置是放在远程配置中心的,那么我们应该先去远程配置中心拉取配置,然后再去进行数据库的自动化配置,反之如果我们先进行了数据库的自动化配置,那么就会因为找不到 url 或驱动而抛出异常。
|
||||
|
||||
- bootstrap.yml(bootstrap.properties)用来程序引导时执行,应用于更加早期配置信息读取,bootstrap.yml 先于 application.yml 加载。
|
||||
|
||||
@@ -298,7 +298,7 @@ public class ConfigController {
|
||||
|
||||
#### 4.4 依次启动eureka,config-server,config-client ,访问 http://localhost:8030/programmer
|
||||
|
||||
这里需要注意是在启动eureka和config-server,要稍等一会在启动config-client,这里是为了确保config-server已经将服务注册到eureka,然后我们的config-client才能从eureka中获取配置中心的服务。
|
||||
这里需要注意是在启动 eureka 和 config-server,要稍等一会在启动 config-client,这里是为了确保 config-server 已经将服务注册到 eureka,然后我们的 config-client 才能从 eureka 中获取配置中心的服务。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/config-client-programmer.png"/> </div>
|
||||
|
||||
@@ -317,7 +317,7 @@ Located environment: name=config-client, profiles=[dev], label=master, version=5
|
||||
|
||||
在微服务的架构中,我们通常想要构建一个共同的消息主题被所有微服务实例所监听,以便对所有微服务实例的管理和通知,这就是消息总线,spring cloud bus 就是消息总线的一种实现。
|
||||
|
||||
目前spring cloud bus 支持的消息中间件有 RabbitMQ和kafka, 我们下面的整合采用的是RrabbitMQ。
|
||||
目前 spring cloud bus 支持的消息中间件有 RabbitMQ 和 kafka, 我们下面的整合采用的是 RrabbitMQ。
|
||||
|
||||
关于热更新只需要对配置客户端(config-client)做更改,不需要对(config-server)做改动。
|
||||
|
||||
@@ -356,10 +356,10 @@ spring:
|
||||
bus:
|
||||
#开启总线
|
||||
enabled: true
|
||||
# 打开ack跟踪的标志(默认关闭)
|
||||
# 打开 ack 跟踪的标志(默认关闭)
|
||||
trace:
|
||||
enabled: true
|
||||
# 使用bus实现热更新
|
||||
# 使用 bus 实现热更新
|
||||
rabbitmq:
|
||||
host: 127.0.0.1
|
||||
port: 5672
|
||||
@@ -408,7 +408,7 @@ public class Programmer{
|
||||
|
||||
#### 5.4 依次启动eureka,config-server, config-client 服务
|
||||
|
||||
在client服务端启动时候,可以在控制台 看到bus 自动创建了交换机、队列等
|
||||
在 client 服务端启动时候,可以在控制台 看到 bus 自动创建了交换机、队列等
|
||||
|
||||
```
|
||||
Created new connection: rabbitConnectionFactory#496c6d94:22/SimpleConnection@185d85d2 [delegate=amqp://guest@127.0.0.1:5672/, localPort= 63713]
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
## 一、项目结构
|
||||
|
||||
eureka-server为服务注册中心,负责服务的管理;
|
||||
eureka-server 为服务注册中心,负责服务的管理;
|
||||
|
||||
eureka-client 为eureka客户端;
|
||||
eureka-client 为 eureka 客户端;
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-eureka-cluster.png"/> </div>
|
||||
|
||||
@@ -103,7 +103,7 @@ eureka:
|
||||
defaultZone: http://127.0.0.1:8010/eureka/,http://localhost:8020/eureka/
|
||||
```
|
||||
|
||||
需要注意的是Eureka互相注册要求各个Eureka实例的eureka.instance.hostname不同,如果相同,则会被Eureka标记为unavailable-replicas(不可用副本)。
|
||||
需要注意的是 Eureka 互相注册要求各个 Eureka 实例的 eureka.instance.hostname 不同,如果相同,则会被 Eureka 标记为 unavailable-replicas(不可用副本)。
|
||||
|
||||
#### 2.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置
|
||||
|
||||
@@ -170,22 +170,22 @@ public class EurekaClientApplication {
|
||||
|
||||
### 4.2 高可用集群搭建成功的判定
|
||||
|
||||
这里需要主要的是仅仅status中出现其他注册中心时,并不一定是搭建成功的,**一定是当注册中心的DS Replicas 和 available replicas中显示其余的注册中心时候**,才代表搭建成功。
|
||||
这里需要主要的是仅仅 status 中出现其他注册中心时,并不一定是搭建成功的,**一定是当注册中心的 DS Replicas 和 available replicas 中显示其余的注册中心时候**,才代表搭建成功。
|
||||
|
||||
#### 4.2.1 点击下面注册中心的可用实例列表中的地址,访问链接分以下几个情况:
|
||||
|
||||
1. hostname和prefer-ip-address都没有配置,则访问 主机名:服务名:端口号,
|
||||
1. hostname 和 prefer-ip-address 都没有配置,则访问 主机名:服务名:端口号,
|
||||
|
||||
```
|
||||
如:http://desktop-8jgsflj:8761/info
|
||||
```
|
||||
|
||||
2. 配置了hostname而没有配置prefer-ip-address,则访问 hostname:服务名:端口号,
|
||||
2. 配置了 hostname 而没有配置 prefer-ip-address,则访问 hostname:服务名:端口号,
|
||||
|
||||
```
|
||||
如:http://server:8761/info
|
||||
```
|
||||
3. 如果配置了prefer-ip-address,则访问 ipAddress:服务名:端口号,
|
||||
3. 如果配置了 prefer-ip-address,则访问 ipAddress:服务名:端口号,
|
||||
```
|
||||
如:http://192.168.200.228:8761/info
|
||||
```
|
||||
@@ -203,10 +203,10 @@ public class EurekaClientApplication {
|
||||
|
||||
### 4.3 prefer-ip-address 参数说明
|
||||
|
||||
在有的配置示例中,配置了prefer-ip-address为true。
|
||||
在有的配置示例中,配置了 prefer-ip-address 为 true。
|
||||
|
||||
```properties
|
||||
eureka.instance.prefer-ip-address=true
|
||||
```
|
||||
|
||||
在多机器独立部署的情况下是没有问题的,配置prefer-ip-address为ture,代表发现服务时候优先按照ip去搜寻,对于多集群而言,可以保证尽快准确搜索到服务。而对于单机部署来说,ip地址都是相同的,这会导致其余注册中心出现在unavailable-replicas(不可用副本)中。所以单机部署时候不建议开启这个参数(默认值为false),多机部署时候可以开启。
|
||||
在多机器独立部署的情况下是没有问题的,配置 prefer-ip-address 为 ture,代表发现服务时候优先按照 ip 去搜寻,对于多集群而言,可以保证尽快准确搜索到服务。而对于单机部署来说,ip 地址都是相同的,这会导致其余注册中心出现在 unavailable-replicas(不可用副本) 中。所以单机部署时候不建议开启这个参数(默认值为 false),多机部署时候可以开启。
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# eureka 服务的注册与发现
|
||||
|
||||
# eureka 服务的注册与发现
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一eureka-简介">一、eureka 简介</a><br/>
|
||||
<a href="#二项目结构">二、项目结构</a><br/>
|
||||
<a href="#三三步搭建eureka-服务注册中心">三、三步搭建eureka 服务注册中心</a><br/>
|
||||
@@ -15,113 +16,113 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、eureka 简介
|
||||
|
||||
Spring Cloud Eureka使用Netflix Eureka来实现服务注册与发现,它既包含了服务端组件,也包含了客户端组件。
|
||||
|
||||
**Eureka服务端**:服务的注册中心,负责维护注册的服务列表。
|
||||
|
||||
**Eureka客户端**: 在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务,并周期性地发送心跳来更新它的服务租约。同时它也能把从服务端查询到服务信息缓存到本地,并周期性地刷新服务状态。
|
||||
|
||||
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
eureka-server为服务注册中心,负责服务的管理;
|
||||
|
||||
eureka-client 为eureka客户端;
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-eureka.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 三、三步搭建eureka 服务注册中心
|
||||
|
||||
#### 3.1 引入eureka服务端依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 3.2 eureka 服务端配置
|
||||
|
||||
```yaml
|
||||
server:
|
||||
port: 8010
|
||||
eureka:
|
||||
instance:
|
||||
hostname: localhost
|
||||
client:
|
||||
# 设置为false,代表不向注册中心注册自己
|
||||
register-with-eureka: false
|
||||
# 注册中心主要用于维护服务,并不需要检索服务,所以设置为false
|
||||
fetch-registry: false
|
||||
serviceUrl:
|
||||
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
|
||||
```
|
||||
|
||||
#### 3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@EnableEurekaServer
|
||||
public class EurekaServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EurekaServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、三步搭建eureka 客户端
|
||||
|
||||
#### 4.1 引入eureka客户端依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 4.2 eureka 客户端配置
|
||||
|
||||
```yaml
|
||||
server:
|
||||
port: 8020
|
||||
# 指定服务命名
|
||||
spring:
|
||||
application:
|
||||
name: eureka-client
|
||||
# 指定注册中心地址
|
||||
eureka:
|
||||
client:
|
||||
serviceUrl:
|
||||
defaultZone: http://localhost:8010/eureka/
|
||||
```
|
||||
|
||||
#### 4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
public class EurekaClientApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EurekaClientApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 五、启动项目
|
||||
|
||||
#### 5.1 进入注册中心控制台,查看服务注册情况
|
||||
|
||||
## 一、eureka 简介
|
||||
|
||||
Spring Cloud Eureka 使用 Netflix Eureka 来实现服务注册与发现,它既包含了服务端组件,也包含了客户端组件。
|
||||
|
||||
**Eureka 服务端**:服务的注册中心,负责维护注册的服务列表。
|
||||
|
||||
**Eureka 客户端**: 在应用程序运行时,Eureka 客户端向注册中心注册自身提供的服务,并周期性地发送心跳来更新它的服务租约。同时它也能把从服务端查询到服务信息缓存到本地,并周期性地刷新服务状态。
|
||||
|
||||
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
eureka-server 为服务注册中心,负责服务的管理;
|
||||
|
||||
eureka-client 为 eureka 客户端;
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-eureka.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 三、三步搭建eureka 服务注册中心
|
||||
|
||||
#### 3.1 引入eureka服务端依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 3.2 eureka 服务端配置
|
||||
|
||||
```yaml
|
||||
server:
|
||||
port: 8010
|
||||
eureka:
|
||||
instance:
|
||||
hostname: localhost
|
||||
client:
|
||||
# 设置为 false,代表不向注册中心注册自己
|
||||
register-with-eureka: false
|
||||
# 注册中心主要用于维护服务,并不需要检索服务,所以设置为 false
|
||||
fetch-registry: false
|
||||
serviceUrl:
|
||||
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
|
||||
```
|
||||
|
||||
#### 3.3 启动类上增加注解@EnableEurekaServer激活eureka服务端自动配置
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@EnableEurekaServer
|
||||
public class EurekaServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EurekaServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、三步搭建eureka 客户端
|
||||
|
||||
#### 4.1 引入eureka客户端依赖
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 4.2 eureka 客户端配置
|
||||
|
||||
```yaml
|
||||
server:
|
||||
port: 8020
|
||||
# 指定服务命名
|
||||
spring:
|
||||
application:
|
||||
name: eureka-client
|
||||
# 指定注册中心地址
|
||||
eureka:
|
||||
client:
|
||||
serviceUrl:
|
||||
defaultZone: http://localhost:8010/eureka/
|
||||
```
|
||||
|
||||
#### 4.3 启动类上增加注解@EnableDiscoveryClient激活eureka客户端自动配置
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
public class EurekaClientApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(EurekaClientApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 五、启动项目
|
||||
|
||||
#### 5.1 进入注册中心控制台,查看服务注册情况
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/eureka.png"/> </div>
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
|
||||
## 一、feign 简介
|
||||
|
||||
在上一个用例中,我们使用ribbon+restTemplate 实现服务之间的远程调用,实际上每一个调用都是模板化的内容,所以spring cloud Feign 在此基础上进行了进一步的封装。我们只需要定义一个接口并使用feign注解的方式来进行配置,同时采用springMvc 注解进行参数绑定就可以完成服务的调用。feign同时还内置实现了负载均衡、服务容错等功能。
|
||||
在上一个用例中,我们使用 ribbon+restTemplate 实现服务之间的远程调用,实际上每一个调用都是模板化的内容,所以 spring cloud Feign 在此基础上进行了进一步的封装。我们只需要定义一个接口并使用 feign 注解的方式来进行配置,同时采用 springMvc 注解进行参数绑定就可以完成服务的调用。feign 同时还内置实现了负载均衡、服务容错等功能。
|
||||
|
||||
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
+ common: 公共的接口和实体类;
|
||||
+ consumer: 服务的消费者,采用feign调用产品服务;
|
||||
+ consumer: 服务的消费者,采用 feign 调用产品服务;
|
||||
+ producer:服务的提供者;
|
||||
+ eureka: 注册中心。
|
||||
|
||||
@@ -186,7 +186,7 @@ public interface ProductFeign {
|
||||
List<Product> productList();
|
||||
|
||||
/**
|
||||
* 这是需要强调的是使用feign时候@PathVariable一定要用value指明参数,
|
||||
* 这是需要强调的是使用 feign 时候@PathVariable 一定要用 value 指明参数,
|
||||
* 不然会抛出.IllegalStateException: PathVariable annotation was empty on param 异常
|
||||
*/
|
||||
@GetMapping("product/{id}")
|
||||
@@ -259,7 +259,7 @@ public class SellController {
|
||||
|
||||
#### 5.1 启动一个Eureka服务、三个producer服务(注意区分端口)、和一个消费者服务
|
||||
|
||||
feign 的依赖中导入了spring-cloud-starter-netflix-ribbon依赖,并且在内部实现了基于ribbon的客户端负载均衡,所以我们这里启动三个producer实例来观察负载均衡的情况。
|
||||
feign 的依赖中导入了 spring-cloud-starter-netflix-ribbon 依赖,并且在内部实现了基于 ribbon 的客户端负载均衡,所以我们这里启动三个 producer 实例来观察负载均衡的情况。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon-app.png"/> </div>
|
||||
|
||||
@@ -292,7 +292,7 @@ feign 的依赖中导入了spring-cloud-starter-netflix-ribbon依赖,并且在
|
||||
```yml
|
||||
feign:
|
||||
hystrix:
|
||||
# 如果为true,则OpenFign客户端将使用Hystrix断路器进行封装 默认为false
|
||||
# 如果为 true,则 OpenFign 客户端将使用 Hystrix 断路器进行封装 默认为 false
|
||||
enabled: true
|
||||
```
|
||||
|
||||
@@ -301,7 +301,7 @@ feign:
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 定义发生熔断时候的回退处理。除了继承自CProductFeign,还需要用@Component声明为spring的组件
|
||||
* @description : 定义发生熔断时候的回退处理。除了继承自 CProductFeign,还需要用@Component 声明为 spring 的组件
|
||||
*/
|
||||
@Component
|
||||
public class CProductFeignImpl implements CProductFeign {
|
||||
@@ -368,7 +368,7 @@ public interface CProductFeign extends ProductFeign {
|
||||
|
||||
#### 6.5 测试熔断处理
|
||||
|
||||
hystrix 默认调用超时时间为2s ,这里我们使用线程休眠的方式来模拟超时熔断。
|
||||
hystrix 默认调用超时时间为 2s ,这里我们使用线程休眠的方式来模拟超时熔断。
|
||||
|
||||
```java
|
||||
public List<Product> queryAllProducts() {
|
||||
@@ -385,4 +385,4 @@ public List<Product> queryAllProducts() {
|
||||
|
||||
测试结果:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-hystrix.png"/> </div>
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/feign-hystrix.png"/> </div>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
在分布式系统中,由于服务之间相互的依赖调用,如果一个服务单元发生了故障就有可能导致故障蔓延至整个系统,从而衍生出一系列的保护机制,断路器就是其中之一。
|
||||
|
||||
断路器可以在服务单元发生故障的时候,及时切断与服务单元的连接,避免资源被长时间占用。spring cloud hystrix组件实现了断路器、线程隔离等一系列基本功能,并具有服务降级、服务熔断、请求缓存、请求合并以及服务监控等配套功能。
|
||||
断路器可以在服务单元发生故障的时候,及时切断与服务单元的连接,避免资源被长时间占用。spring cloud hystrix 组件实现了断路器、线程隔离等一系列基本功能,并具有服务降级、服务熔断、请求缓存、请求合并以及服务监控等配套功能。
|
||||
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
[spring-cloud-ribbon](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-ribbon)用例已经实现通过ribbon+restTemplate实现服务间的调用,本用例在其基础上进行hystrix 的整合。
|
||||
[spring-cloud-ribbon](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-ribbon) 用例已经实现通过 ribbon+restTemplate 实现服务间的调用,本用例在其基础上进行 hystrix 的整合。
|
||||
|
||||
+ common: 公共的接口和实体类;
|
||||
+ consumer: 服务的消费者,采用RestTemplate调用产品服务;
|
||||
+ consumer: 服务的消费者,采用 RestTemplate 调用产品服务;
|
||||
+ producer:服务的提供者;
|
||||
+ eureka: 注册中心;
|
||||
+ turbine:多个熔断器的聚合监控。
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
#### 3.1 引入依赖
|
||||
|
||||
hystrix的仪表盘功能实际上是从[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator)获取数据,所以需要actuator starter开启端点的相关功能。
|
||||
hystrix 的仪表盘功能实际上是从[端点](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-boot/spring-boot-actuator) 获取数据,所以需要 actuator starter 开启端点的相关功能。
|
||||
|
||||
```xml
|
||||
<!--hystrix 依赖-->
|
||||
@@ -76,7 +76,7 @@ management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
# 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
|
||||
# 需要开启 hystrix.stream 端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
|
||||
include: "*"
|
||||
```
|
||||
|
||||
@@ -140,11 +140,11 @@ public List<Product> queryProductsFail() {
|
||||
|
||||
#### 3.5 模拟熔断
|
||||
|
||||
这里被调用方采用线程休眠的方式模拟服务超时,Hystrix默认超时时间为2s,调用远程服务时候超过这个时间,会触发熔断。
|
||||
这里被调用方采用线程休眠的方式模拟服务超时,Hystrix 默认超时时间为 2s,调用远程服务时候超过这个时间,会触发熔断。
|
||||
|
||||
```java
|
||||
public List<Product> queryAllProducts() {
|
||||
// hystrix 默认超时是2秒
|
||||
// hystrix 默认超时是 2 秒
|
||||
int i = new Random().nextInt(2500);
|
||||
try {
|
||||
Thread.sleep(i);
|
||||
@@ -155,15 +155,15 @@ public List<Product> queryAllProducts() {
|
||||
}
|
||||
```
|
||||
|
||||
3.5 启动服务,访问http://localhost:8030/sell/products ,多次刷新查看熔断情况
|
||||
3.5 启动服务,访问 http://localhost:8030/sell/products ,多次刷新查看熔断情况
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030.png"/> </div>
|
||||
|
||||
#### 3.5 启动服务,访问 localhost:8030/hystrix
|
||||
|
||||
依次输出http://localhost:8030/actuator/hystrix.stream(监控地址) ,2000(延迟时间),title可以任意填写,进入监控台。
|
||||
依次输出 http://localhost:8030/actuator/hystrix.stream(监控地址) ,2000(延迟时间),title 可以任意填写,进入监控台。
|
||||
|
||||
需要注意的是在spring cloud Finchley.SR2,监控地址中都是有/actuator的,因为在spring boot 2.x 的所有端点(包括自定义端点)都是暴露在这个路径下,在启动时候的控制台输出的日志可以查看到所有暴露端点的映射。
|
||||
需要注意的是在 spring cloud Finchley.SR2,监控地址中都是有/actuator 的,因为在 spring boot 2.x 的所有端点(包括自定义端点)都是暴露在这个路径下,在启动时候的控制台输出的日志可以查看到所有暴露端点的映射。
|
||||
|
||||
**登录页面**:
|
||||
|
||||
@@ -173,7 +173,7 @@ public List<Product> queryAllProducts() {
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-8030-login.png"/> </div>
|
||||
|
||||
**关于各个参数的说明参见[官方wiki](https://github.com/Netflix-Skunkworks/hystrix-dashboard/wiki)提供的图**:
|
||||
**关于各个参数的说明参见[官方 wiki](https://github.com/Netflix-Skunkworks/hystrix-dashboard/wiki) 提供的图**:
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dashboard.png"/> </div>
|
||||
|
||||
@@ -290,7 +290,7 @@ public class TurbineApplication {
|
||||
|
||||
#### 4.4 依次启动eureka、producer、consumer、turbine四个项目
|
||||
|
||||
在 localhost:8030/hystrix或者localhost:8030/hystrix(consumer和producer都集成了hystrix) 页面输入http://localhost:8040/turbine.stream,查看断路器聚合信息
|
||||
在 localhost:8030/hystrix 或者 localhost:8030/hystrix(consumer 和 producer 都集成了 hystrix) 页面输入 http://localhost:8040/turbine.stream,查看断路器聚合信息
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/hystrix-cluster-login.png"/> </div>
|
||||
|
||||
@@ -302,7 +302,7 @@ public class TurbineApplication {
|
||||
|
||||
#### 5.1 无法访问监控页面
|
||||
|
||||
1. 一般是端点链接输入不对,在F版本的spring cloud 中,输入监控的端点链接是 http://localhost:8030/actuator/hystrix.stream ,中间是有/actuator/(之前版本的没有/actuator/)
|
||||
1. 一般是端点链接输入不对,在 F 版本的 spring cloud 中,输入监控的端点链接是 http://localhost:8030/actuator/hystrix.stream ,中间是有/actuator/(之前版本的没有/actuator/)
|
||||
|
||||
2. 没有暴露端点链接,暴露端点链接有两种方式,一种是我们在上文中提到的基于配置的方式
|
||||
|
||||
@@ -311,7 +311,7 @@ public class TurbineApplication {
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
# 需要开启hystrix.stream端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
|
||||
# 需要开启 hystrix.stream 端点的暴露 这样才能获取到监控信息 * 代表开启所有可监控端点
|
||||
include: "*"
|
||||
```
|
||||
|
||||
@@ -329,7 +329,7 @@ public class TurbineApplication {
|
||||
}
|
||||
```
|
||||
|
||||
这两种方式二选一即可,就算是采用代码的方式,还是建议将地址设置为/actuator/hystrix.stream,而不是原来的hystrix.stream,因为turbine默认也是从/actuator/hystrix.stream去获取信息。
|
||||
这两种方式二选一即可,就算是采用代码的方式,还是建议将地址设置为/actuator/hystrix.stream,而不是原来的 hystrix.stream,因为 turbine 默认也是从/actuator/hystrix.stream 去获取信息。
|
||||
|
||||
#### 5.2 页面一直loading 或者访问端点页面一直出现ping
|
||||
|
||||
|
||||
@@ -18,16 +18,16 @@
|
||||
|
||||
## 一、ribbon 简介
|
||||
|
||||
ribbon是Netfix公司开源的负载均衡组件,采用服务端负载均衡的方式,即消费者客户端维护可用的服务列表,并通过负载均衡的方式将请求按照指定的策略分摊给消费者,从而达到负载均衡的方式。
|
||||
ribbon 是 Netfix 公司开源的负载均衡组件,采用服务端负载均衡的方式,即消费者客户端维护可用的服务列表,并通过负载均衡的方式将请求按照指定的策略分摊给消费者,从而达到负载均衡的方式。
|
||||
|
||||
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
+ common: 公共的接口和实体类;
|
||||
+ consumer: 服务的消费者,采用RestTemplate调用产品服务;
|
||||
+ consumer: 服务的消费者,采用 RestTemplate 调用产品服务;
|
||||
+ producer:服务的提供者;
|
||||
+ eureka: 注册中心,ribbon 从注册中心获取可用的服务列表,是实现负载均衡的基础。这里使用我们在[服务的注册与发现](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-eureka)这个用例中搭建的简单注册中心作为测试即可。
|
||||
+ eureka: 注册中心,ribbon 从注册中心获取可用的服务列表,是实现负载均衡的基础。这里使用我们在[服务的注册与发现](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-eureka) 这个用例中搭建的简单注册中心作为测试即可。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-ribbon.png"/> </div>
|
||||
|
||||
@@ -200,7 +200,7 @@ public class RibbonConfig {
|
||||
|
||||
#### 4.4 使用RestTemplate调用远程服务
|
||||
|
||||
这里我们在调用远程服务的时候,url填写的是服务名+具体接口地址 ,由于我们的同一个服务会存在多个实例,在使用@LoadBalanced配置RestTemplate调用服务时,客户端就会从按照指定的负载均衡的方式将请求分摊到多个实例上。(默认的负载均衡采用的是RoundRobinRule(轮询)的策略,有特殊需求时候可以采用其他内置的策略规则,或者实现IRule来定义自己的负载均衡策略)。
|
||||
这里我们在调用远程服务的时候,url 填写的是服务名 + 具体接口地址 ,由于我们的同一个服务会存在多个实例,在使用@LoadBalanced 配置 RestTemplate 调用服务时,客户端就会从按照指定的负载均衡的方式将请求分摊到多个实例上。(默认的负载均衡采用的是 RoundRobinRule(轮询)的策略,有特殊需求时候可以采用其他内置的策略规则,或者实现 IRule 来定义自己的负载均衡策略)。
|
||||
|
||||
```java
|
||||
@Service
|
||||
@@ -250,18 +250,18 @@ public class ProductService implements IProductService {
|
||||
|
||||
#### 6.1 restTemplate 规范
|
||||
|
||||
restTemplate 调用对应resultful接口时候,使用的方法应该与接口声明方式(@GetMapping、@PostMapping、@PutMapping、@DeleteMapping)保持一致。请求类型与对应的调用方法如下。
|
||||
restTemplate 调用对应 resultful 接口时候,使用的方法应该与接口声明方式(@GetMapping、@PostMapping、@PutMapping、@DeleteMapping)保持一致。请求类型与对应的调用方法如下。
|
||||
|
||||
- GET请求(getForObject 、getForEntity)
|
||||
- POST请求(postForObject 、postForEntity)
|
||||
- PUT请求(put)
|
||||
- DELETE请求 (delete)
|
||||
- GET 请求 (getForObject 、getForEntity)
|
||||
- POST 请求(postForObject 、postForEntity)
|
||||
- PUT 请求(put)
|
||||
- DELETE 请求 (delete)
|
||||
|
||||
#### 6.2 ForEntity()和ForObject()的区别
|
||||
|
||||
- `ForEntity()`发送一个请求,返回的ResponseEntity包含了响应体所映射成的对象
|
||||
- `ForEntity()` 发送一个请求,返回的 ResponseEntity 包含了响应体所映射成的对象
|
||||
|
||||
- `ForObject()`发送一个请求,返回的请求体将映射为一个对象
|
||||
- `ForObject()` 发送一个请求,返回的请求体将映射为一个对象
|
||||
|
||||
例如:
|
||||
|
||||
@@ -274,19 +274,19 @@ Product product = restTemplate.getForObject("http://producer/product/{1}", Produ
|
||||
|
||||
## 七、 附2: 关于ribbon更多负载均衡的策略
|
||||
|
||||
Ribbon内置了多种负载均衡策略,如果有更复杂的需求,可以自己实现IRule。
|
||||
Ribbon 内置了多种负载均衡策略,如果有更复杂的需求,可以自己实现 IRule。
|
||||
|
||||
#### 7.1 内置的负载均衡的策略如下图
|
||||
|
||||

|
||||

|
||||
|
||||
图片来源于博客:[Ribbon负载均衡策略与自定义配置](https://blog.csdn.net/jrn1012/article/details/77837680)
|
||||
图片来源于博客:[Ribbon 负载均衡策略与自定义配置](https://blog.csdn.net/jrn1012/article/details/77837680)
|
||||
|
||||
|
||||
|
||||
#### 7.2 配置文件指定负载均衡的方式
|
||||
|
||||
要设置`IRule`名为的服务名称`users`,您可以设置以下属性:
|
||||
要设置 `IRule` 名为的服务名称 `users`,您可以设置以下属性:
|
||||
|
||||
```yaml
|
||||
users:
|
||||
@@ -316,10 +316,10 @@ public class CustomConfiguration {
|
||||
}
|
||||
```
|
||||
|
||||
在使用代码方式的时候需要注意 [官方文档](http://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html#_customizing_the_ribbon_client)中关于注解方式有以下强调:
|
||||
在使用代码方式的时候需要注意 [官方文档](http://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html#_customizing_the_ribbon_client) 中关于注解方式有以下强调:
|
||||
|
||||
|
||||
The CustomConfiguration clas must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).
|
||||
|
||||
|
||||
该`CustomConfiguration`类必须是`@Configuration`标注的,但需要注意的它不是在`@ComponentScan`主应用程序上下文。否则,它将由所有`@RibbonClients`共享。如果你使用`@ComponentScan`(或`@SpringBootApplication`),你需要采取一些措施来避免它被扫描到(例如,你可以把它放在一个独立的,非重叠的包,或用`@ComponentScan`时显示扫描指定的包)。
|
||||
该 `CustomConfiguration` 类必须是 `@Configuration` 标注的,但需要注意的它不是在 `@ComponentScan` 主应用程序上下文。否则,它将由所有 `@RibbonClients` 共享。如果你使用 `@ComponentScan`(或 `@SpringBootApplication`),你需要采取一些措施来避免它被扫描到(例如,你可以把它放在一个独立的,非重叠的包,或用 `@ComponentScan` 时显示扫描指定的包)。
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring-sleuth-zipkin
|
||||
|
||||
# spring-sleuth-zipkin
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一简介">一、简介</a><br/>
|
||||
<a href="#二项目结构">二、项目结构</a><br/>
|
||||
<a href="#三构建-zipkin-服务端">三、构建 zipkin 服务端</a><br/>
|
||||
@@ -10,94 +11,94 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、简介
|
||||
|
||||
在微服务架构中,几乎每一个前端的请求都会经过多个服务单元协调来提供服务,形成复杂的服务调用链路。当服务发生问题时候,很难知道问题来源于链路的哪一个环节,这时候就需要进行链路追踪。
|
||||
|
||||
zipkin 是一个开源的分布式跟踪系统,可以使用spring cloud sleuth 来轻松的集成 zipkin。
|
||||
|
||||
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
这里的项目是在之前的 [spring-cloud-zuul](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-zuul) 进行集成,zuul 项目的产品接口调用链路从 网关 -> consumer -> producer,历经三个环节的调用链路可以直观展示zipkin对链路追踪可视化的好处。
|
||||
|
||||
+ common: 公共的接口和实体类;
|
||||
+ consumer: 服务的消费者,采用feign调用产品服务;
|
||||
+ producer:服务的提供者;
|
||||
+ eureka: 注册中心;
|
||||
+ zuul: api网关。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-sleuth-zipkin.png"/> </div>
|
||||
|
||||
## 三、构建 zipkin 服务端
|
||||
|
||||
zipkin 客户端可以不用自己构建,直接从[官网](https://zipkin.io/pages/quickstart)上下载对应的jar 包启动即可,默认端口 9411
|
||||
|
||||
```shell
|
||||
java -jar zipkin.jar
|
||||
```
|
||||
|
||||
可以直接从docker仓库拉取,然后启动容器:
|
||||
|
||||
```shell
|
||||
docker run -d -p 9411:9411 openzipkin/zipkin
|
||||
```
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-download.png"/> </div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 四、集成zipkin
|
||||
|
||||
这里我们对zuul、consumer、producer 三个模块都进行集成
|
||||
|
||||
#### 4.1 对三个模块(zuul、consumer、producer )添加依赖
|
||||
|
||||
```xml
|
||||
<!--zipkin-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-zipkin</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 4.2 分别在三个模块的application.yml 配置文件中指定zipkin的地址
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
zipkin:
|
||||
base-url: http://localhost:9411/
|
||||
# 可以指定监控数据的采样率
|
||||
sleuth:
|
||||
sampler:
|
||||
probability: 1
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 五、启动项目
|
||||
|
||||
分别启动,eureka,zuul,consumer,producer,zuul ,访问 http://localhost:9411/ ,查看我们的服务调用链路
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin.png"/> </div>
|
||||
|
||||
点击链路,则可以查看具体的调用情况
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-detail.png"/> </div>
|
||||
|
||||
展示信息说明:
|
||||
|
||||
Span : 基本工作单元,发送一个远程调度任务就会产生一个 Span。
|
||||
|
||||
Trace:由一系列 Span 组成的,呈树状结构。 所有由这个请求产生的 Span 组成了这个 Trace 。
|
||||
|
||||
SpanId ; 工作单元 (Span) 的唯一标识。
|
||||
|
||||
TraceId : 一条请求链路 (Trace) 的唯 一 标识。
|
||||
|
||||
除了TraceID外,还需要SpanID用于记录调用父子关系。每个服务会记录下parent id和span id,通过他们可以组织一次完整调用链的父子关系。
|
||||
|
||||
## 一、简介
|
||||
|
||||
在微服务架构中,几乎每一个前端的请求都会经过多个服务单元协调来提供服务,形成复杂的服务调用链路。当服务发生问题时候,很难知道问题来源于链路的哪一个环节,这时候就需要进行链路追踪。
|
||||
|
||||
zipkin 是一个开源的分布式跟踪系统,可以使用 spring cloud sleuth 来轻松的集成 zipkin。
|
||||
|
||||
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
这里的项目是在之前的 [spring-cloud-zuul](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-zuul) 进行集成,zuul 项目的产品接口调用链路从 网关 -> consumer -> producer,历经三个环节的调用链路可以直观展示 zipkin 对链路追踪可视化的好处。
|
||||
|
||||
+ common: 公共的接口和实体类;
|
||||
+ consumer: 服务的消费者,采用 feign 调用产品服务;
|
||||
+ producer:服务的提供者;
|
||||
+ eureka: 注册中心;
|
||||
+ zuul: api 网关。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-cloud-sleuth-zipkin.png"/> </div>
|
||||
|
||||
## 三、构建 zipkin 服务端
|
||||
|
||||
zipkin 客户端可以不用自己构建,直接从[官网](https://zipkin.io/pages/quickstart) 上下载对应的 jar 包启动即可,默认端口 9411
|
||||
|
||||
```shell
|
||||
java -jar zipkin.jar
|
||||
```
|
||||
|
||||
可以直接从 docker 仓库拉取,然后启动容器:
|
||||
|
||||
```shell
|
||||
docker run -d -p 9411:9411 openzipkin/zipkin
|
||||
```
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-download.png"/> </div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 四、集成zipkin
|
||||
|
||||
这里我们对 zuul、consumer、producer 三个模块都进行集成
|
||||
|
||||
#### 4.1 对三个模块(zuul、consumer、producer )添加依赖
|
||||
|
||||
```xml
|
||||
<!--zipkin-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-zipkin</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### 4.2 分别在三个模块的application.yml 配置文件中指定zipkin的地址
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
zipkin:
|
||||
base-url: http://localhost:9411/
|
||||
# 可以指定监控数据的采样率
|
||||
sleuth:
|
||||
sampler:
|
||||
probability: 1
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 五、启动项目
|
||||
|
||||
分别启动,eureka,zuul,consumer,producer,zuul ,访问 http://localhost:9411/ ,查看我们的服务调用链路
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin.png"/> </div>
|
||||
|
||||
点击链路,则可以查看具体的调用情况
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zipkin-detail.png"/> </div>
|
||||
|
||||
展示信息说明:
|
||||
|
||||
Span : 基本工作单元,发送一个远程调度任务就会产生一个 Span。
|
||||
|
||||
Trace:由一系列 Span 组成的,呈树状结构。 所有由这个请求产生的 Span 组成了这个 Trace 。
|
||||
|
||||
SpanId ; 工作单元 (Span) 的唯一标识。
|
||||
|
||||
TraceId : 一条请求链路 (Trace) 的唯 一 标识。
|
||||
|
||||
除了 TraceID 外,还需要 SpanID 用于记录调用父子关系。每个服务会记录下 parent id 和 span id,通过他们可以组织一次完整调用链的父子关系。
|
||||
|
||||
注:关于以上概念可以比对链表的实现原理来理解。
|
||||
|
||||
@@ -19,25 +19,25 @@
|
||||
|
||||
api 网关是整个微服务系统的门面,所有的外部访问需要通过网关进行调度和过滤。它实现了请求转发、负载均衡、校验过滤、错误熔断、服务聚合等功能。
|
||||
|
||||
下图是直观的显示api Gateway 在微服务网关中的作用(图片引用自spring boot 官网)。
|
||||
下图是直观的显示 api Gateway 在微服务网关中的作用(图片引用自 spring boot 官网)。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/apiGateway.png"/> </div>
|
||||
|
||||
### 1.2 zuul
|
||||
|
||||
spring cloud 中提供了基础Net flix Zuul 实现的网关组件,这就是Zuul,它除了实现负载均衡、错误熔断、路由转发等功能,还能与spring 其他组件无缝配合使用。
|
||||
spring cloud 中提供了基础 Net flix Zuul 实现的网关组件,这就是 Zuul,它除了实现负载均衡、错误熔断、路由转发等功能,还能与 spring 其他组件无缝配合使用。
|
||||
|
||||
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
[spring-cloud-feign](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-feign)用例已经实现通过feign实现服务间的调用,且提供了两个业务服务单元(consumer、producer),可以方便直观的测试zuul的路由、负载均衡、和错误熔断等功能,所以本用例在其基础上进行zuul的整合。
|
||||
[spring-cloud-feign](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring-cloud/spring-cloud-feign) 用例已经实现通过 feign 实现服务间的调用,且提供了两个业务服务单元 (consumer、producer),可以方便直观的测试 zuul 的路由、负载均衡、和错误熔断等功能,所以本用例在其基础上进行 zuul 的整合。
|
||||
|
||||
+ common: 公共的接口和实体类;
|
||||
+ consumer: 服务的消费者,采用feign调用产品服务;
|
||||
+ consumer: 服务的消费者,采用 feign 调用产品服务;
|
||||
+ producer:服务的提供者;
|
||||
+ eureka: 注册中心;
|
||||
+ zuul: api网关。
|
||||
+ zuul: api 网关。
|
||||
|
||||
聚合项目目录如下:
|
||||
|
||||
@@ -131,7 +131,7 @@ zuul 项目目录如下:
|
||||
|
||||
#### 3.2 在启动类上添加注解@EnableZuulProxy和@EnableDiscoveryClient
|
||||
|
||||
@EnableZuulProxy会自动设置Zuul服务器端点并在其中开启一些反向代理过滤器,以便将请求转发到后端服务器。
|
||||
@EnableZuulProxy 会自动设置 Zuul 服务器端点并在其中开启一些反向代理过滤器,以便将请求转发到后端服务器。
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
@@ -150,7 +150,7 @@ public class ZuulApplication {
|
||||
|
||||
#### 3.3 指定注册中心、配置网关的路由规则
|
||||
|
||||
zuul 需要指定注册中心的地址,zuul 会从eureka获取其他微服务的实例信息,然后按照指定的路由规则进行请求转发。
|
||||
zuul 需要指定注册中心的地址,zuul 会从 eureka 获取其他微服务的实例信息,然后按照指定的路由规则进行请求转发。
|
||||
|
||||
```yaml
|
||||
server:
|
||||
@@ -212,7 +212,7 @@ public class CustomZuulFallbackProvider implements FallbackProvider {
|
||||
return new ClientHttpResponse() {
|
||||
|
||||
/**
|
||||
* 返回响应的HTTP状态代码
|
||||
* 返回响应的 HTTP 状态代码
|
||||
*/
|
||||
@Override
|
||||
public HttpStatus getStatusCode() throws IOException {
|
||||
@@ -220,7 +220,7 @@ public class CustomZuulFallbackProvider implements FallbackProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回HTTP状态代码
|
||||
* 返回 HTTP 状态代码
|
||||
*/
|
||||
@Override
|
||||
public int getRawStatusCode() throws IOException {
|
||||
@@ -228,7 +228,7 @@ public class CustomZuulFallbackProvider implements FallbackProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回响应的HTTP状态文本
|
||||
* 返回响应的 HTTP 状态文本
|
||||
*/
|
||||
@Override
|
||||
public String getStatusText() throws IOException {
|
||||
@@ -262,7 +262,7 @@ public class CustomZuulFallbackProvider implements FallbackProvider {
|
||||
}
|
||||
```
|
||||
|
||||
正确返回了内容、同时返回的http状态码也和我们设置的一样。
|
||||
正确返回了内容、同时返回的 http 状态码也和我们设置的一样。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/zuul-broker.png"/> </div>
|
||||
|
||||
@@ -270,12 +270,12 @@ public class CustomZuulFallbackProvider implements FallbackProvider {
|
||||
|
||||
## 五、zuul 过滤器
|
||||
|
||||
创建自定义过滤器继承自CustomZuulFilter,当我们访问网关的时候,如果判断session 中没有对应的 code,则跳转到我们自定义的登录页面。
|
||||
创建自定义过滤器继承自 CustomZuulFilter,当我们访问网关的时候,如果判断 session 中没有对应的 code,则跳转到我们自定义的登录页面。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义filter过滤器
|
||||
* @description : 自定义 filter 过滤器
|
||||
*/
|
||||
|
||||
@Component
|
||||
@@ -306,7 +306,7 @@ public class CustomZuulFilter extends ZuulFilter {
|
||||
}
|
||||
|
||||
/**
|
||||
* ZuulFilter的核心校验方法
|
||||
* ZuulFilter 的核心校验方法
|
||||
*/
|
||||
@Override
|
||||
public Object run() throws ZuulException {
|
||||
@@ -314,7 +314,7 @@ public class CustomZuulFilter extends ZuulFilter {
|
||||
HttpServletRequest request = currentContext.getRequest();
|
||||
String code = (String)request.getSession().getAttribute("code");
|
||||
if (StringUtils.isEmpty(code)){
|
||||
// 设置值为false 不将请求转发到对应的服务上
|
||||
// 设置值为 false 不将请求转发到对应的服务上
|
||||
currentContext.setSendZuulResponse(false);
|
||||
// 设置返回的状态码
|
||||
currentContext.setResponseStatusCode(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
|
||||
@@ -372,7 +372,7 @@ index.ftl:
|
||||
|
||||
## 七、附:关于版本问题可能导致的 zuul 启动失败
|
||||
|
||||
如果出现以下错误导致启动失败,是 spring boot 版本不兼容导致的错误,Finchley SR2版本 spring cloud 中的 zuul 和 spring boot 2.1.x 版本存在不兼容。如果出现这个问题,则将 spring boot 将至 2.0.x 的版本即可,用例中采用的是 2.0.8 版本。在实际的开发中应该严格遵循spring 官方的版本依赖说明。
|
||||
如果出现以下错误导致启动失败,是 spring boot 版本不兼容导致的错误,Finchley SR2 版本 spring cloud 中的 zuul 和 spring boot 2.1.x 版本存在不兼容。如果出现这个问题,则将 spring boot 将至 2.0.x 的版本即可,用例中采用的是 2.0.8 版本。在实际的开发中应该严格遵循 spring 官方的版本依赖说明。
|
||||
|
||||
```java
|
||||
APPLICATION FAILED TO START
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring AOP(注解方式)
|
||||
|
||||
# spring AOP(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -13,237 +14,237 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 切面配置位于com.heibaiying.config下AopConfig.java文件;
|
||||
2. 自定义切面位于advice下,其中CustomAdvice是标准的自定义切面,FirstAdvice和SecondAdvice用于测试多切面共同作用于同一个被切入点时的执行顺序;
|
||||
3. OrderService是待切入方法。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-aop-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入aop依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
注:@Pointcut的值可以是多个切面表达式的组合。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
@Aspect
|
||||
@Component //除了加上@Aspect外 还需要声明为spring的组件 @Aspect只是一个切面声明
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
/**
|
||||
* 使用 || , or 表示或
|
||||
* 使用 && , and 表示与
|
||||
* ! 表示非
|
||||
*/
|
||||
@Pointcut("execution(* com.heibaiying.service.OrderService.*(..)) && !execution(* com.heibaiying.service.OrderService.deleteOrder(..))")
|
||||
private void pointCut() {
|
||||
|
||||
}
|
||||
|
||||
@Before("pointCut()")
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
// returning 参数用于指定返回结果与哪一个参数绑定
|
||||
@AfterReturning(pointcut = "pointCut()", returning = "result")
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
@Around("pointCut()")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
// throwing 参数用于指定抛出的异常与哪一个参数绑定
|
||||
@AfterThrowing(pointcut = "pointCut()", throwing = "exception")
|
||||
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
|
||||
@After("pointCut()")
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 开启切面配置
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.*")
|
||||
@EnableAspectJAutoProxy // 开启@Aspect注解支持 等价于<aop:aspectj-autoproxy>
|
||||
public class AopConfig {
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = AopConfig.class)
|
||||
public class AopTest {
|
||||
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void saveAndQuery() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
|
||||
* 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行
|
||||
*/
|
||||
@Test
|
||||
public void delete() {
|
||||
orderService.deleteOrder(12793179L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 切面执行顺序
|
||||
|
||||
- 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
|
||||
|
||||
- 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行,类似于同心圆原理。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/aop执行顺序.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 附: 关于切面表达式的说明
|
||||
|
||||
切面表达式遵循以下格式:
|
||||
|
||||
```shell
|
||||
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
```
|
||||
|
||||
- 除了返回类型模式,名字模式和参数模式以外,所有的部分都是可选的;
|
||||
- `*`,它代表了匹配任意的返回类型;
|
||||
- `()` 匹配了一个不接受任何参数的方法, 而 `(..)` 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 `(*)` 匹配了一个接受一个任何类型的参数的方法。 模式 `(*,String)` 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型。
|
||||
|
||||
下面给出一些常见切入点表达式的例子。
|
||||
|
||||
- 任意公共方法的执行:
|
||||
|
||||
```java
|
||||
execution(public * *(..))
|
||||
```
|
||||
|
||||
- 任何一个以“set”开始的方法的执行:
|
||||
|
||||
```java
|
||||
execution(* set*(..))
|
||||
```
|
||||
|
||||
- `AccountService` 接口的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
```
|
||||
|
||||
- 定义在service包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
```
|
||||
|
||||
- 定义在service包或者子包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
```
|
||||
|
||||
- 在service包里的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```java
|
||||
within(com.xyz.service.*)
|
||||
```
|
||||
|
||||
- 在service包或者子包里的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```
|
||||
within(com.xyz.service..*)
|
||||
```
|
||||
|
||||
- 实现了 `AccountService` 接口的代理对象的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```
|
||||
this(com.xyz.service.AccountService)
|
||||
```
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 切面配置位于 com.heibaiying.config 下 AopConfig.java 文件;
|
||||
2. 自定义切面位于 advice 下,其中 CustomAdvice 是标准的自定义切面,FirstAdvice 和 SecondAdvice 用于测试多切面共同作用于同一个被切入点时的执行顺序;
|
||||
3. OrderService 是待切入方法。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-aop-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 aop 依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
注:@Pointcut 的值可以是多个切面表达式的组合。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
@Aspect
|
||||
@Component //除了加上@Aspect 外 还需要声明为 spring 的组件 @Aspect 只是一个切面声明
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
/**
|
||||
* 使用 || , or 表示或
|
||||
* 使用 && , and 表示与
|
||||
* ! 表示非
|
||||
*/
|
||||
@Pointcut("execution(* com.heibaiying.service.OrderService.*(..)) && !execution(* com.heibaiying.service.OrderService.deleteOrder(..))")
|
||||
private void pointCut() {
|
||||
|
||||
}
|
||||
|
||||
@Before("pointCut()")
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
// returning 参数用于指定返回结果与哪一个参数绑定
|
||||
@AfterReturning(pointcut = "pointCut()", returning = "result")
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
@Around("pointCut()")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
// throwing 参数用于指定抛出的异常与哪一个参数绑定
|
||||
@AfterThrowing(pointcut = "pointCut()", throwing = "exception")
|
||||
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
|
||||
@After("pointCut()")
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 开启切面配置
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.*")
|
||||
@EnableAspectJAutoProxy // 开启@Aspect 注解支持 等价于<aop:aspectj-autoproxy>
|
||||
public class AopConfig {
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = AopConfig.class)
|
||||
public class AopTest {
|
||||
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void saveAndQuery() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个切面作用于同一个切入点时,可以用@Order 指定切面的执行顺序
|
||||
* 优先级高的切面在切入方法前执行的通知 (before) 会优先执行,但是位于方法后执行的通知 (after,afterReturning) 反而会延后执行
|
||||
*/
|
||||
@Test
|
||||
public void delete() {
|
||||
orderService.deleteOrder(12793179L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 切面执行顺序
|
||||
|
||||
- 多个切面作用于同一个切入点时,可以用@Order 指定切面的执行顺序
|
||||
|
||||
- 优先级高的切面在切入方法前执行的通知 (before) 会优先执行,但是位于方法后执行的通知 (after,afterReturning) 反而会延后执行,类似于同心圆原理。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/aop 执行顺序.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 附: 关于切面表达式的说明
|
||||
|
||||
切面表达式遵循以下格式:
|
||||
|
||||
```shell
|
||||
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
```
|
||||
|
||||
- 除了返回类型模式,名字模式和参数模式以外,所有的部分都是可选的;
|
||||
- `*`,它代表了匹配任意的返回类型;
|
||||
- `()` 匹配了一个不接受任何参数的方法, 而 `(..)` 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 `(*)` 匹配了一个接受一个任何类型的参数的方法。 模式 `(*,String)` 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是 String 类型。
|
||||
|
||||
下面给出一些常见切入点表达式的例子。
|
||||
|
||||
- 任意公共方法的执行:
|
||||
|
||||
```java
|
||||
execution(public * *(..))
|
||||
```
|
||||
|
||||
- 任何一个以“set”开始的方法的执行:
|
||||
|
||||
```java
|
||||
execution(* set*(..))
|
||||
```
|
||||
|
||||
- `AccountService` 接口的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
```
|
||||
|
||||
- 定义在 service 包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
```
|
||||
|
||||
- 定义在 service 包或者子包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
```
|
||||
|
||||
- 在 service 包里的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```java
|
||||
within(com.xyz.service.*)
|
||||
```
|
||||
|
||||
- 在 service 包或者子包里的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```
|
||||
within(com.xyz.service..*)
|
||||
```
|
||||
|
||||
- 实现了 `AccountService` 接口的代理对象的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```
|
||||
this(com.xyz.service.AccountService)
|
||||
```
|
||||
|
||||
更多表达式可以参考官方文档:[Declaring a Pointcut](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#aop-pointcuts)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring AOP(xml配置方式)
|
||||
|
||||
# spring AOP(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -12,228 +13,228 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
切面配置位于resources下的aop.xml文件,其中CustomAdvice是自定义切面类,OrderService是待切入的方法。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-aop.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入aop依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
//前置通知
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
//后置通知(抛出异常后不会被执行)
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
//环绕通知
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
//异常通知
|
||||
public void afterException(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
// 后置通知 总会执行 但是不能访问到返回值
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
http://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!--开启后允许使用Spring AOP的@AspectJ注解 如果是纯xml配置 可以不用开启这个声明-->
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<!-- 1.配置目标对象 -->
|
||||
<bean name="orderService" class="com.heibaiying.service.OrderServiceImpl"/>
|
||||
<!-- 2.声明切面 -->
|
||||
<bean name="myAdvice" class="com.heibaiying.advice.CustomAdvice"/>
|
||||
<!-- 3.配置将通知织入目标对象 -->
|
||||
<aop:config>
|
||||
<!--命名切入点 关于切入点更多表达式写法可以参见README.md-->
|
||||
<aop:pointcut expression="execution(* com.heibaiying.service.OrderService.*(..))" id="cutPoint"/>
|
||||
<aop:aspect ref="myAdvice">
|
||||
<!-- 前置通知 -->
|
||||
<aop:before method="before" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置通知 如果需要拿到返回值 则要指明返回值对应的参数名称-->
|
||||
<aop:after-returning method="afterReturning" pointcut-ref="cutPoint" returning="result"/>
|
||||
<!-- 环绕通知 -->
|
||||
<aop:around method="around" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置异常 如果需要拿到异常 则要指明异常对应的参数名称 -->
|
||||
<aop:after-throwing method="afterException" pointcut-ref="cutPoint" throwing="exception"/>
|
||||
<!-- 最终通知 -->
|
||||
<aop:after method="after" pointcut-ref="cutPoint"/>
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration("classpath:aop.xml")
|
||||
public class AopTest {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附: 关于切面表达式的说明
|
||||
|
||||
切面表达式遵循以下格式:
|
||||
|
||||
```shell
|
||||
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
```
|
||||
|
||||
- 除了返回类型模式,名字模式和参数模式以外,所有的部分都是可选的;
|
||||
- `*`,它代表了匹配任意的返回类型;
|
||||
- `()` 匹配了一个不接受任何参数的方法, 而 `(..)` 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 `(*)` 匹配了一个接受一个任何类型的参数的方法。 模式 `(*,String)` 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型。
|
||||
|
||||
下面给出一些常见切入点表达式的例子。
|
||||
|
||||
- 任意公共方法的执行:
|
||||
|
||||
```java
|
||||
execution(public * *(..))
|
||||
```
|
||||
|
||||
- 任何一个以“set”开始的方法的执行:
|
||||
|
||||
```java
|
||||
execution(* set*(..))
|
||||
```
|
||||
|
||||
- `AccountService` 接口的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
```
|
||||
|
||||
- 定义在service包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
```
|
||||
|
||||
- 定义在service包或者子包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
```
|
||||
|
||||
- 在service包里的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```java
|
||||
within(com.xyz.service.*)
|
||||
```
|
||||
|
||||
- 在service包或者子包里的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```
|
||||
within(com.xyz.service..*)
|
||||
```
|
||||
|
||||
- 实现了 `AccountService` 接口的代理对象的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```
|
||||
this(com.xyz.service.AccountService)
|
||||
```
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
切面配置位于 resources 下的 aop.xml 文件,其中 CustomAdvice 是自定义切面类,OrderService 是待切入的方法。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-aop.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 aop 依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
//前置通知
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
//后置通知 (抛出异常后不会被执行)
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
//环绕通知
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
//异常通知
|
||||
public void afterException(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
// 后置通知 总会执行 但是不能访问到返回值
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
http://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!--开启后允许使用 Spring AOP 的@AspectJ 注解 如果是纯 xml 配置 可以不用开启这个声明-->
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<!-- 1.配置目标对象 -->
|
||||
<bean name="orderService" class="com.heibaiying.service.OrderServiceImpl"/>
|
||||
<!-- 2.声明切面 -->
|
||||
<bean name="myAdvice" class="com.heibaiying.advice.CustomAdvice"/>
|
||||
<!-- 3.配置将通知织入目标对象 -->
|
||||
<aop:config>
|
||||
<!--命名切入点 关于切入点更多表达式写法可以参见 README.md-->
|
||||
<aop:pointcut expression="execution(* com.heibaiying.service.OrderService.*(..))" id="cutPoint"/>
|
||||
<aop:aspect ref="myAdvice">
|
||||
<!-- 前置通知 -->
|
||||
<aop:before method="before" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置通知 如果需要拿到返回值 则要指明返回值对应的参数名称-->
|
||||
<aop:after-returning method="afterReturning" pointcut-ref="cutPoint" returning="result"/>
|
||||
<!-- 环绕通知 -->
|
||||
<aop:around method="around" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置异常 如果需要拿到异常 则要指明异常对应的参数名称 -->
|
||||
<aop:after-throwing method="afterException" pointcut-ref="cutPoint" throwing="exception"/>
|
||||
<!-- 最终通知 -->
|
||||
<aop:after method="after" pointcut-ref="cutPoint"/>
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration("classpath:aop.xml")
|
||||
public class AopTest {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附: 关于切面表达式的说明
|
||||
|
||||
切面表达式遵循以下格式:
|
||||
|
||||
```shell
|
||||
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
```
|
||||
|
||||
- 除了返回类型模式,名字模式和参数模式以外,所有的部分都是可选的;
|
||||
- `*`,它代表了匹配任意的返回类型;
|
||||
- `()` 匹配了一个不接受任何参数的方法, 而 `(..)` 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 `(*)` 匹配了一个接受一个任何类型的参数的方法。 模式 `(*,String)` 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是 String 类型。
|
||||
|
||||
下面给出一些常见切入点表达式的例子。
|
||||
|
||||
- 任意公共方法的执行:
|
||||
|
||||
```java
|
||||
execution(public * *(..))
|
||||
```
|
||||
|
||||
- 任何一个以“set”开始的方法的执行:
|
||||
|
||||
```java
|
||||
execution(* set*(..))
|
||||
```
|
||||
|
||||
- `AccountService` 接口的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
```
|
||||
|
||||
- 定义在 service 包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
```
|
||||
|
||||
- 定义在 service 包或者子包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
```
|
||||
|
||||
- 在 service 包里的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```java
|
||||
within(com.xyz.service.*)
|
||||
```
|
||||
|
||||
- 在 service 包或者子包里的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```
|
||||
within(com.xyz.service..*)
|
||||
```
|
||||
|
||||
- 实现了 `AccountService` 接口的代理对象的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```
|
||||
this(com.xyz.service.AccountService)
|
||||
```
|
||||
|
||||
更多表达式可以参考官方文档:[Declaring a Pointcut](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#aop-pointcuts)
|
||||
|
||||
@@ -76,7 +76,7 @@ public class DispatcherServletInitializer extends AbstractAnnotationConfigDispat
|
||||
|
||||
#### 3、基于servlet 3.0的支持,可以采用注解的方式注册druid的servlet和filter
|
||||
|
||||
注:关于servlet 更多注解支持可以查看[Servlet 规范文档](https://github.com/heibaiying/spring-samples-for-all/blob/master/referenced%20documents/Servlet3.1%E8%A7%84%E8%8C%83%EF%BC%88%E6%9C%80%E7%BB%88%E7%89%88%EF%BC%89.pdf)中**8.1小节 注解和可插拔性**
|
||||
注:关于 servlet 更多注解支持可以查看[Servlet 规范文档](https://github.com/heibaiying/spring-samples-for-all/blob/master/referenced%20documents/Servlet3.1%E8%A7%84%E8%8C%83%EF%BC%88%E6%9C%80%E7%BB%88%E7%89%88%EF%BC%89.pdf) 中**8.1 小节 注解和可插拔性**
|
||||
|
||||
```java
|
||||
/**
|
||||
@@ -98,7 +98,7 @@ public class DruidStatViewServlet extends StatViewServlet {
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : WebStatFilter用于采集web-jdbc关联监控的数据
|
||||
* @description : WebStatFilter 用于采集 web-jdbc 关联监控的数据
|
||||
*/
|
||||
@WebFilter(filterName="druidWebStatFilter",urlPatterns="/*",
|
||||
initParams={
|
||||
@@ -124,7 +124,7 @@ mysql.password=root
|
||||
```properties
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
@@ -157,7 +157,7 @@ public class DataSourceConfig {
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于xml中<tx:annotation-driven/>
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于 xml 中<tx:annotation-driven/>
|
||||
@EnableWebMvc
|
||||
@ComponentScan(basePackages = {"com.heibaiying.*"})
|
||||
public class ServletConfig implements WebMvcConfigurer {
|
||||
@@ -170,7 +170,7 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置druid 数据源
|
||||
* 配置 druid 数据源
|
||||
*/
|
||||
@Bean
|
||||
public DruidDataSource dataSource(DataSourceConfig sourceConfig) throws SQLException {
|
||||
@@ -189,24 +189,24 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
dataSource.setMinEvictableIdleTimeMillis(600000);
|
||||
dataSource.setMaxEvictableIdleTimeMillis(900000);
|
||||
|
||||
/* validationQuery 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。
|
||||
/* validationQuery 用来检测连接是否有效的 sql,要求是一个查询语句,常用 select 'x'。
|
||||
* 但是在 oracle 数据库下需要写成 select 'x' from dual 不然实例化数据源的时候就会失败,
|
||||
* 这是由于oracle 和 mysql 语法间的差异造成的
|
||||
* 这是由于 oracle 和 mysql 语法间的差异造成的
|
||||
*/
|
||||
dataSource.setValidationQuery("select 'x'");
|
||||
|
||||
// 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
|
||||
// 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
|
||||
dataSource.setTestWhileIdle(true);
|
||||
// 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
|
||||
// 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
|
||||
dataSource.setTestOnBorrow(false);
|
||||
// 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
|
||||
// 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能
|
||||
dataSource.setTestOnReturn(false);
|
||||
|
||||
// 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。
|
||||
// 连接池中的 minIdle 数量以内的连接,空闲时间超过 minEvictableIdleTimeMillis,则会执行 keepAlive 操作。
|
||||
dataSource.setPhyMaxUseCount(100000);
|
||||
|
||||
/*配置监控统计拦截的filters Druid连接池的监控信息主要是通过StatFilter 采集的,
|
||||
采集的信息非常全面,包括SQL执行、并发、慢查、执行时间区间分布等*/
|
||||
/*配置监控统计拦截的 filters Druid 连接池的监控信息主要是通过 StatFilter 采集的,
|
||||
采集的信息非常全面,包括 SQL 执行、并发、慢查、执行时间区间分布等*/
|
||||
dataSource.setFilters("stat");
|
||||
|
||||
return dataSource;
|
||||
@@ -214,10 +214,10 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
|
||||
|
||||
/**
|
||||
* 配置mybatis 会话工厂
|
||||
* 配置 mybatis 会话工厂
|
||||
*
|
||||
* @param dataSource 这个参数的名称需要保持和上面方法名一致 才能自动注入,因为
|
||||
* 采用@Bean注解生成的bean 默认采用方法名为名称,当然也可以在使用@Bean时指定name属性
|
||||
* 采用@Bean 注解生成的 bean 默认采用方法名为名称,当然也可以在使用@Bean 时指定 name 属性
|
||||
*/
|
||||
@Bean
|
||||
public SqlSessionFactoryBean sessionFactoryBean(DruidDataSource dataSource) throws IOException {
|
||||
@@ -230,7 +230,7 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置mybatis 会话工厂
|
||||
* 配置 mybatis 会话工厂
|
||||
*/
|
||||
@Bean
|
||||
public MapperScannerConfigurer MapperScannerConfigurer() {
|
||||
@@ -267,7 +267,7 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询sql -->
|
||||
<!-- 打印查询 sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
@@ -358,6 +358,6 @@ public class OracleController {
|
||||
|
||||
#### 10、druid 监控页面访问地址http://localhost:8080/druid/index.html
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||
version="3.1">
|
||||
|
||||
<!--配置spring前端控制器-->
|
||||
<!--配置 spring 前端控制器-->
|
||||
<servlet>
|
||||
<servlet-name>springMvc</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
@@ -104,7 +104,7 @@
|
||||
<url-pattern>/druid/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<!--配置WebStatFilter用于采集web-jdbc关联监控的数据-->
|
||||
<!--配置 WebStatFilter 用于采集 web-jdbc 关联监控的数据-->
|
||||
<filter>
|
||||
<filter-name>DruidWebStatFilter</filter-name>
|
||||
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
|
||||
@@ -131,7 +131,7 @@ mysql.username=root
|
||||
mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
@@ -150,19 +150,19 @@ oracle.password=密码
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--使用默认的Servlet来响应静态文件 -->
|
||||
<!--使用默认的 Servlet 来响应静态文件 -->
|
||||
<mvc:default-servlet-handler/>
|
||||
|
||||
<!-- 开启注解驱动-->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!--引入druid.xml配置 由于druid 的可配置项比较多,所以可以单独拆分为一个配置文件-->
|
||||
<!--引入 druid.xml 配置 由于 druid 的可配置项比较多,所以可以单独拆分为一个配置文件-->
|
||||
<import resource="druid.xml"/>
|
||||
|
||||
<!--配置 mybatis 会话工厂 -->
|
||||
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<!--指定mapper文件所在的位置-->
|
||||
<!--指定 mapper 文件所在的位置-->
|
||||
<property name="mapperLocations" value="classpath*:/mappers/**/*.xml"/>
|
||||
<property name="configLocation" value="classpath:mybatisConfig.xml"/>
|
||||
</bean>
|
||||
@@ -172,7 +172,7 @@ oracle.password=密码
|
||||
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
|
||||
<!--指定会话工厂 -->
|
||||
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
|
||||
<!-- 指定mybatis接口所在的包 -->
|
||||
<!-- 指定 mybatis 接口所在的包 -->
|
||||
<property name="basePackage" value="com.heibaiying.dao"/>
|
||||
</bean>
|
||||
|
||||
@@ -182,7 +182,7 @@ oracle.password=密码
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- 开启事务注解@Transactional支持 -->
|
||||
<!-- 开启事务注解@Transactional 支持 -->
|
||||
<tx:annotation-driven/>
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ oracle.password=密码
|
||||
<!--指定配置文件的位置-->
|
||||
<context:property-placeholder location="classpath:jdbc.properties"/>
|
||||
|
||||
<!--配置druid数据源 关于更多的配置项 可以参考官方文档 <a href="https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8" > -->
|
||||
<!--配置 druid 数据源 关于更多的配置项 可以参考官方文档 <a href="https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8" > -->
|
||||
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
|
||||
<!-- 基本属性 url、user、password -->
|
||||
<property name="url" value="${mysql.url}"/>
|
||||
@@ -224,24 +224,24 @@ oracle.password=密码
|
||||
<!-- 配置一个连接在池中最大生存的时间,单位是毫秒 -->
|
||||
<property name="maxEvictableIdleTimeMillis" value="900000"/>
|
||||
|
||||
<!--validationQuery 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。
|
||||
<!--validationQuery 用来检测连接是否有效的 sql,要求是一个查询语句,常用 select 'x'。
|
||||
但是在 oracle 数据库下需要写成 select 'x' from dual 不然实例化数据源的时候就会失败,
|
||||
这是由于oracle 和 mysql 语法间的差异造成的-->
|
||||
这是由于 oracle 和 mysql 语法间的差异造成的-->
|
||||
<property name="validationQuery" value="select 'x'"/>
|
||||
<!--建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,
|
||||
如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。-->
|
||||
<!--建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,
|
||||
如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。-->
|
||||
<property name="testWhileIdle" value="true"/>
|
||||
<!--申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。-->
|
||||
<!--申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。-->
|
||||
<property name="testOnBorrow" value="false"/>
|
||||
<!--归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。-->
|
||||
<!--归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。-->
|
||||
<property name="testOnReturn" value="false"/>
|
||||
|
||||
<!--连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。-->
|
||||
<!--连接池中的 minIdle 数量以内的连接,空闲时间超过 minEvictableIdleTimeMillis,则会执行 keepAlive 操作。-->
|
||||
<property name="keepAlive" value="true"/>
|
||||
<property name="phyMaxUseCount" value="100000"/>
|
||||
|
||||
<!-- 配置监控统计拦截的filters Druid连接池的监控信息主要是通过StatFilter 采集的,
|
||||
采集的信息非常全面,包括SQL执行、并发、慢查、执行时间区间分布等-->
|
||||
<!-- 配置监控统计拦截的 filters Druid 连接池的监控信息主要是通过 StatFilter 采集的,
|
||||
采集的信息非常全面,包括 SQL 执行、并发、慢查、执行时间区间分布等-->
|
||||
<property name="filters" value="stat"/>
|
||||
</bean>
|
||||
|
||||
@@ -262,7 +262,7 @@ oracle.password=密码
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询sql -->
|
||||
<!-- 打印查询 sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
@@ -382,4 +382,4 @@ public class OracleController {
|
||||
|
||||
#### 8、druid 监控页面访问地址http://localhost:8080/druid/index.html
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 dubbo(注解方式)
|
||||
|
||||
# spring 整合 dubbo(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
|
||||
<a href="#二项目依赖">二、项目依赖</a><br/>
|
||||
<a href="#三公共模块dubbo-ano-common">三、公共模块(dubbo-ano-common)</a><br/>
|
||||
@@ -14,328 +15,328 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo-annotation下构建三个子模块:
|
||||
|
||||
1. dubbo-ano-common 是公共模块,用于存放公共的接口和bean,被dubbo-ano-provider和dubbo-ano-provider在pom.xml中引用;
|
||||
2. dubbo-ano-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-ano-provider 是服务的消费者,调用provider提供的查询服务。
|
||||
|
||||
1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中统一导入依赖dubbo依赖的的jar包**
|
||||
|
||||
这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-ano-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(dubbo-ano-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-provider.png"/> </div>
|
||||
|
||||
#### 4.1 提供方配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 提供方应用信息,用于计算依赖关系
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-provider");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用zookeeper注册中心暴露服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用dubbo协议在20880端口暴露服务
|
||||
*/
|
||||
@Bean
|
||||
public ProtocolConfig protocolConfig() {
|
||||
ProtocolConfig protocolConfig = new ProtocolConfig();
|
||||
protocolConfig.setName("dubbo");
|
||||
protocolConfig.setPort(20880);
|
||||
return protocolConfig;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 使用注解@Service暴露服务
|
||||
|
||||
需要注意的是这里的@Service注解不是spring的注解,而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
|
||||
|
||||
```java
|
||||
package com.heibaiying.service;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Service;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service(timeout = 5000)
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 五、服务消费者(dubbo-ano-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-consumer.png"/> </div>
|
||||
|
||||
#### 1.消费方的配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-consumer");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置调用服务超时时间
|
||||
* 关闭所有服务的启动时检查
|
||||
*/
|
||||
@Bean
|
||||
public ConsumerConfig consumerConfig() {
|
||||
ConsumerConfig consumerConfig = new ConsumerConfig();
|
||||
consumerConfig.setTimeout(3000);
|
||||
consumerConfig.setCheck(false);
|
||||
return consumerConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用zookeeper注册中心暴露发现服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.使用注解@Reference引用远程服务
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
// dubbo远程引用注解
|
||||
@Reference
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer中 pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider中 pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照 dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用 maven 多模块的构建方式,在 spring-dubbo-annotation 下构建三个子模块:
|
||||
|
||||
1. dubbo-ano-common 是公共模块,用于存放公共的接口和 bean,被 dubbo-ano-provider 和 dubbo-ano-provider 在 pom.xml 中引用;
|
||||
2. dubbo-ano-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-ano-provider 是服务的消费者,调用 provider 提供的查询服务。
|
||||
|
||||
1.2 本项目 dubbo 的搭建采用 zookeeper 作为注册中心, 关于 zookeeper 的安装和基本操作可以参见我的手记[Zookeeper 基础命令与 Java 客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中统一导入依赖 dubbo 依赖的的 jar 包**
|
||||
|
||||
这里需要注意的是 ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是 beta 版本,所以 zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework 自动引用的最新的 3.5 的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-ano-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(dubbo-ano-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-provider.png"/> </div>
|
||||
|
||||
#### 4.1 提供方配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 提供方应用信息,用于计算依赖关系
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-provider");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 zookeeper 注册中心暴露服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用 dubbo 协议在 20880 端口暴露服务
|
||||
*/
|
||||
@Bean
|
||||
public ProtocolConfig protocolConfig() {
|
||||
ProtocolConfig protocolConfig = new ProtocolConfig();
|
||||
protocolConfig.setName("dubbo");
|
||||
protocolConfig.setPort(20880);
|
||||
return protocolConfig;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 使用注解@Service暴露服务
|
||||
|
||||
需要注意的是这里的@Service 注解不是 spring 的注解,而是 dubbo 的注解 com.alibaba.dubbo.config.annotation.Service
|
||||
|
||||
```java
|
||||
package com.heibaiying.service;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Service;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service(timeout = 5000)
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 五、服务消费者(dubbo-ano-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-consumer.png"/> </div>
|
||||
|
||||
#### 1.消费方的配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-consumer");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置调用服务超时时间
|
||||
* 关闭所有服务的启动时检查
|
||||
*/
|
||||
@Bean
|
||||
public ConsumerConfig consumerConfig() {
|
||||
ConsumerConfig consumerConfig = new ConsumerConfig();
|
||||
consumerConfig.setTimeout(3000);
|
||||
consumerConfig.setCheck(false);
|
||||
return consumerConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 zookeeper 注册中心暴露发现服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.使用注解@Reference引用远程服务
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
// dubbo 远程引用注解
|
||||
@Reference
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer 和 provider 模块均依赖公共模块,所以在构建 consumer 和 provider 项目前需要将 common 模块安装到本地仓库,**依次**对**父工程**和**common 模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer 中 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider 中 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是 127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 dubbo(xml配置方式)
|
||||
|
||||
# spring 整合 dubbo(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
|
||||
<a href="#二项目依赖">二、项目依赖</a><br/>
|
||||
<a href="#三公共模块dubbo-common">三、公共模块(dubbo-common)</a><br/>
|
||||
@@ -14,280 +15,280 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo下构建三个子模块:
|
||||
|
||||
1. dubbo-common 是公共模块,用于存放公共的接口和bean,被dubbo-provider和dubbo-provider在pom.xml中引用;
|
||||
2. dubbo-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-provider 是服务的消费者,调用provider提供的查询服务。
|
||||
|
||||
1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中统一导入依赖dubbo依赖的的jar包**
|
||||
|
||||
这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(dubbo-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-provider.png"/> </div>
|
||||
|
||||
#### 4.1 productService是服务的提供者( 商品数据用模拟数据展示)
|
||||
|
||||
注:这里实现的接口IProductService来源于公共模块
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 在dubbo.xml暴露服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 提供方应用信息,用于计算依赖关系 -->
|
||||
<dubbo:application name="dubbo-provider"/>
|
||||
|
||||
<!-- 使用zookeeper注册中心暴露服务地址 如果是集群配置 用, 分隔地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 用dubbo协议在20880端口暴露服务 -->
|
||||
<dubbo:protocol name="dubbo" port="20880"/>
|
||||
|
||||
<!-- 声明需要暴露的服务接口 -->
|
||||
<dubbo:service interface="com.heibaiying.api.IProductService" ref="productService"/>
|
||||
|
||||
<!-- 和本地bean一样实现服务 -->
|
||||
<bean id="productService" class="com.heibaiying.service.ProductService"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
## 五、服务消费者(dubbo-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-consumer.png"/> </div>
|
||||
|
||||
#### 1.在dubbo.xml调用远程的服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
|
||||
<dubbo:application name="dubbo-consumer">
|
||||
<dubbo:parameter key="shutdown.timeout" value="60000"/> <!-- 单位毫秒 -->
|
||||
</dubbo:application>
|
||||
|
||||
<!--Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"。-->
|
||||
<!--可以关闭所有服务的启动时检查 -->
|
||||
<dubbo:consumer check="false" />
|
||||
|
||||
<!-- 使用zookeeper注册中心暴露发现服务地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
|
||||
<dubbo:reference id="sellService" interface="com.heibaiying.api.IProductService"/>
|
||||
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.消费服务
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
@Autowired
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer中 pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider中 pom.xml如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照 dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用 maven 多模块的构建方式,在 spring-dubbo 下构建三个子模块:
|
||||
|
||||
1. dubbo-common 是公共模块,用于存放公共的接口和 bean,被 dubbo-provider 和 dubbo-provider 在 pom.xml 中引用;
|
||||
2. dubbo-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-provider 是服务的消费者,调用 provider 提供的查询服务。
|
||||
|
||||
1.2 本项目 dubbo 的搭建采用 zookeeper 作为注册中心, 关于 zookeeper 的安装和基本操作可以参见我的手记[Zookeeper 基础命令与 Java 客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中统一导入依赖 dubbo 依赖的的 jar 包**
|
||||
|
||||
这里需要注意的是 ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是 beta 版本,所以 zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework 自动引用的最新的 3.5 的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(dubbo-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-provider.png"/> </div>
|
||||
|
||||
#### 4.1 productService是服务的提供者( 商品数据用模拟数据展示)
|
||||
|
||||
注:这里实现的接口 IProductService 来源于公共模块
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 在dubbo.xml暴露服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 提供方应用信息,用于计算依赖关系 -->
|
||||
<dubbo:application name="dubbo-provider"/>
|
||||
|
||||
<!-- 使用 zookeeper 注册中心暴露服务地址 如果是集群配置 用, 分隔地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 用 dubbo 协议在 20880 端口暴露服务 -->
|
||||
<dubbo:protocol name="dubbo" port="20880"/>
|
||||
|
||||
<!-- 声明需要暴露的服务接口 -->
|
||||
<dubbo:service interface="com.heibaiying.api.IProductService" ref="productService"/>
|
||||
|
||||
<!-- 和本地 bean 一样实现服务 -->
|
||||
<bean id="productService" class="com.heibaiying.service.ProductService"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
## 五、服务消费者(dubbo-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-consumer.png"/> </div>
|
||||
|
||||
#### 1.在dubbo.xml调用远程的服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
|
||||
<dubbo:application name="dubbo-consumer">
|
||||
<dubbo:parameter key="shutdown.timeout" value="60000"/> <!-- 单位毫秒 -->
|
||||
</dubbo:application>
|
||||
|
||||
<!--Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"。-->
|
||||
<!--可以关闭所有服务的启动时检查 -->
|
||||
<dubbo:consumer check="false" />
|
||||
|
||||
<!-- 使用 zookeeper 注册中心暴露发现服务地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 生成远程服务代理,可以和本地 bean 一样使用 demoService -->
|
||||
<dubbo:reference id="sellService" interface="com.heibaiying.api.IProductService"/>
|
||||
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.消费服务
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
@Autowired
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer 和 provider 模块均依赖公共模块,所以在构建 consumer 和 provider 项目前需要将 common 模块安装到本地仓库,**依次**对**父工程**和**common 模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer 中 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider 中 pom.xml 如下
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是 127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -10,288 +11,288 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置类为com.heibaiying.config下EmailConfig.java;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为SendEmail。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-email-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是beetl,beetl性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.email")
|
||||
public class EmailConfig {
|
||||
|
||||
/***
|
||||
* 在这里可以声明不同的邮件服务器主机,通常是SMTP主机,而具体的用户名和时授权码则建议在业务中从数据库查询
|
||||
*/
|
||||
@Bean(name = "qqMailSender")
|
||||
JavaMailSenderImpl javaMailSender() {
|
||||
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
|
||||
javaMailSender.setHost("smtp.qq.com");
|
||||
javaMailSender.setPassword("587");
|
||||
return javaMailSender;
|
||||
}
|
||||
|
||||
/***
|
||||
* 配置模板引擎
|
||||
*/
|
||||
@Bean
|
||||
GroupTemplate groupTemplate() throws IOException {
|
||||
//指定加载模板资源的位置 指定在classpath:beetl下-
|
||||
ClasspathResourceLoader loader = new ClasspathResourceLoader("beetl");
|
||||
//beetl配置 这里采用默认的配置-
|
||||
org.beetl.core.Configuration configuration = org.beetl.core.Configuration.defaultConfiguration();
|
||||
return new GroupTemplate(loader, configuration);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板template.html如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = EmailConfig.class)
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
|
||||
springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置类为 com.heibaiying.config 下 EmailConfig.java;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在 SpringMail 类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为 SendEmail。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-email-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入邮件发送的支持包 spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是 beetl,beetl 性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.email")
|
||||
public class EmailConfig {
|
||||
|
||||
/***
|
||||
* 在这里可以声明不同的邮件服务器主机,通常是 SMTP 主机,而具体的用户名和时授权码则建议在业务中从数据库查询
|
||||
*/
|
||||
@Bean(name = "qqMailSender")
|
||||
JavaMailSenderImpl javaMailSender() {
|
||||
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
|
||||
javaMailSender.setHost("smtp.qq.com");
|
||||
javaMailSender.setPassword("587");
|
||||
return javaMailSender;
|
||||
}
|
||||
|
||||
/***
|
||||
* 配置模板引擎
|
||||
*/
|
||||
@Bean
|
||||
GroupTemplate groupTemplate() throws IOException {
|
||||
//指定加载模板资源的位置 指定在 classpath:beetl 下-
|
||||
ClasspathResourceLoader loader = new ClasspathResourceLoader("beetl");
|
||||
//beetl 配置 这里采用默认的配置-
|
||||
org.beetl.core.Configuration configuration = org.beetl.core.Configuration.defaultConfiguration();
|
||||
return new GroupTemplate(loader, configuration);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在 qq 邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为 true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为 true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用 true 标志来指示包含的文本是 HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为 true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的 beetl 模板引擎,原因是其性能优异,官网是介绍其性能 6 倍与 freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板 template.html 如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = EmailConfig.class)
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
|
||||
springMail.sendTextMessage(from, authWord, to, "spring 简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm 相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ 模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring 多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring 内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ 模型架构.png"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring 模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -10,299 +11,299 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置文件为springApplication.xml;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为SendEmail。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-email.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是beetl,beetl性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.email"/>
|
||||
|
||||
<!--在这里可以声明不同的邮件服务器主机,通常是SMTP主机,而具体的用户名和时授权码则建议在业务中从数据库查询-->
|
||||
<bean id="qqMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
||||
<!--qq 邮箱配置 <a href="https://service.mail.qq.com/cgi-bin/help?subtype=1&no=167&id=28"> -->
|
||||
<property name="host" value="smtp.qq.com"/>
|
||||
<property name="port" value="587"/>
|
||||
</bean>
|
||||
|
||||
<!--配置beetle模板引擎 如果不使用模板引擎,以下的配置不是必须的-->
|
||||
<bean id="resourceLoader" class="org.beetl.core.resource.ClasspathResourceLoader">
|
||||
<!--指定加载模板资源的位置 指定在classpath:beetl下-->
|
||||
<constructor-arg name="root" value="beetl"/>
|
||||
</bean>
|
||||
<!--beetl 配置 这里采用默认的配置-->
|
||||
<bean id="configuration" class="org.beetl.core.Configuration" init-method="defaultConfiguration"/>
|
||||
<bean id="groupTemplate" class="org.beetl.core.GroupTemplate">
|
||||
<constructor-arg name="loader" ref="resourceLoader"/>
|
||||
<constructor-arg name="conf" ref="configuration"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板template.html如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
|
||||
/**
|
||||
* 简单邮件测试
|
||||
*/
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送内嵌资源的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送模板邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置文件为 springApplication.xml;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在 SpringMail 类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为 SendEmail。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-email.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入邮件发送的支持包 spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是 beetl,beetl 性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.email"/>
|
||||
|
||||
<!--在这里可以声明不同的邮件服务器主机,通常是 SMTP 主机,而具体的用户名和时授权码则建议在业务中从数据库查询-->
|
||||
<bean id="qqMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
||||
<!--qq 邮箱配置 <a href="https://service.mail.qq.com/cgi-bin/help?subtype=1&no=167&id=28"> -->
|
||||
<property name="host" value="smtp.qq.com"/>
|
||||
<property name="port" value="587"/>
|
||||
</bean>
|
||||
|
||||
<!--配置 beetle 模板引擎 如果不使用模板引擎,以下的配置不是必须的-->
|
||||
<bean id="resourceLoader" class="org.beetl.core.resource.ClasspathResourceLoader">
|
||||
<!--指定加载模板资源的位置 指定在 classpath:beetl 下-->
|
||||
<constructor-arg name="root" value="beetl"/>
|
||||
</bean>
|
||||
<!--beetl 配置 这里采用默认的配置-->
|
||||
<bean id="configuration" class="org.beetl.core.Configuration" init-method="defaultConfiguration"/>
|
||||
<bean id="groupTemplate" class="org.beetl.core.GroupTemplate">
|
||||
<constructor-arg name="loader" ref="resourceLoader"/>
|
||||
<constructor-arg name="conf" ref="configuration"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在 qq 邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为 true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为 true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用 true 标志来指示包含的文本是 HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为 true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的 beetl 模板引擎,原因是其性能优异,官网是介绍其性能 6 倍与 freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板 template.html 如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
|
||||
/**
|
||||
* 简单邮件测试
|
||||
*/
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
springMail.sendTextMessage(from, authWord, to, "spring 简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm 相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ 模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring 多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送内嵌资源的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring 内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ 模型架构.png"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送模板邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring 模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
#### 1.1 项目目录结构
|
||||
|
||||
1. 数据源配置位于config目录下的DatabaseConfig.java和DataSourceConfig.java
|
||||
1. 数据源配置位于 config 目录下的 DatabaseConfig.java 和 DataSourceConfig.java
|
||||
2. 项目以单元测试的方法进行测试
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-jdbc-annotation.png"/> </div>
|
||||
@@ -59,7 +59,7 @@ mysql.password=root
|
||||
```properties
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
@@ -87,7 +87,7 @@ public class DataSourceConfig {
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于xml中<tx:annotation-driven/>
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于 xml 中<tx:annotation-driven/>
|
||||
@ComponentScan(basePackages = {"com.heibaiying.*"})
|
||||
public class DatabaseConfig {
|
||||
|
||||
@@ -106,9 +106,9 @@ public class DatabaseConfig {
|
||||
|
||||
|
||||
/**
|
||||
* 配置jdbcTemplate
|
||||
* 配置 jdbcTemplate
|
||||
* @param dataSource 这个参数的名称需要保持和上面方法名一致 才能自动注入,因为
|
||||
* 采用@Bean注解生成的bean 默认采用方法名为名称,当然也可以在使用@Bean时指定name属性
|
||||
* 采用@Bean 注解生成的 bean 默认采用方法名为名称,当然也可以在使用@Bean 时指定 name 属性
|
||||
*/
|
||||
@Bean
|
||||
public JdbcTemplate jdbcTemplate(DriverManagerDataSource dataSource) {
|
||||
@@ -141,7 +141,7 @@ public class MysqlDaoImpl implements MysqlDao {
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 更多JDBC 的使用可以参考官方文档
|
||||
* 更多 JDBC 的使用可以参考官方文档
|
||||
* @see <a href="https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#jdbc-JdbcTemplate">JdbcTemplate</a>
|
||||
*/
|
||||
public List<Relation> get() {
|
||||
@@ -171,7 +171,7 @@ public class OracleDaoImpl implements OracleDao {
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 更多JDBC 的使用可以参考官方文档
|
||||
* 更多 JDBC 的使用可以参考官方文档
|
||||
* @see <a href="https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#jdbc-JdbcTemplate">JdbcTemplate</a>
|
||||
*/
|
||||
public List<Flow> get() {
|
||||
|
||||
@@ -55,7 +55,7 @@ mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
@@ -102,7 +102,7 @@ oracle.password=密码
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- 开启事务注解@Transactional支持 -->
|
||||
<!-- 开启事务注解@Transactional 支持 -->
|
||||
<tx:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
@@ -118,7 +118,7 @@ public class MysqlDaoImpl implements MysqlDao {
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 更多JDBC 的使用可以参考官方文档
|
||||
* 更多 JDBC 的使用可以参考官方文档
|
||||
* @see <a href="https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#jdbc-JdbcTemplate">JdbcTemplate</a>
|
||||
*/
|
||||
public List<Relation> get() {
|
||||
@@ -146,7 +146,7 @@ public class OracleDaoImpl implements OracleDao {
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 更多JDBC 的使用可以参考官方文档
|
||||
* 更多 JDBC 的使用可以参考官方文档
|
||||
* @see <a href="https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#jdbc-JdbcTemplate">JdbcTemplate</a>
|
||||
*/
|
||||
public List<Flow> get() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 mecached(注解方式)
|
||||
|
||||
# spring 整合 mecached(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11--XMemcached客户端说明">1.1 XMemcached客户端说明</a><br/>
|
||||
<a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
|
||||
@@ -13,151 +14,151 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached的整合配置位于com.heibaiying.config文件夹下。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-memcached-annotation.png"/> </div>
|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入xmemcached依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClient() {
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClientForCluster() {
|
||||
|
||||
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11211));
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
|
||||
// 赋予权重
|
||||
int[] weights = {1, 2};
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
|
||||
// 设置连接池大小
|
||||
builder.setConnectionPoolSize(10);
|
||||
// 协议工厂
|
||||
builder.setCommandFactory(new TextCommandFactory());
|
||||
// 分布策略,一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
|
||||
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
|
||||
// 设置序列化器
|
||||
builder.setTranscoder(new SerializingTranscoder());
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
|
||||
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached 是基于 java nio 的 memcached 高性能客户端,支持完整的 memcached 协议,支持客户端分布并且提供了一致性哈希 (consistent hash) 算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached 的整合配置位于 com.heibaiying.config 文件夹下。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-memcached-annotation.png"/> </div>
|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 xmemcached 依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClient() {
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClientForCluster() {
|
||||
|
||||
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11211));
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
|
||||
// 赋予权重
|
||||
int[] weights = {1, 2};
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
|
||||
// 设置连接池大小
|
||||
builder.setConnectionPoolSize(10);
|
||||
// 协议工厂
|
||||
builder.setCommandFactory(new TextCommandFactory());
|
||||
// 分布策略,一致性哈希 KetamaMemcachedSessionLocator 或者 ArraySessionLocator(默认)
|
||||
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
|
||||
// 设置序列化器
|
||||
builder.setTranscoder(new SerializingTranscoder());
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached 单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当 key 不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在 key 的 value 值 |
|
||||
| 追加内容 append | append key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 前面追加 prepend | prepend key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr 和 decr 只能操作能转换为数字的 Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr 不能将数字减少至 0 以下 |
|
||||
| 清库 | flush_all | |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 mecached(xml配置方式)
|
||||
|
||||
# spring 整合 mecached(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11--XMemcached客户端说明">1.1 XMemcached客户端说明</a><br/>
|
||||
<a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
|
||||
@@ -13,192 +14,192 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached的整合配置位于resources下的memcached文件夹下,其中集群配置用cluster开头。所有配置按照需要在springApplication.xml用import导入。
|
||||
2. 实体类Programmer.java用于测试memcached序列化与反序列化
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-memcached.png"/> </div>
|
||||
|
||||
**springapplication.xml文件:**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--memcached 单机版配置-->
|
||||
<!--<import resource="classpath*:memcached/singleConfig.xml"/>-->
|
||||
|
||||
<!--memcached 集群配置-->
|
||||
<import resource="classpath*:memcached/clusterConfig.xml"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入xmemcached依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<constructor-arg name="addressList" value="192.168.200.201:11211"/>
|
||||
</bean>
|
||||
|
||||
<bean id="memcachedClient" factory-bean="memcachedClientBuilder" factory-method="build"
|
||||
destroy-method="shutdown"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean name="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<!--memcached servers 节点列表-->
|
||||
<constructor-arg name="addressList">
|
||||
<list>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11211"/>
|
||||
</bean>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11212"/>
|
||||
</bean>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--与servers对应的节点的权重-->
|
||||
<constructor-arg name="weights">
|
||||
<list>
|
||||
<value>1</value>
|
||||
<value>2</value>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--连接池大小-->
|
||||
<property name="connectionPoolSize" value="10"/>
|
||||
<!--协议工厂-->
|
||||
<property name="commandFactory">
|
||||
<bean class="net.rubyeye.xmemcached.command.TextCommandFactory"/>
|
||||
</property>
|
||||
<!--分布策略,一致性哈希net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)-->
|
||||
<property name="sessionLocator">
|
||||
<bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator"/>
|
||||
</property>
|
||||
<!--序列化转换器,默认使用net.rubyeye.xmemcached.transcoders.SerializingTranscoder-->
|
||||
<property name="transcoder">
|
||||
<bean class="net.rubyeye.xmemcached.transcoders.SerializingTranscoder"/>
|
||||
</property>
|
||||
</bean>
|
||||
<!-- 集群配置 实例化bean -->
|
||||
<bean name="memcachedClientForCulster" factory-bean="memcachedClientBuilder"
|
||||
factory-method="build" destroy-method="shutdown"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
|
||||
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached 是基于 java nio 的 memcached 高性能客户端,支持完整的 memcached 协议,支持客户端分布并且提供了一致性哈希 (consistent hash) 算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached 的整合配置位于 resources 下的 memcached 文件夹下,其中集群配置用 cluster 开头。所有配置按照需要在 springApplication.xml 用 import 导入。
|
||||
2. 实体类 Programmer.java 用于测试 memcached 序列化与反序列化
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-memcached.png"/> </div>
|
||||
|
||||
**springapplication.xml 文件:**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--memcached 单机版配置-->
|
||||
<!--<import resource="classpath*:memcached/singleConfig.xml"/>-->
|
||||
|
||||
<!--memcached 集群配置-->
|
||||
<import resource="classpath*:memcached/clusterConfig.xml"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 xmemcached 依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<constructor-arg name="addressList" value="192.168.200.201:11211"/>
|
||||
</bean>
|
||||
|
||||
<bean id="memcachedClient" factory-bean="memcachedClientBuilder" factory-method="build"
|
||||
destroy-method="shutdown"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean name="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<!--memcached servers 节点列表-->
|
||||
<constructor-arg name="addressList">
|
||||
<list>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11211"/>
|
||||
</bean>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11212"/>
|
||||
</bean>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--与 servers 对应的节点的权重-->
|
||||
<constructor-arg name="weights">
|
||||
<list>
|
||||
<value>1</value>
|
||||
<value>2</value>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--连接池大小-->
|
||||
<property name="connectionPoolSize" value="10"/>
|
||||
<!--协议工厂-->
|
||||
<property name="commandFactory">
|
||||
<bean class="net.rubyeye.xmemcached.command.TextCommandFactory"/>
|
||||
</property>
|
||||
<!--分布策略,一致性哈希 net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator 或者 ArraySessionLocator(默认)-->
|
||||
<property name="sessionLocator">
|
||||
<bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator"/>
|
||||
</property>
|
||||
<!--序列化转换器,默认使用 net.rubyeye.xmemcached.transcoders.SerializingTranscoder-->
|
||||
<property name="transcoder">
|
||||
<bean class="net.rubyeye.xmemcached.transcoders.SerializingTranscoder"/>
|
||||
</property>
|
||||
</bean>
|
||||
<!-- 集群配置 实例化 bean -->
|
||||
<bean name="memcachedClientForCulster" factory-bean="memcachedClientBuilder"
|
||||
factory-method="build" destroy-method="shutdown"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached 单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当 key 不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在 key 的 value 值 |
|
||||
| 追加内容 append | append key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 前面追加 prepend | prepend key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr 和 decr 只能操作能转换为数字的 Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr 不能将数字减少至 0 以下 |
|
||||
| 清库 | flush_all | |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 mongodb(注解方式)
|
||||
|
||||
# spring 整合 mongodb(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -10,162 +11,162 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于com.heibaiying.config下,项目以单元测试的方式进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mongodb-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入mongodb整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件及其映射类
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置属性
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:mongodb.properties")
|
||||
public class MongoProperty {
|
||||
|
||||
@Value("${mongo.host}")
|
||||
private String host;
|
||||
@Value("${mongo.port}")
|
||||
private int port;
|
||||
@Value("${mongo.dbname}")
|
||||
private String dbname;
|
||||
@Value("${mongo.connectionsPerHost}")
|
||||
private int connectionsPerHost;
|
||||
@Value("${mongo.threadsAllowedToBlockForConnectionMultiplier}")
|
||||
private int multiplier;
|
||||
@Value("${mongo.connectTimeout}")
|
||||
private int connectTimeout;
|
||||
@Value("${mongo.maxWaitTime}")
|
||||
private int maxWaitTime;
|
||||
@Value("${mongo.socketKeepAlive}")
|
||||
private boolean socketKeepAlive;
|
||||
@Value("${mongo.socketTimeout}")
|
||||
private int socketTimeout;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.*")
|
||||
public class MongoConfig {
|
||||
|
||||
@Bean
|
||||
public MongoDbFactory mongoDbFactory(MongoProperty mongo) {
|
||||
MongoClientOptions options = MongoClientOptions.builder()
|
||||
.threadsAllowedToBlockForConnectionMultiplier(mongo.getMultiplier())
|
||||
.connectionsPerHost(mongo.getConnectionsPerHost())
|
||||
.connectTimeout(mongo.getConnectTimeout())
|
||||
.maxWaitTime(mongo.getMaxWaitTime())
|
||||
.socketTimeout(mongo.getSocketTimeout())
|
||||
.build();
|
||||
MongoClient client = new MongoClient(new ServerAddress(mongo.getHost(), mongo.getPort()), options);
|
||||
return new SimpleMongoDbFactory(client, mongo.getDbname());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) {
|
||||
return new MongoTemplate(mongoDbFactory);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = MongoConfig.class)
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于 com.heibaiying.config 下,项目以单元测试的方式进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mongodb-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 mongodb 整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件及其映射类
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置属性
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:mongodb.properties")
|
||||
public class MongoProperty {
|
||||
|
||||
@Value("${mongo.host}")
|
||||
private String host;
|
||||
@Value("${mongo.port}")
|
||||
private int port;
|
||||
@Value("${mongo.dbname}")
|
||||
private String dbname;
|
||||
@Value("${mongo.connectionsPerHost}")
|
||||
private int connectionsPerHost;
|
||||
@Value("${mongo.threadsAllowedToBlockForConnectionMultiplier}")
|
||||
private int multiplier;
|
||||
@Value("${mongo.connectTimeout}")
|
||||
private int connectTimeout;
|
||||
@Value("${mongo.maxWaitTime}")
|
||||
private int maxWaitTime;
|
||||
@Value("${mongo.socketKeepAlive}")
|
||||
private boolean socketKeepAlive;
|
||||
@Value("${mongo.socketTimeout}")
|
||||
private int socketTimeout;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.*")
|
||||
public class MongoConfig {
|
||||
|
||||
@Bean
|
||||
public MongoDbFactory mongoDbFactory(MongoProperty mongo) {
|
||||
MongoClientOptions options = MongoClientOptions.builder()
|
||||
.threadsAllowedToBlockForConnectionMultiplier(mongo.getMultiplier())
|
||||
.connectionsPerHost(mongo.getConnectionsPerHost())
|
||||
.connectTimeout(mongo.getConnectTimeout())
|
||||
.maxWaitTime(mongo.getMaxWaitTime())
|
||||
.socketTimeout(mongo.getSocketTimeout())
|
||||
.build();
|
||||
MongoClient client = new MongoClient(new ServerAddress(mongo.getHost(), mongo.getPort()), options);
|
||||
return new SimpleMongoDbFactory(client, mongo.getDbname());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) {
|
||||
return new MongoTemplate(mongoDbFactory);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = MongoConfig.class)
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 mongodb(xml配置方式)
|
||||
|
||||
# spring 整合 mongodb(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -10,145 +11,145 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于resources下,项目以单元测试的方式进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mongodb.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入mongodb整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--扫描配置文件-->
|
||||
<context:property-placeholder location="classpath:mongodb.properties"/>
|
||||
|
||||
<!--定义用于访问MongoDB的MongoClient实例-->
|
||||
<mongo:mongo-client host="${mongo.host}" port="${mongo.port}">
|
||||
<mongo:client-options
|
||||
connections-per-host="${mongo.connectionsPerHost}"
|
||||
threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
|
||||
connect-timeout="${mongo.connectTimeout}"
|
||||
max-wait-time="${mongo.maxWaitTime}"
|
||||
socket-keep-alive="${mongo.socketKeepAlive}"
|
||||
socket-timeout="${mongo.socketTimeout}"
|
||||
/>
|
||||
</mongo:mongo-client>
|
||||
|
||||
<!--定义用于连接到数据库的连接工厂-->
|
||||
<mongo:db-factory dbname="${mongo.dbname}" mongo-ref="mongoClient"/>
|
||||
|
||||
<!--实际操作mongodb的template,在代码中注入-->
|
||||
<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
|
||||
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : MongoDB 查询
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:mongodb.xml")
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于 resources 下,项目以单元测试的方式进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mongodb.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 mongodb 整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--扫描配置文件-->
|
||||
<context:property-placeholder location="classpath:mongodb.properties"/>
|
||||
|
||||
<!--定义用于访问 MongoDB 的 MongoClient 实例-->
|
||||
<mongo:mongo-client host="${mongo.host}" port="${mongo.port}">
|
||||
<mongo:client-options
|
||||
connections-per-host="${mongo.connectionsPerHost}"
|
||||
threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
|
||||
connect-timeout="${mongo.connectTimeout}"
|
||||
max-wait-time="${mongo.maxWaitTime}"
|
||||
socket-keep-alive="${mongo.socketKeepAlive}"
|
||||
socket-timeout="${mongo.socketTimeout}"
|
||||
/>
|
||||
</mongo:mongo-client>
|
||||
|
||||
<!--定义用于连接到数据库的连接工厂-->
|
||||
<mongo:db-factory dbname="${mongo.dbname}" mongo-ref="mongoClient"/>
|
||||
|
||||
<!--实际操作 mongodb 的 template,在代码中注入-->
|
||||
<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
|
||||
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : MongoDB 查询
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:mongodb.xml")
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 mybatis(注解方式)
|
||||
|
||||
# spring 整合 mybatis(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目依赖">1.2 项目依赖</a><br/>
|
||||
@@ -12,263 +13,263 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mybatis-annotation.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
除了spring相关依赖外,还需要导入数据库驱动和对应的mybatis依赖包
|
||||
|
||||
```xml
|
||||
<!--jdbc 相关依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3.0</version>
|
||||
</dependency>
|
||||
<!--mybatis 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.4.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、spring 整合 mybatis
|
||||
|
||||
#### 2.1 在resources文件夹下新建数据库配置文件jdbc.properties及其映射类
|
||||
|
||||
```properties
|
||||
# mysql 数据库配置
|
||||
mysql.driverClassName=com.mysql.jdbc.Driver
|
||||
mysql.url=jdbc:mysql://localhost:3306/mysql
|
||||
mysql.username=root
|
||||
mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:mysql.properties")
|
||||
@Data
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Value("${mysql.driverClassName}")
|
||||
private String driverClassName;
|
||||
@Value("${mysql.url}")
|
||||
private String url;
|
||||
@Value("${mysql.username}")
|
||||
private String username;
|
||||
@Value("${mysql.password}")
|
||||
private String password;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 配置数据源和mybatis会话工厂、定义事务管理器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于xml中<tx:annotation-driven/>
|
||||
@ComponentScan(basePackages = {"com.heibaiying.*"})
|
||||
public class DatabaseConfig {
|
||||
|
||||
/* @Autowired
|
||||
* private DataSourceConfig sourceConfig;
|
||||
* 不要采用这种方式注入DataSourceConfig,由于类的加载顺序影响会报空指针异常
|
||||
* 最好的方式是在DriverManagerDataSource构造中采用参数注入
|
||||
*/
|
||||
|
||||
/**
|
||||
* 配置数据源
|
||||
*/
|
||||
@Bean
|
||||
public DriverManagerDataSource dataSource(DataSourceConfig sourceConfig) {
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(sourceConfig.getDriverClassName());
|
||||
dataSource.setUrl(sourceConfig.getUrl());
|
||||
dataSource.setUsername(sourceConfig.getUsername());
|
||||
dataSource.setPassword(sourceConfig.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置mybatis 会话工厂
|
||||
*
|
||||
* @param dataSource 这个参数的名称需要保持和上面方法名一致 才能自动注入,因为
|
||||
* 采用@Bean注解生成的bean 默认采用方法名为名称,当然也可以在使用@Bean时指定name属性
|
||||
*/
|
||||
@Bean
|
||||
public SqlSessionFactoryBean sessionFactoryBean(DriverManagerDataSource dataSource) throws IOException {
|
||||
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
|
||||
sessionFactoryBean.setDataSource(dataSource);
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
sessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:/mappers/**/*.xml"));
|
||||
sessionFactoryBean.setConfigLocation(resolver.getResource("classpath:mybatisConfig.xml"));
|
||||
return sessionFactoryBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置mybatis 会话工厂
|
||||
*/
|
||||
@Bean
|
||||
public MapperScannerConfigurer MapperScannerConfigurer() {
|
||||
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
|
||||
configurer.setSqlSessionFactoryBeanName("sessionFactoryBean");
|
||||
configurer.setBasePackage("com.heibaiying.dao");
|
||||
return configurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义事务管理器
|
||||
*/
|
||||
@Bean
|
||||
public DataSourceTransactionManager transactionManager(DriverManagerDataSource dataSource) {
|
||||
DataSourceTransactionManager manager = new DataSourceTransactionManager();
|
||||
manager.setDataSource(dataSource);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
|
||||
<!-- mybatis 配置文件 -->
|
||||
<configuration>
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
</configuration>
|
||||
|
||||
<!--更多settings配置项可以参考官方文档: <a href="http://www.mybatis.org/mybatis-3/zh/configuration.html"/>-->
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 新建查询接口及其对应的mapper文件
|
||||
|
||||
```java
|
||||
public interface MysqlDao {
|
||||
|
||||
List<Relation> get();
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.MysqlDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Relation">
|
||||
SELECT help_keyword_id AS id,name
|
||||
FROM HELP_KEYWORD
|
||||
WHERE HELP_KEYWORD_ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
```mysql
|
||||
public interface OracleDao {
|
||||
|
||||
List<Flow> queryById(long id);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.OracleDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Flow">
|
||||
select * from APEX_030200.WWV_FLOW_CALS where ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 2.5 新建测试类进行测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {DatabaseConfig.class})
|
||||
public class MysqlDaoTest {
|
||||
|
||||
@Autowired
|
||||
private MysqlDao mysqlDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Relation> relations = mysqlDao.queryById("691");
|
||||
if (relations != null) {
|
||||
for (Relation relation : relations) {
|
||||
System.out.println(relation.getId() + " " + relation.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {DatabaseConfig.class})
|
||||
public class OracleDaoTest {
|
||||
|
||||
@Autowired
|
||||
private OracleDao oracleDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Flow> flows = oracleDao.queryById(217584603977429772L);
|
||||
if (flows != null) {
|
||||
for (Flow flow : flows) {
|
||||
System.out.println(flow.getId() + " " + flow.getPlugId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mybatis-annotation.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
除了 spring 相关依赖外,还需要导入数据库驱动和对应的 mybatis 依赖包
|
||||
|
||||
```xml
|
||||
<!--jdbc 相关依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3.0</version>
|
||||
</dependency>
|
||||
<!--mybatis 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.4.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、spring 整合 mybatis
|
||||
|
||||
#### 2.1 在resources文件夹下新建数据库配置文件jdbc.properties及其映射类
|
||||
|
||||
```properties
|
||||
# mysql 数据库配置
|
||||
mysql.driverClassName=com.mysql.jdbc.Driver
|
||||
mysql.url=jdbc:mysql://localhost:3306/mysql
|
||||
mysql.username=root
|
||||
mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:mysql.properties")
|
||||
@Data
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Value("${mysql.driverClassName}")
|
||||
private String driverClassName;
|
||||
@Value("${mysql.url}")
|
||||
private String url;
|
||||
@Value("${mysql.username}")
|
||||
private String username;
|
||||
@Value("${mysql.password}")
|
||||
private String password;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 配置数据源和mybatis会话工厂、定义事务管理器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于 xml 中<tx:annotation-driven/>
|
||||
@ComponentScan(basePackages = {"com.heibaiying.*"})
|
||||
public class DatabaseConfig {
|
||||
|
||||
/* @Autowired
|
||||
* private DataSourceConfig sourceConfig;
|
||||
* 不要采用这种方式注入 DataSourceConfig,由于类的加载顺序影响会报空指针异常
|
||||
* 最好的方式是在 DriverManagerDataSource 构造中采用参数注入
|
||||
*/
|
||||
|
||||
/**
|
||||
* 配置数据源
|
||||
*/
|
||||
@Bean
|
||||
public DriverManagerDataSource dataSource(DataSourceConfig sourceConfig) {
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(sourceConfig.getDriverClassName());
|
||||
dataSource.setUrl(sourceConfig.getUrl());
|
||||
dataSource.setUsername(sourceConfig.getUsername());
|
||||
dataSource.setPassword(sourceConfig.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置 mybatis 会话工厂
|
||||
*
|
||||
* @param dataSource 这个参数的名称需要保持和上面方法名一致 才能自动注入,因为
|
||||
* 采用@Bean 注解生成的 bean 默认采用方法名为名称,当然也可以在使用@Bean 时指定 name 属性
|
||||
*/
|
||||
@Bean
|
||||
public SqlSessionFactoryBean sessionFactoryBean(DriverManagerDataSource dataSource) throws IOException {
|
||||
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
|
||||
sessionFactoryBean.setDataSource(dataSource);
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
sessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:/mappers/**/*.xml"));
|
||||
sessionFactoryBean.setConfigLocation(resolver.getResource("classpath:mybatisConfig.xml"));
|
||||
return sessionFactoryBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置 mybatis 会话工厂
|
||||
*/
|
||||
@Bean
|
||||
public MapperScannerConfigurer MapperScannerConfigurer() {
|
||||
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
|
||||
configurer.setSqlSessionFactoryBeanName("sessionFactoryBean");
|
||||
configurer.setBasePackage("com.heibaiying.dao");
|
||||
return configurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义事务管理器
|
||||
*/
|
||||
@Bean
|
||||
public DataSourceTransactionManager transactionManager(DriverManagerDataSource dataSource) {
|
||||
DataSourceTransactionManager manager = new DataSourceTransactionManager();
|
||||
manager.setDataSource(dataSource);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
|
||||
<!-- mybatis 配置文件 -->
|
||||
<configuration>
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询 sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
</configuration>
|
||||
|
||||
<!--更多settings配置项可以参考官方文档: <a href="http://www.mybatis.org/mybatis-3/zh/configuration.html"/>-->
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 新建查询接口及其对应的mapper文件
|
||||
|
||||
```java
|
||||
public interface MysqlDao {
|
||||
|
||||
List<Relation> get();
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.MysqlDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Relation">
|
||||
SELECT help_keyword_id AS id,name
|
||||
FROM HELP_KEYWORD
|
||||
WHERE HELP_KEYWORD_ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
```mysql
|
||||
public interface OracleDao {
|
||||
|
||||
List<Flow> queryById(long id);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.OracleDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Flow">
|
||||
select * from APEX_030200.WWV_FLOW_CALS where ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 2.5 新建测试类进行测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {DatabaseConfig.class})
|
||||
public class MysqlDaoTest {
|
||||
|
||||
@Autowired
|
||||
private MysqlDao mysqlDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Relation> relations = mysqlDao.queryById("691");
|
||||
if (relations != null) {
|
||||
for (Relation relation : relations) {
|
||||
System.out.println(relation.getId() + " " + relation.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {DatabaseConfig.class})
|
||||
public class OracleDaoTest {
|
||||
|
||||
@Autowired
|
||||
private OracleDao oracleDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Flow> flows = oracleDao.queryById(217584603977429772L);
|
||||
if (flows != null) {
|
||||
for (Flow flow : flows) {
|
||||
System.out.println(flow.getId() + " " + flow.getPlugId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 mybatis(xml配置方式)
|
||||
|
||||
# spring 整合 mybatis(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目依赖">1.2 项目依赖</a><br/>
|
||||
@@ -12,237 +13,237 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mybatis.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
除了spring相关依赖外,还需要导入数据库驱动和对应的mybatis依赖包
|
||||
|
||||
```xml
|
||||
<!--jdbc 相关依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3.0</version>
|
||||
</dependency>
|
||||
<!--mybatis 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.4.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、spring 整合 mybatis
|
||||
|
||||
#### 2.1 在resources文件夹下新建数据库配置文件jdbc.properties
|
||||
|
||||
```properties
|
||||
# mysql 数据库配置
|
||||
mysql.driverClassName=com.mysql.jdbc.Driver
|
||||
mysql.url=jdbc:mysql://localhost:3306/mysql
|
||||
mysql.username=root
|
||||
mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
|
||||
#### 2.2 配置数据源和mybatis会话工厂、定义事务管理器
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--指定配置文件的位置-->
|
||||
<context:property-placeholder location="classpath:jdbc.properties"/>
|
||||
|
||||
<!--配置数据源-->
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<!--Mysql 配置-->
|
||||
<property name="driverClassName" value="${mysql.driverClassName}"/>
|
||||
<property name="url" value="${mysql.url}"/>
|
||||
<property name="username" value="${mysql.username}"/>
|
||||
<property name="password" value="${mysql.password}"/>
|
||||
<!--Oracle 配置-->
|
||||
<!--<property name="driverClassName" value="${oracle.driverClassName}"/>
|
||||
<property name="url" value="${oracle.url}"/>
|
||||
<property name="username" value="${oracle.username}"/>
|
||||
<property name="password" value="${oracle.password}"/>-->
|
||||
</bean>
|
||||
|
||||
<!--配置 mybatis 会话工厂 -->
|
||||
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<!--指定mapper文件所在的位置-->
|
||||
<property name="mapperLocations" value="classpath*:/mappers/**/*.xml"/>
|
||||
<property name="configLocation" value="classpath:mybatisConfig.xml"/>
|
||||
</bean>
|
||||
|
||||
<!--扫描注册接口 -->
|
||||
<!--作用:从接口的基础包开始递归搜索,并将它们注册为 MapperFactoryBean(只有至少一种方法的接口才会被注册;, 具体类将被忽略)-->
|
||||
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
|
||||
<!--指定会话工厂 -->
|
||||
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
|
||||
<!-- 指定mybatis接口所在的包 -->
|
||||
<property name="basePackage" value="com.heibaiying.dao"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<!--定义事务管理器-->
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- 开启事务注解@Transactional支持 -->
|
||||
<tx:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
|
||||
<!-- mybatis 配置文件 -->
|
||||
<configuration>
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
</configuration>
|
||||
|
||||
<!--更多settings配置项可以参考官方文档: <a href="http://www.mybatis.org/mybatis-3/zh/configuration.html"/>-->
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 新建查询接口及其对应的mapper文件
|
||||
|
||||
```java
|
||||
public interface MysqlDao {
|
||||
|
||||
List<Relation> get();
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.MysqlDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Relation">
|
||||
SELECT help_keyword_id AS id,name
|
||||
FROM HELP_KEYWORD
|
||||
WHERE HELP_KEYWORD_ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
```mysql
|
||||
public interface OracleDao {
|
||||
|
||||
List<Flow> queryById(long id);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.OracleDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Flow">
|
||||
select * from APEX_030200.WWV_FLOW_CALS where ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 2.5 新建测试类进行测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MysqlDaoTest {
|
||||
|
||||
@Autowired
|
||||
private MysqlDao mysqlDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Relation> relations = mysqlDao.get();
|
||||
if (relations != null) {
|
||||
for (Relation relation : relations) {
|
||||
System.out.println(relation.getId() + " " + relation.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class OracleDaoTest {
|
||||
|
||||
/*注入接口时: 如果接口有多个实现类 可以用这个注解指定具体的实现类*/
|
||||
@Qualifier("oracleDaoImpl")
|
||||
@Autowired
|
||||
private OracleDao oracleDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Flow> flows = oracleDao.get();
|
||||
if (flows != null) {
|
||||
for (Flow flow : flows) {
|
||||
System.out.println(flow.getId() + " " + flow.getPlugId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mybatis.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
除了 spring 相关依赖外,还需要导入数据库驱动和对应的 mybatis 依赖包
|
||||
|
||||
```xml
|
||||
<!--jdbc 相关依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3.0</version>
|
||||
</dependency>
|
||||
<!--mybatis 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.4.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、spring 整合 mybatis
|
||||
|
||||
#### 2.1 在resources文件夹下新建数据库配置文件jdbc.properties
|
||||
|
||||
```properties
|
||||
# mysql 数据库配置
|
||||
mysql.driverClassName=com.mysql.jdbc.Driver
|
||||
mysql.url=jdbc:mysql://localhost:3306/mysql
|
||||
mysql.username=root
|
||||
mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
|
||||
#### 2.2 配置数据源和mybatis会话工厂、定义事务管理器
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--指定配置文件的位置-->
|
||||
<context:property-placeholder location="classpath:jdbc.properties"/>
|
||||
|
||||
<!--配置数据源-->
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<!--Mysql 配置-->
|
||||
<property name="driverClassName" value="${mysql.driverClassName}"/>
|
||||
<property name="url" value="${mysql.url}"/>
|
||||
<property name="username" value="${mysql.username}"/>
|
||||
<property name="password" value="${mysql.password}"/>
|
||||
<!--Oracle 配置-->
|
||||
<!--<property name="driverClassName" value="${oracle.driverClassName}"/>
|
||||
<property name="url" value="${oracle.url}"/>
|
||||
<property name="username" value="${oracle.username}"/>
|
||||
<property name="password" value="${oracle.password}"/>-->
|
||||
</bean>
|
||||
|
||||
<!--配置 mybatis 会话工厂 -->
|
||||
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<!--指定 mapper 文件所在的位置-->
|
||||
<property name="mapperLocations" value="classpath*:/mappers/**/*.xml"/>
|
||||
<property name="configLocation" value="classpath:mybatisConfig.xml"/>
|
||||
</bean>
|
||||
|
||||
<!--扫描注册接口 -->
|
||||
<!--作用:从接口的基础包开始递归搜索,并将它们注册为 MapperFactoryBean(只有至少一种方法的接口才会被注册;, 具体类将被忽略)-->
|
||||
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
|
||||
<!--指定会话工厂 -->
|
||||
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
|
||||
<!-- 指定 mybatis 接口所在的包 -->
|
||||
<property name="basePackage" value="com.heibaiying.dao"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<!--定义事务管理器-->
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- 开启事务注解@Transactional 支持 -->
|
||||
<tx:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
|
||||
<!-- mybatis 配置文件 -->
|
||||
<configuration>
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询 sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
</configuration>
|
||||
|
||||
<!--更多settings配置项可以参考官方文档: <a href="http://www.mybatis.org/mybatis-3/zh/configuration.html"/>-->
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 新建查询接口及其对应的mapper文件
|
||||
|
||||
```java
|
||||
public interface MysqlDao {
|
||||
|
||||
List<Relation> get();
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.MysqlDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Relation">
|
||||
SELECT help_keyword_id AS id,name
|
||||
FROM HELP_KEYWORD
|
||||
WHERE HELP_KEYWORD_ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
```mysql
|
||||
public interface OracleDao {
|
||||
|
||||
List<Flow> queryById(long id);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.OracleDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Flow">
|
||||
select * from APEX_030200.WWV_FLOW_CALS where ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 2.5 新建测试类进行测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MysqlDaoTest {
|
||||
|
||||
@Autowired
|
||||
private MysqlDao mysqlDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Relation> relations = mysqlDao.get();
|
||||
if (relations != null) {
|
||||
for (Relation relation : relations) {
|
||||
System.out.println(relation.getId() + " " + relation.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class OracleDaoTest {
|
||||
|
||||
/*注入接口时: 如果接口有多个实现类 可以用这个注解指定具体的实现类*/
|
||||
@Qualifier("oracleDaoImpl")
|
||||
@Autowired
|
||||
private OracleDao oracleDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Flow> flows = oracleDao.get();
|
||||
if (flows != null) {
|
||||
for (Flow flow : flows) {
|
||||
System.out.println(flow.getId() + " " + flow.getPlugId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 rabbitmq(注解方式)
|
||||
|
||||
# spring 整合 rabbitmq(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -16,404 +17,404 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-rabbitmq-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
#### 2.1 基本配置属性及其映射类
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : rabbit 属性配置
|
||||
*/
|
||||
@Data
|
||||
@PropertySource(value = "classpath:rabbitmq.properties")
|
||||
@Configuration
|
||||
public class RabbitProperty {
|
||||
|
||||
|
||||
@Value("${rabbitmq.addresses}")
|
||||
private String addresses;
|
||||
|
||||
@Value("${rabbitmq.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${rabbitmq.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${rabbitmq.virtualhost}")
|
||||
private String virtualhost;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 创建连接工厂、管理器
|
||||
|
||||
```java
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.rabbit.config")
|
||||
public class RabbitBaseConfig {
|
||||
|
||||
/**
|
||||
* 声明连接工厂
|
||||
*/
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory(RabbitProperty property) {
|
||||
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
|
||||
connectionFactory.setAddresses(property.getAddresses());
|
||||
connectionFactory.setUsername(property.getUsername());
|
||||
connectionFactory.setPassword(property.getPassword());
|
||||
connectionFactory.setVirtualHost(property.getVirtualhost());
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
* auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为true。
|
||||
*/
|
||||
@Bean
|
||||
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
rabbitAdmin.setAutoStartup(true);
|
||||
return rabbitAdmin;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
|
||||
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
|
||||
return rabbitTemplate;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试topic这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列1消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列2消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
|
||||
SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于 rabbitmq 的整合提供**简单消息发送**和**对象消费发送**两种情况下的 sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java 中声明了 topic 类型的交换机、持久化队列、及其绑定关系,用于测试说明 topic 交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java 中声明了 direct 类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于 rabbitmq 安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ 实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-rabbitmq-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
#### 2.1 基本配置属性及其映射类
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : rabbit 属性配置
|
||||
*/
|
||||
@Data
|
||||
@PropertySource(value = "classpath:rabbitmq.properties")
|
||||
@Configuration
|
||||
public class RabbitProperty {
|
||||
|
||||
|
||||
@Value("${rabbitmq.addresses}")
|
||||
private String addresses;
|
||||
|
||||
@Value("${rabbitmq.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${rabbitmq.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${rabbitmq.virtualhost}")
|
||||
private String virtualhost;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 创建连接工厂、管理器
|
||||
|
||||
```java
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.rabbit.config")
|
||||
public class RabbitBaseConfig {
|
||||
|
||||
/**
|
||||
* 声明连接工厂
|
||||
*/
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory(RabbitProperty property) {
|
||||
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
|
||||
connectionFactory.setAddresses(property.getAddresses());
|
||||
connectionFactory.setUsername(property.getUsername());
|
||||
connectionFactory.setPassword(property.getPassword());
|
||||
connectionFactory.setVirtualHost(property.getVirtualhost());
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
* auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为 true。
|
||||
*/
|
||||
@Bean
|
||||
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
rabbitAdmin.setAutoStartup(true);
|
||||
return rabbitAdmin;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
|
||||
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
|
||||
return rabbitTemplate;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列 1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列 2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试 topic 这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列 1 消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为 true,则所有 DeliveryTag 小于该 DeliveryTag 的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列 2 消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合 queue1 的要求,只能被 queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合 queue2 的要求,只能被 queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue 收到消息:我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue 收到消息:我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue 收到消息:我的路由键 quick.orange.fox 只符合 queue1 的要求,只能被 queue 1 接收到
|
||||
SecondQueue 收到消息:我的路由键 lazy.brown.fox 只符合 queue2 的要求,只能被 queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的 handleMessage 方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是 handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType 为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的 Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 整合 rabbitmq(xml配置方式)
|
||||
|
||||
# spring 整合 rabbitmq(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -14,382 +15,382 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-rabbitmq.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/rabbit
|
||||
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
|
||||
|
||||
<context:property-placeholder location="rabbitmq.properties"/>
|
||||
|
||||
<!--声明连接工厂-->
|
||||
<rabbit:connection-factory id="connectionFactory"
|
||||
addresses="${rabbitmq.addresses}"
|
||||
username="${rabbitmq.username}"
|
||||
password="${rabbitmq.password}"
|
||||
virtual-host="${rabbitmq.virtualhost}"/>
|
||||
|
||||
<!--创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为true。-->
|
||||
<rabbit:admin connection-factory="connectionFactory" auto-startup="true"/>
|
||||
|
||||
<!--声明 template 的时候需要声明id 不然会抛出异常-->
|
||||
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
|
||||
|
||||
|
||||
<!--可以在xml采用如下方式声明交换机、队列、绑定管理 但是建议使用代码方式声明 方法更加灵活且可以采用链调用-->
|
||||
<rabbit:queue name="remoting.queue"/>
|
||||
|
||||
<rabbit:direct-exchange name="remoting.exchange">
|
||||
<rabbit:bindings>
|
||||
<rabbit:binding queue="remoting.queue" key="remoting.binding"/>
|
||||
</rabbit:bindings>
|
||||
</rabbit:direct-exchange>
|
||||
|
||||
<!--扫描rabbit包 自动声明交换器、队列、绑定关系-->
|
||||
<context:component-scan base-package="com.heibaiying.rabbit"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试topic这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列1消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列2消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
|
||||
SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于 rabbitmq 的整合提供**简单消息发送**和**对象消费发送**两种情况下的 sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java 中声明了 topic 类型的交换机、持久化队列、及其绑定关系,用于测试说明 topic 交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java 中声明了 direct 类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于 rabbitmq 安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ 实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-rabbitmq.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/rabbit
|
||||
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
|
||||
|
||||
<context:property-placeholder location="rabbitmq.properties"/>
|
||||
|
||||
<!--声明连接工厂-->
|
||||
<rabbit:connection-factory id="connectionFactory"
|
||||
addresses="${rabbitmq.addresses}"
|
||||
username="${rabbitmq.username}"
|
||||
password="${rabbitmq.password}"
|
||||
virtual-host="${rabbitmq.virtualhost}"/>
|
||||
|
||||
<!--创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为 true。-->
|
||||
<rabbit:admin connection-factory="connectionFactory" auto-startup="true"/>
|
||||
|
||||
<!--声明 template 的时候需要声明 id 不然会抛出异常-->
|
||||
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
|
||||
|
||||
|
||||
<!--可以在 xml 采用如下方式声明交换机、队列、绑定管理 但是建议使用代码方式声明 方法更加灵活且可以采用链调用-->
|
||||
<rabbit:queue name="remoting.queue"/>
|
||||
|
||||
<rabbit:direct-exchange name="remoting.exchange">
|
||||
<rabbit:bindings>
|
||||
<rabbit:binding queue="remoting.queue" key="remoting.binding"/>
|
||||
</rabbit:bindings>
|
||||
</rabbit:direct-exchange>
|
||||
|
||||
<!--扫描 rabbit 包 自动声明交换器、队列、绑定关系-->
|
||||
<context:component-scan base-package="com.heibaiying.rabbit"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列 1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列 2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试 topic 这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列 1 消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为 true,则所有 DeliveryTag 小于该 DeliveryTag 的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列 2 消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合 queue1 的要求,只能被 queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合 queue2 的要求,只能被 queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue 收到消息:我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue 收到消息:我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue 收到消息:我的路由键 quick.orange.fox 只符合 queue1 的要求,只能被 queue 1 接收到
|
||||
SecondQueue 收到消息:我的路由键 lazy.brown.fox 只符合 queue2 的要求,只能被 queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的 handleMessage 方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是 handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType 为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的 Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
# spring 定时任务(注解方式)
|
||||
|
||||
# spring 定时任务(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -9,193 +10,193 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在ServletConfig.java中,为方便观察项目定时执行的情况,项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-scheduling-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Component
|
||||
public class Task {
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。
|
||||
*/
|
||||
@Scheduled(fixedDelay = 5000, initialDelay = 1000)
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。
|
||||
*/
|
||||
@Scheduled(fixedRate = 5000)
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0/10 * * * * ?")
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* spring 主配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableScheduling //启用Spring的计划任务执行功能
|
||||
@EnableAsync //启用Spring的异步方法执行功能
|
||||
@ComponentScan(basePackages = {"com.heibaiying.task"})
|
||||
public class ServletConfig implements WebMvcConfigurer, AsyncConfigurer, SchedulingConfigurer {
|
||||
|
||||
private ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// 任务执行器线程池配置
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
executor.setCorePoolSize(5);
|
||||
executor.setMaxPoolSize(10);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("MyExecutor-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
// 这个方法可以监听到异步程序发生的错误
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new AsyncUncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
||||
System.out.println(method.getName() + "发生错误:" + ex.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池 不然程序无法完全退出 只能kill才能完全退出
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// 调度程序线程池配置
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.setScheduler(taskExecutor());
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public Executor taskExecutor() {
|
||||
return Executors.newScheduledThreadPool(50);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在 ServletConfig.java 中,为方便观察项目定时执行的情况,项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-scheduling-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Component
|
||||
public class Task {
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。
|
||||
*/
|
||||
@Scheduled(fixedDelay = 5000, initialDelay = 1000)
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA 方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。
|
||||
*/
|
||||
@Scheduled(fixedRate = 5000)
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB 方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0/10 * * * * ?")
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC 方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* spring 主配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableScheduling //启用 Spring 的计划任务执行功能
|
||||
@EnableAsync //启用 Spring 的异步方法执行功能
|
||||
@ComponentScan(basePackages = {"com.heibaiying.task"})
|
||||
public class ServletConfig implements WebMvcConfigurer, AsyncConfigurer, SchedulingConfigurer {
|
||||
|
||||
private ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// 任务执行器线程池配置
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
executor.setCorePoolSize(5);
|
||||
executor.setMaxPoolSize(10);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("MyExecutor-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
// 这个方法可以监听到异步程序发生的错误
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new AsyncUncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
||||
System.out.println(method.getName() + "发生错误:" + ex.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池 不然程序无法完全退出 只能 kill 才能完全退出
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// 调度程序线程池配置
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.setScheduler(taskExecutor());
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public Executor taskExecutor() {
|
||||
return Executors.newScheduledThreadPool(50);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有 methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则 methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每 5 秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池 (如果不配置就使用默认的线程池配置)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring 定时任务(xml配置方式)
|
||||
|
||||
# spring 定时任务(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -9,184 +10,184 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在springApplication.xml中,为方便观察项目定时执行的情况,项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-scheduling.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
package com.heibaiying.task;
|
||||
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
public class Task {
|
||||
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/task
|
||||
http://www.springframework.org/schema/task/spring-task.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!--配置定时任务-->
|
||||
<bean id="task" class="com.heibaiying.task.Task"/>
|
||||
<task:scheduled-tasks scheduler="myScheduler">
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。-->
|
||||
<task:scheduled ref="task" method="methodA" fixed-delay="5000" initial-delay="1000"/>
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。-->
|
||||
<task:scheduled ref="task" method="methodB" fixed-rate="5000"/>
|
||||
<!-- cron 表达式-->
|
||||
<task:scheduled ref="task" method="methodC" cron="0/10 * * * * ?"/>
|
||||
</task:scheduled-tasks>
|
||||
|
||||
<!--定义任务调度器线程池的大小-->
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
|
||||
<!--定义任务执行器的线程池大小、等待队列的容量、和拒绝策略-->
|
||||
<task:executor
|
||||
id="executorWithPoolSizeRange"
|
||||
pool-size="5-25"
|
||||
queue-capacity="100"
|
||||
rejection-policy="CALLER_RUNS"/>
|
||||
<!--拒绝策略默认值为ABORT
|
||||
CALLER_RUNS来限制入栈任务
|
||||
DISCARD删除当前任务
|
||||
DISCARD_OLDEST将任务放在队列的头部。-->
|
||||
|
||||
<!--允许在任何Spring管理的对象上检测@Async和@Scheduled注释, 如果存在,将生成用于异步执行带注释的方法的代理。-->
|
||||
<task:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在 springApplication.xml 中,为方便观察项目定时执行的情况,项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-scheduling.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```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/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
package com.heibaiying.task;
|
||||
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
public class Task {
|
||||
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA 方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB 方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC 方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/task
|
||||
http://www.springframework.org/schema/task/spring-task.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!--配置定时任务-->
|
||||
<bean id="task" class="com.heibaiying.task.Task"/>
|
||||
<task:scheduled-tasks scheduler="myScheduler">
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。-->
|
||||
<task:scheduled ref="task" method="methodA" fixed-delay="5000" initial-delay="1000"/>
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。-->
|
||||
<task:scheduled ref="task" method="methodB" fixed-rate="5000"/>
|
||||
<!-- cron 表达式-->
|
||||
<task:scheduled ref="task" method="methodC" cron="0/10 * * * * ?"/>
|
||||
</task:scheduled-tasks>
|
||||
|
||||
<!--定义任务调度器线程池的大小-->
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
|
||||
<!--定义任务执行器的线程池大小、等待队列的容量、和拒绝策略-->
|
||||
<task:executor
|
||||
id="executorWithPoolSizeRange"
|
||||
pool-size="5-25"
|
||||
queue-capacity="100"
|
||||
rejection-policy="CALLER_RUNS"/>
|
||||
<!--拒绝策略默认值为 ABORT
|
||||
CALLER_RUNS 来限制入栈任务
|
||||
DISCARD 删除当前任务
|
||||
DISCARD_OLDEST 将任务放在队列的头部。-->
|
||||
|
||||
<!--允许在任何 Spring 管理的对象上检测@Async 和@Scheduled 注释, 如果存在,将生成用于异步执行带注释的方法的代理。-->
|
||||
<task:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有 methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则 methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每 5 秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池 (如果不配置就使用默认的线程池配置)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring websocket(注解方式)
|
||||
|
||||
# spring websocket(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -12,204 +13,204 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
|
||||
2. webconfig 包是基础注解的方式配置web,在spring-base-annotation项目中已经讲解过每个类作用;
|
||||
3. CustomHander为消息的自定义处理器;
|
||||
4. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
|
||||
5. webSocketConfig 是websocket 的主要配置类;
|
||||
6. 项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-websocket-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的spring 依赖外,还需要导入webSocket的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的beforeHandshake方法
|
||||
* 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
|
||||
* 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 创建websocket的配置类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :websocket 配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new CustomHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在 session 当中;
|
||||
2. webconfig 包是基础注解的方式配置 web,在 spring-base-annotation 项目中已经讲解过每个类作用;
|
||||
3. CustomHander 为消息的自定义处理器;
|
||||
4. CustomHandershakerInterceptor 为自定义的 websocket 的握手拦截器;
|
||||
5. webSocketConfig 是 websocket 的主要配置类;
|
||||
6. 项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-websocket-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的 spring 依赖外,还需要导入 webSocket 的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的 beforeHandshake 方法
|
||||
* 把 ServerHttpRequest 中 session 中对应的值拷贝到 WebSocketSession 中。
|
||||
* 如果我们没有实现这个方法,我们在最后的 handler 处理中 是拿不到 session 中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中 session 的 username 总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 创建websocket的配置类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :websocket 配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new CustomHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭 ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# spring websocket(xml配置方式)
|
||||
|
||||
# spring websocket(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@@ -12,234 +13,234 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
|
||||
2. CustomHander为消息的自定义处理器;
|
||||
3. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
|
||||
4. 项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-websocket.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的spring 依赖外,还需要导入webSocket的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的beforeHandshake方法
|
||||
* 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
|
||||
* 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 配置websocket
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--使用默认的Servlet来响应静态文件 -->
|
||||
<mvc:default-servlet-handler/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!-- 配置视图解析器 -->
|
||||
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
|
||||
id="internalResourceViewResolver">
|
||||
<!-- 前缀 -->
|
||||
<property name="prefix" value="/WEB-INF/jsp/"/>
|
||||
<!-- 后缀 -->
|
||||
<property name="suffix" value=".jsp"/>
|
||||
</bean>
|
||||
|
||||
<!--配置webSocket-->
|
||||
<bean id="customHandler" class="com.heibaiying.websocket.CustomHandler"/>
|
||||
<websocket:handlers>
|
||||
<!--指定webSocket 地址-->
|
||||
<websocket:mapping path="/socket" handler="customHandler"/>
|
||||
<!--webSocket握手-->
|
||||
<websocket:handshake-interceptors>
|
||||
<bean class="com.heibaiying.websocket.CustomHandshakeInterceptor"/>
|
||||
</websocket:handshake-interceptors>
|
||||
</websocket:handlers>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在 session 当中;
|
||||
2. CustomHander 为消息的自定义处理器;
|
||||
3. CustomHandershakerInterceptor 为自定义的 websocket 的握手拦截器;
|
||||
4. 项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-websocket.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的 spring 依赖外,还需要导入 webSocket 的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的 beforeHandshake 方法
|
||||
* 把 ServerHttpRequest 中 session 中对应的值拷贝到 WebSocketSession 中。
|
||||
* 如果我们没有实现这个方法,我们在最后的 handler 处理中 是拿不到 session 中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中 session 的 username 总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 配置websocket
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--使用默认的 Servlet 来响应静态文件 -->
|
||||
<mvc:default-servlet-handler/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!-- 配置视图解析器 -->
|
||||
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
|
||||
id="internalResourceViewResolver">
|
||||
<!-- 前缀 -->
|
||||
<property name="prefix" value="/WEB-INF/jsp/"/>
|
||||
<!-- 后缀 -->
|
||||
<property name="suffix" value=".jsp"/>
|
||||
</bean>
|
||||
|
||||
<!--配置 webSocket-->
|
||||
<bean id="customHandler" class="com.heibaiying.websocket.CustomHandler"/>
|
||||
<websocket:handlers>
|
||||
<!--指定 webSocket 地址-->
|
||||
<websocket:mapping path="/socket" handler="customHandler"/>
|
||||
<!--webSocket 握手-->
|
||||
<websocket:handshake-interceptors>
|
||||
<bean class="com.heibaiying.websocket.CustomHandshakeInterceptor"/>
|
||||
</websocket:handshake-interceptors>
|
||||
</websocket:handlers>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭 ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
### 1.1 项目搭建
|
||||
|
||||
1.新建maven web工程,并引入相应的依赖
|
||||
1.新建 maven web 工程,并引入相应的依赖
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
@@ -62,9 +62,9 @@
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
2.得益于servlet3.0和spring的支持,我们可以在没有web.xml的情况下完成关于servlet配置。
|
||||
2.得益于 servlet3.0 和 spring 的支持,我们可以在没有 web.xml 的情况下完成关于 servlet 配置。
|
||||
|
||||
新建DispatcherServletInitializer.java文件,这个类的作用相当于我们在xml方式下web.xml中配置的DispatcherServlet
|
||||
新建 DispatcherServletInitializer.java 文件,这个类的作用相当于我们在 xml 方式下 web.xml 中配置的 DispatcherServlet
|
||||
|
||||
```java
|
||||
package com.heibaiying.config;
|
||||
@@ -92,7 +92,7 @@ public class DispatcherServletInitializer extends AbstractAnnotationConfigDispat
|
||||
|
||||
```
|
||||
|
||||
3.新建ServletConfig.java,文件内容如下(这个类相当于我们在xml配置方式中的springApplication.xml)
|
||||
3.新建 ServletConfig.java,文件内容如下 (这个类相当于我们在 xml 配置方式中的 springApplication.xml)
|
||||
|
||||
```java
|
||||
package com.heibaiying.config;
|
||||
@@ -144,7 +144,7 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
|
||||
```
|
||||
|
||||
4.在src 下新建controller用于测试
|
||||
4.在 src 下新建 controller 用于测试
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@@ -171,7 +171,7 @@ public class HelloController {
|
||||
|
||||
```
|
||||
|
||||
5.在WEB-INF 下新建jsp文件夹,新建hello.jsp 文件
|
||||
5.在 WEB-INF 下新建 jsp 文件夹,新建 hello.jsp 文件
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@@ -185,23 +185,23 @@ public class HelloController {
|
||||
</html>
|
||||
```
|
||||
|
||||
6.启动tomcat服务,访问localhost:8080/mvc/hello
|
||||
6.启动 tomcat 服务,访问 localhost:8080/mvc/hello
|
||||
|
||||
### 1.2 相关注解说明
|
||||
|
||||
**1.@Configuration**
|
||||
|
||||
@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
|
||||
@Configuration 用于定义配置类,可替换 xml 配置文件,被注解的类内部包含有一个或多个被@Bean 注解的方法,这些方法将会被 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描,并用于构建 bean 定义,初始化 Spring 容器。
|
||||
|
||||
**2.@EnableWebMvc**
|
||||
|
||||
简单的说就是提供了部分springmvc的功能,例如格式转换和参数绑定。
|
||||
简单的说就是提供了部分 springmvc 的功能,例如格式转换和参数绑定。
|
||||
|
||||
|
||||
|
||||
## 二、配置自定义拦截器
|
||||
|
||||
1.创建自定义拦截器,实现接口HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
|
||||
1.创建自定义拦截器,实现接口 HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
|
||||
|
||||
```java
|
||||
package com.heibaiying.interceptors;
|
||||
@@ -219,17 +219,17 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class MyFirstInterceptor implements HandlerInterceptor {
|
||||
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
System.out.println("进入第一个拦截器preHandle");
|
||||
System.out.println("进入第一个拦截器 preHandle");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入afterCompletion这个方法
|
||||
// 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入 afterCompletion 这个方法
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
System.out.println("进入第一个拦截器postHandle");
|
||||
System.out.println("进入第一个拦截器 postHandle");
|
||||
}
|
||||
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
System.out.println("进入第一个拦截器afterCompletion");
|
||||
System.out.println("进入第一个拦截器 afterCompletion");
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -250,22 +250,22 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class MySecondInterceptor implements HandlerInterceptor {
|
||||
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
System.out.println("进入第二个拦截器preHandle");
|
||||
System.out.println("进入第二个拦截器 preHandle");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
System.out.println("进入第二个拦截器postHandle");
|
||||
System.out.println("进入第二个拦截器 postHandle");
|
||||
}
|
||||
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
System.out.println("进入第二个拦截器afterCompletion");
|
||||
System.out.println("进入第二个拦截器 afterCompletion");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
2.在ServletConfig.java中注册自定义拦截器
|
||||
2.在 ServletConfig.java 中注册自定义拦截器
|
||||
|
||||
```java
|
||||
/**
|
||||
@@ -279,7 +279,7 @@ public class MySecondInterceptor implements HandlerInterceptor {
|
||||
|
||||
3.关于多个拦截器方法执行顺序的说明
|
||||
|
||||
拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
|
||||
拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的 preHandle 方法会先执行,然而它的 postHandle 方法和 afterCompletion 方法却会后执行。
|
||||
|
||||
|
||||
|
||||
@@ -340,7 +340,7 @@ public class NoAuthExceptionResolver implements HandlerExceptionResolver {
|
||||
return new ModelAndView();
|
||||
}
|
||||
|
||||
// 判断是否是Ajax请求
|
||||
// 判断是否是 Ajax 请求
|
||||
private boolean isAjax(HttpServletRequest request) {
|
||||
return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
|
||||
}
|
||||
@@ -348,7 +348,7 @@ public class NoAuthExceptionResolver implements HandlerExceptionResolver {
|
||||
|
||||
```
|
||||
|
||||
3.在ServletConfig.java注册自定义异常处理器
|
||||
3.在 ServletConfig.java 注册自定义异常处理器
|
||||
|
||||
```java
|
||||
/**
|
||||
@@ -359,7 +359,7 @@ public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> re
|
||||
}
|
||||
```
|
||||
|
||||
4.定义测试controller,抛出自定义异常
|
||||
4.定义测试 controller,抛出自定义异常
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@@ -379,7 +379,7 @@ public class HelloController {
|
||||
}
|
||||
```
|
||||
|
||||
注:调用这个controller时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入postHandle这个方法 但一定会进入afterCompletion这个方法
|
||||
注:调用这个 controller 时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入 postHandle 这个方法 但一定会进入 afterCompletion 这个方法
|
||||
|
||||
|
||||
|
||||
@@ -387,7 +387,7 @@ public class HelloController {
|
||||
|
||||
### 4.1 参数绑定
|
||||
|
||||
1.新建Programmer.java
|
||||
1.新建 Programmer.java
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@@ -412,9 +412,9 @@ public class Programmer {
|
||||
|
||||
```
|
||||
|
||||
注:@Data 是lombok包下的注解,用来生成相应的set、get方法,使得类的书写更为简洁。
|
||||
注:@Data 是 lombok 包下的注解,用来生成相应的 set、get 方法,使得类的书写更为简洁。
|
||||
|
||||
2.新建ParamBindController.java 文件
|
||||
2.新建 ParamBindController.java 文件
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@@ -475,7 +475,7 @@ public class ParamBindController {
|
||||
|
||||
```
|
||||
|
||||
3.新建param.jsp 文件
|
||||
3.新建 param.jsp 文件
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@@ -495,11 +495,11 @@ public class ParamBindController {
|
||||
|
||||
```
|
||||
|
||||
4.启动tomcat,用[postman](https://www.getpostman.com/)软件发送请求进行测试
|
||||
4.启动 tomcat,用[postman](https://www.getpostman.com/) 软件发送请求进行测试
|
||||
|
||||
### 4.2 关于日期格式转换的三种方法
|
||||
|
||||
1.如上实例代码所示,在对应的controller中初始化绑定
|
||||
1.如上实例代码所示,在对应的 controller 中初始化绑定
|
||||
|
||||
```java
|
||||
@InitBinder
|
||||
@@ -508,13 +508,13 @@ public class ParamBindController {
|
||||
}
|
||||
```
|
||||
|
||||
2.利用@DateTimeFormat注解,如果是用实体类去接收参数,则在对应的属性上用@DateTimeFormat和@JsonFormat声明
|
||||
2.利用@DateTimeFormat 注解,如果是用实体类去接收参数,则在对应的属性上用@DateTimeFormat 和@JsonFormat 声明
|
||||
|
||||
```java
|
||||
public String param(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday)
|
||||
```
|
||||
|
||||
3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在ServletConfig.java中进行注册
|
||||
3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在 ServletConfig.java 中进行注册
|
||||
|
||||
```java
|
||||
package com.heibaiying.convert;
|
||||
@@ -558,7 +558,7 @@ public void addFormatters(FormatterRegistry registry) {
|
||||
|
||||
## 五、数据校验
|
||||
|
||||
1.spring支持的数据校验是JSR303的标准,需要引入依赖的jar包
|
||||
1.spring 支持的数据校验是 JSR303 的标准,需要引入依赖的 jar 包
|
||||
|
||||
```java
|
||||
<!-- 数据校验依赖包 -->
|
||||
@@ -574,7 +574,7 @@ public void addFormatters(FormatterRegistry registry) {
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2.新建测试ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上bindingResult参数,这个参数中包含了校验的结果
|
||||
2.新建测试 ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上 bindingResult 参数,这个参数中包含了校验的结果
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@@ -620,7 +620,7 @@ public class ParamValidController {
|
||||
|
||||
```
|
||||
|
||||
3.在Programmer.java的对应属性上加上注解约束(支持的注解可以在javax.validation.constraints包中查看)
|
||||
3.在 Programmer.java 的对应属性上加上注解约束 (支持的注解可以在 javax.validation.constraints 包中查看)
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@@ -655,7 +655,7 @@ public class Programmer {
|
||||
|
||||
#### 6.1 文件上传
|
||||
|
||||
1.在ServletConfig.java中进行配置,使之支持文件上传
|
||||
1.在 ServletConfig.java 中进行配置,使之支持文件上传
|
||||
|
||||
```java
|
||||
/**
|
||||
@@ -671,7 +671,7 @@ public CommonsMultipartResolver multipartResolver(){
|
||||
}
|
||||
```
|
||||
|
||||
2.新建测试上传的FileController.java
|
||||
2.新建测试上传的 FileController.java
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@@ -714,7 +714,7 @@ public class FileController {
|
||||
*/
|
||||
@PostMapping("upFile")
|
||||
public String upFile(MultipartFile file, HttpSession session) {
|
||||
//保存在项目根目录下image文件夹下,如果文件夹不存在则创建
|
||||
//保存在项目根目录下 image 文件夹下,如果文件夹不存在则创建
|
||||
FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
|
||||
// success.jsp 就是一个简单的成功页面
|
||||
return "success";
|
||||
@@ -732,7 +732,7 @@ public class FileController {
|
||||
}
|
||||
|
||||
/***
|
||||
* 多文件上传方式2 分别为不同文件指定不同名字
|
||||
* 多文件上传方式 2 分别为不同文件指定不同名字
|
||||
*/
|
||||
@PostMapping("upFiles2")
|
||||
public String upFile(String extendParam,
|
||||
@@ -748,7 +748,7 @@ public class FileController {
|
||||
|
||||
```
|
||||
|
||||
3.其中工具类FileUtil.java代码如下
|
||||
3.其中工具类 FileUtil.java 代码如下
|
||||
|
||||
```java
|
||||
package com.heibaiying.utils;
|
||||
@@ -803,7 +803,7 @@ public class FileUtil {
|
||||
}
|
||||
```
|
||||
|
||||
4.新建用于上传的jsp页面,上传文件时表单必须声明 enctype="multipart/form-data"
|
||||
4.新建用于上传的 jsp 页面,上传文件时表单必须声明 enctype="multipart/form-data"
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@@ -820,13 +820,13 @@ public class FileUtil {
|
||||
</form>
|
||||
|
||||
<form action="${pageContext.request.contextPath }/upFiles" method="post" enctype="multipart/form-data">
|
||||
请选择上传文件(多选):<input name="file" type="file" multiple><br>
|
||||
请选择上传文件 (多选):<input name="file" type="file" multiple><br>
|
||||
<input type="submit" value="点击上传文件">
|
||||
</form>
|
||||
|
||||
<form action="${pageContext.request.contextPath }/upFiles2" method="post" enctype="multipart/form-data">
|
||||
请选择上传文件1:<input name="file1" type="file"><br>
|
||||
请选择上传文件2:<input name="file2" type="file"><br>
|
||||
请选择上传文件 1:<input name="file1" type="file"><br>
|
||||
请选择上传文件 2:<input name="file2" type="file"><br>
|
||||
文件内容额外备注: <input name="extendParam" type="text"><br>
|
||||
<input type="submit" value="点击上传文件">
|
||||
</form>
|
||||
@@ -838,7 +838,7 @@ public class FileUtil {
|
||||
|
||||
#### 6.2 文件下载
|
||||
|
||||
1.在fileController.java中加上方法:
|
||||
1.在 fileController.java 中加上方法:
|
||||
|
||||
```java
|
||||
/***
|
||||
@@ -869,7 +869,7 @@ public class FileUtil {
|
||||
}
|
||||
```
|
||||
|
||||
2.其中fileDownload.jsp 如下:
|
||||
2.其中 fileDownload.jsp 如下:
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@@ -887,7 +887,7 @@ public class FileUtil {
|
||||
|
||||
## 七、Restful风格的请求
|
||||
|
||||
1.新建Pet.java实体类
|
||||
1.新建 Pet.java 实体类
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@@ -896,7 +896,7 @@ import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :测试restful风格的实体类
|
||||
* @description :测试 restful 风格的实体类
|
||||
*/
|
||||
|
||||
@Data
|
||||
@@ -909,13 +909,13 @@ public class Pet {
|
||||
|
||||
```
|
||||
|
||||
2.新建RestfulController.java,用@PathVariable和@ModelAttribute注解进行参数绑定。
|
||||
2.新建 RestfulController.java,用@PathVariable 和@ModelAttribute 注解进行参数绑定。
|
||||
|
||||
注: 在REST中,资源通过URL进行识别和定位。REST中的行为是通过HTTP方法定义的。在进行不同行为时对应HTTP方法和Spring注解分别如下:
|
||||
注: 在 REST 中,资源通过 URL 进行识别和定位。REST 中的行为是通过 HTTP 方法定义的。在进行不同行为时对应 HTTP 方法和 Spring 注解分别如下:
|
||||
|
||||
- 创建资源时:POST(PostMapping)
|
||||
- 读取资源时:GET( @GetMapping)
|
||||
- 更新资源时:PUT或PATCH(PutMapping、PatchMapping)
|
||||
- 更新资源时:PUT 或 PATCH(PutMapping、PatchMapping)
|
||||
- 删除资源时:DELETE(DeleteMapping)
|
||||
|
||||
```java
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
### 1.1 项目搭建
|
||||
|
||||
1.新建maven web工程,并引入相应的依赖
|
||||
1.新建 maven web 工程,并引入相应的依赖
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
@@ -63,7 +63,7 @@
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
2.配置web.xml
|
||||
2.配置 web.xml
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -73,7 +73,7 @@
|
||||
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||
version="3.1">
|
||||
|
||||
<!--配置spring前端控制器-->
|
||||
<!--配置 spring 前端控制器-->
|
||||
<servlet>
|
||||
<servlet-name>springMvc</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
@@ -92,7 +92,7 @@
|
||||
</web-app>
|
||||
```
|
||||
|
||||
3.在resources下新建springApplication.xml文件,文件内容如下:
|
||||
3.在 resources 下新建 springApplication.xml 文件,文件内容如下:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -107,7 +107,7 @@
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--使用默认的Servlet来响应静态文件 详见 1.2 -->
|
||||
<!--使用默认的 Servlet 来响应静态文件 详见 1.2 -->
|
||||
<mvc:default-servlet-handler/>
|
||||
|
||||
<!-- 开启注解驱动 详见 1.2 -->
|
||||
@@ -125,7 +125,7 @@
|
||||
</beans>
|
||||
```
|
||||
|
||||
4.在src 下新建controller用于测试
|
||||
4.在 src 下新建 controller 用于测试
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@@ -152,7 +152,7 @@ public class HelloController {
|
||||
|
||||
```
|
||||
|
||||
5.在WEB-INF 下新建jsp文件夹,新建hello.jsp 文件
|
||||
5.在 WEB-INF 下新建 jsp 文件夹,新建 hello.jsp 文件
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@@ -166,15 +166,15 @@ public class HelloController {
|
||||
</html>
|
||||
```
|
||||
|
||||
6.启动tomcat服务,访问localhost:8080/mvc/hello
|
||||
6.启动 tomcat 服务,访问 localhost:8080/mvc/hello
|
||||
|
||||
### 1.2 相关配置讲解
|
||||
|
||||
**1.\<mvc:default-servlet-handler/>**
|
||||
|
||||
在web.xml配置中,我们将DispatcherServlet的拦截路径设置为“\”,则spring会捕获所有web请求,包括对静态资源的请求,为了正确处理对静态资源的请求,spring提供了两种解决方案:
|
||||
在 web.xml 配置中,我们将 DispatcherServlet 的拦截路径设置为“\”,则 spring 会捕获所有 web 请求,包括对静态资源的请求,为了正确处理对静态资源的请求,spring 提供了两种解决方案:
|
||||
|
||||
- 配置\<mvc:default-servlet-handler/> : 配置<mvc:default-servlet-handler />后,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。
|
||||
- 配置\<mvc:default-servlet-handler/> : 配置<mvc:default-servlet-handler />后,会在 Spring MVC 上下文中定义一个 org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的 URL 进行筛查,如果发现是静态资源的请求,就将该请求转由 Web 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。
|
||||
|
||||
- 配置\<mvc:resources /> :指定静态资源的位置和路径映射:
|
||||
|
||||
@@ -187,13 +187,13 @@ public class HelloController {
|
||||
**2.\<mvc:annotation-driven/>**
|
||||
|
||||
<mvc:annotation-driven /> 会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter
|
||||
两个bean,用以支持@Controllers分发请求。并提供了数据绑定、参数转换、json转换等功能,所以必须加上这个配置。
|
||||
两个 bean,用以支持@Controllers 分发请求。并提供了数据绑定、参数转换、json 转换等功能,所以必须加上这个配置。
|
||||
|
||||
|
||||
|
||||
## 二、配置自定义拦截器
|
||||
|
||||
1.创建自定义拦截器,实现接口HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
|
||||
1.创建自定义拦截器,实现接口 HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
|
||||
|
||||
```java
|
||||
package com.heibaiying.interceptors;
|
||||
@@ -211,17 +211,17 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class MyFirstInterceptor implements HandlerInterceptor {
|
||||
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
System.out.println("进入第一个拦截器preHandle");
|
||||
System.out.println("进入第一个拦截器 preHandle");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入afterCompletion这个方法
|
||||
// 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入 afterCompletion 这个方法
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
System.out.println("进入第一个拦截器postHandle");
|
||||
System.out.println("进入第一个拦截器 postHandle");
|
||||
}
|
||||
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
System.out.println("进入第一个拦截器afterCompletion");
|
||||
System.out.println("进入第一个拦截器 afterCompletion");
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -242,22 +242,22 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class MySecondInterceptor implements HandlerInterceptor {
|
||||
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
System.out.println("进入第二个拦截器preHandle");
|
||||
System.out.println("进入第二个拦截器 preHandle");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
System.out.println("进入第二个拦截器postHandle");
|
||||
System.out.println("进入第二个拦截器 postHandle");
|
||||
}
|
||||
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
System.out.println("进入第二个拦截器afterCompletion");
|
||||
System.out.println("进入第二个拦截器 afterCompletion");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
2.在springApplication.xml中注册自定义拦截器
|
||||
2.在 springApplication.xml 中注册自定义拦截器
|
||||
|
||||
```xml
|
||||
<!--配置拦截器-->
|
||||
@@ -276,7 +276,7 @@ public class MySecondInterceptor implements HandlerInterceptor {
|
||||
|
||||
3.关于多个拦截器方法执行顺序的说明
|
||||
|
||||
拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
|
||||
拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的 preHandle 方法会先执行,然而它的 postHandle 方法和 afterCompletion 方法却会后执行。
|
||||
|
||||
|
||||
|
||||
@@ -337,7 +337,7 @@ public class NoAuthExceptionResolver implements HandlerExceptionResolver {
|
||||
return new ModelAndView();
|
||||
}
|
||||
|
||||
// 判断是否是Ajax请求
|
||||
// 判断是否是 Ajax 请求
|
||||
private boolean isAjax(HttpServletRequest request) {
|
||||
return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
|
||||
}
|
||||
@@ -345,14 +345,14 @@ public class NoAuthExceptionResolver implements HandlerExceptionResolver {
|
||||
|
||||
```
|
||||
|
||||
3.在springApplication.xml注册自定义异常处理器
|
||||
3.在 springApplication.xml 注册自定义异常处理器
|
||||
|
||||
```xml
|
||||
<!--配置全局异常处理器-->
|
||||
<bean class="com.heibaiying.exception.NoAuthExceptionResolver"/>
|
||||
```
|
||||
|
||||
4.定义测试controller,抛出自定义异常
|
||||
4.定义测试 controller,抛出自定义异常
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@@ -372,7 +372,7 @@ public class HelloController {
|
||||
}
|
||||
```
|
||||
|
||||
注:调用这个controller时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入postHandle这个方法 但一定会进入afterCompletion这个方法
|
||||
注:调用这个 controller 时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入 postHandle 这个方法 但一定会进入 afterCompletion 这个方法
|
||||
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ public class HelloController {
|
||||
|
||||
### 4.1 参数绑定
|
||||
|
||||
1.新建Programmer.java
|
||||
1.新建 Programmer.java
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@@ -405,9 +405,9 @@ public class Programmer {
|
||||
|
||||
```
|
||||
|
||||
注:@Data 是lombok包下的注解,用来生成相应的set、get方法,使得类的书写更为简洁。
|
||||
注:@Data 是 lombok 包下的注解,用来生成相应的 set、get 方法,使得类的书写更为简洁。
|
||||
|
||||
2.新建ParamBindController.java 文件
|
||||
2.新建 ParamBindController.java 文件
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@@ -468,7 +468,7 @@ public class ParamBindController {
|
||||
|
||||
```
|
||||
|
||||
3.新建param.jsp 文件
|
||||
3.新建 param.jsp 文件
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@@ -488,11 +488,11 @@ public class ParamBindController {
|
||||
|
||||
```
|
||||
|
||||
4.启动tomcat,用[postman](https://www.getpostman.com/)软件发送请求进行测试
|
||||
4.启动 tomcat,用[postman](https://www.getpostman.com/) 软件发送请求进行测试
|
||||
|
||||
### 4.2 关于日期格式转换的三种方法
|
||||
|
||||
1.如上实例代码所示,在对应的controller中初始化绑定
|
||||
1.如上实例代码所示,在对应的 controller 中初始化绑定
|
||||
|
||||
```java
|
||||
@InitBinder
|
||||
@@ -501,13 +501,13 @@ public class ParamBindController {
|
||||
}
|
||||
```
|
||||
|
||||
2.利用@DateTimeFormat注解,如果是用实体类去接收参数,则在对应的属性上用@DateTimeFormat和@JsonFormat声明
|
||||
2.利用@DateTimeFormat 注解,如果是用实体类去接收参数,则在对应的属性上用@DateTimeFormat 和@JsonFormat 声明
|
||||
|
||||
```java
|
||||
public String param(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday)
|
||||
```
|
||||
|
||||
3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在springApplication.xml中进行注册
|
||||
3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在 springApplication.xml 中进行注册
|
||||
|
||||
```java
|
||||
package com.heibaiying.convert;
|
||||
@@ -553,7 +553,7 @@ springApplication.xml
|
||||
|
||||
## 五、数据校验
|
||||
|
||||
1.spring支持的数据校验是JSR303的标准,需要引入依赖的jar包
|
||||
1.spring 支持的数据校验是 JSR303 的标准,需要引入依赖的 jar 包
|
||||
|
||||
```java
|
||||
<!-- 数据校验依赖包 -->
|
||||
@@ -569,7 +569,7 @@ springApplication.xml
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2.新建测试ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上bindingResult参数,这个参数中包含了校验的结果
|
||||
2.新建测试 ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上 bindingResult 参数,这个参数中包含了校验的结果
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@@ -615,7 +615,7 @@ public class ParamValidController {
|
||||
|
||||
```
|
||||
|
||||
3.在Programmer.java的对应属性上加上注解约束(支持的注解可以在javax.validation.constraints包中查看)
|
||||
3.在 Programmer.java 的对应属性上加上注解约束 (支持的注解可以在 javax.validation.constraints 包中查看)
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@@ -650,7 +650,7 @@ public class Programmer {
|
||||
|
||||
#### 6.1 文件上传
|
||||
|
||||
1.在springApplication.xml中进行配置,使之支持文件上传
|
||||
1.在 springApplication.xml 中进行配置,使之支持文件上传
|
||||
|
||||
```xml
|
||||
<!--配置文件上传-->
|
||||
@@ -663,7 +663,7 @@ public class Programmer {
|
||||
</bean>
|
||||
```
|
||||
|
||||
2.新建测试上传的FileController.java
|
||||
2.新建测试上传的 FileController.java
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@@ -706,7 +706,7 @@ public class FileController {
|
||||
*/
|
||||
@PostMapping("upFile")
|
||||
public String upFile(MultipartFile file, HttpSession session) {
|
||||
//保存在项目根目录下image文件夹下,如果文件夹不存在则创建
|
||||
//保存在项目根目录下 image 文件夹下,如果文件夹不存在则创建
|
||||
FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
|
||||
// success.jsp 就是一个简单的成功页面
|
||||
return "success";
|
||||
@@ -724,7 +724,7 @@ public class FileController {
|
||||
}
|
||||
|
||||
/***
|
||||
* 多文件上传方式2 分别为不同文件指定不同名字
|
||||
* 多文件上传方式 2 分别为不同文件指定不同名字
|
||||
*/
|
||||
@PostMapping("upFiles2")
|
||||
public String upFile(String extendParam,
|
||||
@@ -740,7 +740,7 @@ public class FileController {
|
||||
|
||||
```
|
||||
|
||||
3.其中工具类FileUtil.java代码如下
|
||||
3.其中工具类 FileUtil.java 代码如下
|
||||
|
||||
```java
|
||||
package com.heibaiying.utils;
|
||||
@@ -795,7 +795,7 @@ public class FileUtil {
|
||||
}
|
||||
```
|
||||
|
||||
4.新建用于上传的jsp页面,上传文件时表单必须声明 enctype="multipart/form-data"
|
||||
4.新建用于上传的 jsp 页面,上传文件时表单必须声明 enctype="multipart/form-data"
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@@ -812,13 +812,13 @@ public class FileUtil {
|
||||
</form>
|
||||
|
||||
<form action="${pageContext.request.contextPath }/upFiles" method="post" enctype="multipart/form-data">
|
||||
请选择上传文件(多选):<input name="file" type="file" multiple><br>
|
||||
请选择上传文件 (多选):<input name="file" type="file" multiple><br>
|
||||
<input type="submit" value="点击上传文件">
|
||||
</form>
|
||||
|
||||
<form action="${pageContext.request.contextPath }/upFiles2" method="post" enctype="multipart/form-data">
|
||||
请选择上传文件1:<input name="file1" type="file"><br>
|
||||
请选择上传文件2:<input name="file2" type="file"><br>
|
||||
请选择上传文件 1:<input name="file1" type="file"><br>
|
||||
请选择上传文件 2:<input name="file2" type="file"><br>
|
||||
文件内容额外备注: <input name="extendParam" type="text"><br>
|
||||
<input type="submit" value="点击上传文件">
|
||||
</form>
|
||||
@@ -830,7 +830,7 @@ public class FileUtil {
|
||||
|
||||
#### 6.2 文件下载
|
||||
|
||||
1.在fileController.java中加上方法:
|
||||
1.在 fileController.java 中加上方法:
|
||||
|
||||
```java
|
||||
/***
|
||||
@@ -861,7 +861,7 @@ public class FileUtil {
|
||||
}
|
||||
```
|
||||
|
||||
2.其中fileDownload.jsp 如下:
|
||||
2.其中 fileDownload.jsp 如下:
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@@ -879,7 +879,7 @@ public class FileUtil {
|
||||
|
||||
## 七、Restful风格的请求
|
||||
|
||||
1.新建Pet.java实体类
|
||||
1.新建 Pet.java 实体类
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@@ -888,7 +888,7 @@ import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :测试restful风格的实体类
|
||||
* @description :测试 restful 风格的实体类
|
||||
*/
|
||||
|
||||
@Data
|
||||
@@ -901,13 +901,13 @@ public class Pet {
|
||||
|
||||
```
|
||||
|
||||
2.新建RestfulController.java,用@PathVariable和@ModelAttribute注解进行参数绑定。
|
||||
2.新建 RestfulController.java,用@PathVariable 和@ModelAttribute 注解进行参数绑定。
|
||||
|
||||
注: 在REST中,资源通过URL进行识别和定位。REST中的行为是通过HTTP方法定义的。在进行不同行为时对应HTTP方法和Spring注解分别如下:
|
||||
注: 在 REST 中,资源通过 URL 进行识别和定位。REST 中的行为是通过 HTTP 方法定义的。在进行不同行为时对应 HTTP 方法和 Spring 注解分别如下:
|
||||
|
||||
- 创建资源时:POST(PostMapping)
|
||||
- 读取资源时:GET( @GetMapping)
|
||||
- 更新资源时:PUT或PATCH(PutMapping、PatchMapping)
|
||||
- 更新资源时:PUT 或 PATCH(PutMapping、PatchMapping)
|
||||
- 删除资源时:DELETE(DeleteMapping)
|
||||
|
||||
```java
|
||||
|
||||
Reference in New Issue
Block a user