diff --git a/README.md b/README.md
index 357d076..53b83b1 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,6 @@ spring-cloud:Finchley.SR2
| [spring-websocket](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-websocket)
[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) |
| [spring-mail](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-email)
[spring-mail-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-email-annotation) | spring 普通文本邮件、附件邮件、模板邮件 | [Spring Email](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/integration.html#mail) |
| [spring-scheduling](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-scheduling)
[spring-scheduling-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-scheduling-annotation) | spring 定时任务 | [Task Execution and Scheduling](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/integration.html#scheduling) |
-| spring-kafka
spring-kafka-annotation | spring 整合 kafka | [Spring for Apache Kafka](https://docs.spring.io/spring-kafka/docs/2.2.2.RELEASE/reference/html/) |
@@ -86,4 +85,5 @@ spring-cloud:Finchley.SR2
代码涉及到的相关参考资料放在了仓库的referenced documents 目录下,文件清单如下:
- Servlet3.1规范(最终版).pdf
+- Thymeleaf中⽂参考⼿册.pdf
diff --git a/pictures/aop执行顺序.png b/pictures/aop执行顺序.png
new file mode 100644
index 0000000..0597061
Binary files /dev/null and b/pictures/aop执行顺序.png differ
diff --git a/pictures/dubbo-ano-common.png b/pictures/dubbo-ano-common.png
new file mode 100644
index 0000000..6863f88
Binary files /dev/null and b/pictures/dubbo-ano-common.png differ
diff --git a/pictures/dubbo-ano-consumer.png b/pictures/dubbo-ano-consumer.png
new file mode 100644
index 0000000..05d3e05
Binary files /dev/null and b/pictures/dubbo-ano-consumer.png differ
diff --git a/pictures/dubbo-ano-provider.png b/pictures/dubbo-ano-provider.png
new file mode 100644
index 0000000..12878bc
Binary files /dev/null and b/pictures/dubbo-ano-provider.png differ
diff --git a/pictures/dubbo-common.png b/pictures/dubbo-common.png
new file mode 100644
index 0000000..992e798
Binary files /dev/null and b/pictures/dubbo-common.png differ
diff --git a/pictures/dubbo-consumer.png b/pictures/dubbo-consumer.png
new file mode 100644
index 0000000..31e9ff9
Binary files /dev/null and b/pictures/dubbo-consumer.png differ
diff --git a/pictures/dubbo-provider.png b/pictures/dubbo-provider.png
new file mode 100644
index 0000000..25c57ef
Binary files /dev/null and b/pictures/dubbo-provider.png differ
diff --git a/pictures/spring-aop-annotation.png b/pictures/spring-aop-annotation.png
new file mode 100644
index 0000000..189d0c1
Binary files /dev/null and b/pictures/spring-aop-annotation.png differ
diff --git a/pictures/spring-aop.png b/pictures/spring-aop.png
new file mode 100644
index 0000000..3e06667
Binary files /dev/null and b/pictures/spring-aop.png differ
diff --git a/pictures/spring-dubbo-annotation.png b/pictures/spring-dubbo-annotation.png
new file mode 100644
index 0000000..6ad520d
Binary files /dev/null and b/pictures/spring-dubbo-annotation.png differ
diff --git a/pictures/spring-dubbo.png b/pictures/spring-dubbo.png
new file mode 100644
index 0000000..83a4a75
Binary files /dev/null and b/pictures/spring-dubbo.png differ
diff --git a/pictures/spring-email-annotation.png b/pictures/spring-email-annotation.png
new file mode 100644
index 0000000..fdc7e4f
Binary files /dev/null and b/pictures/spring-email-annotation.png differ
diff --git a/pictures/spring-email.png b/pictures/spring-email.png
new file mode 100644
index 0000000..b646084
Binary files /dev/null and b/pictures/spring-email.png differ
diff --git a/pictures/spring-memcached-annotation.png b/pictures/spring-memcached-annotation.png
new file mode 100644
index 0000000..36c3859
Binary files /dev/null and b/pictures/spring-memcached-annotation.png differ
diff --git a/pictures/spring-memcached.png b/pictures/spring-memcached.png
new file mode 100644
index 0000000..667fae2
Binary files /dev/null and b/pictures/spring-memcached.png differ
diff --git a/pictures/spring-mongodb-annotation.png b/pictures/spring-mongodb-annotation.png
new file mode 100644
index 0000000..7394c90
Binary files /dev/null and b/pictures/spring-mongodb-annotation.png differ
diff --git a/pictures/spring-mongodb.png b/pictures/spring-mongodb.png
new file mode 100644
index 0000000..57012a9
Binary files /dev/null and b/pictures/spring-mongodb.png differ
diff --git a/pictures/spring-rabbitmq-annotation.png b/pictures/spring-rabbitmq-annotation.png
new file mode 100644
index 0000000..0e87d7e
Binary files /dev/null and b/pictures/spring-rabbitmq-annotation.png differ
diff --git a/pictures/spring-rabbitmq.png b/pictures/spring-rabbitmq.png
new file mode 100644
index 0000000..5d8c092
Binary files /dev/null and b/pictures/spring-rabbitmq.png differ
diff --git a/pictures/spring+redis+ano项目目录结构.png b/pictures/spring-redis-annotation.png
similarity index 100%
rename from pictures/spring+redis+ano项目目录结构.png
rename to pictures/spring-redis-annotation.png
diff --git a/pictures/spring+redis项目目录结构.png b/pictures/spring-redis.png
similarity index 100%
rename from pictures/spring+redis项目目录结构.png
rename to pictures/spring-redis.png
diff --git a/pictures/spring-scheduling-annotation.png b/pictures/spring-scheduling-annotation.png
new file mode 100644
index 0000000..3c6b0f1
Binary files /dev/null and b/pictures/spring-scheduling-annotation.png differ
diff --git a/pictures/spring-scheduling.png b/pictures/spring-scheduling.png
new file mode 100644
index 0000000..18ff1d7
Binary files /dev/null and b/pictures/spring-scheduling.png differ
diff --git a/pictures/spring-websocket-annotation.png b/pictures/spring-websocket-annotation.png
new file mode 100644
index 0000000..613b7de
Binary files /dev/null and b/pictures/spring-websocket-annotation.png differ
diff --git a/pictures/spring-websocket.png b/pictures/spring-websocket.png
new file mode 100644
index 0000000..f685926
Binary files /dev/null and b/pictures/spring-websocket.png differ
diff --git a/spring/spring-aop-annotation/README.md b/spring/spring-aop-annotation/README.md
new file mode 100644
index 0000000..43778a6
--- /dev/null
+++ b/spring/spring-aop-annotation/README.md
@@ -0,0 +1,169 @@
+# spring AOP(注解方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 切面配置位于com.heibaiying.config下AopConfig.java文件;
+2. 自定义切面位于advice下,其中CustomAdvice是标准的自定义切面,FirstAdvice和SecondAdvice用于测试多切面共同作用于同一个被切入点时的执行顺序;
+3. OrderService是待切入方法。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入aop依赖包
+
+```xml
+
+
+ org.springframework
+ spring-aop
+ ${spring-base-version}
+
+```
+
+
+
+## 二、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注解支持 等价于
+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)反而会延后执行,类似于同心圆原理。
+
+ 
\ No newline at end of file
diff --git a/spring/spring-aop-annotation/src/main/java/com/heibaiying/advice/CustomAdvice.java b/spring/spring-aop-annotation/src/main/java/com/heibaiying/advice/CustomAdvice.java
index eaa002e..68e323d 100644
--- a/spring/spring-aop-annotation/src/main/java/com/heibaiying/advice/CustomAdvice.java
+++ b/spring/spring-aop-annotation/src/main/java/com/heibaiying/advice/CustomAdvice.java
@@ -34,6 +34,7 @@ public class CustomAdvice {
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
}
+ // returning 参数用于指定返回结果与哪一个参数绑定
@AfterReturning(pointcut = "pointCut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("后置返回通知结果" + result);
@@ -48,6 +49,7 @@ public class CustomAdvice {
return proceed;
}
+ // throwing 参数用于指定抛出的异常与哪一个参数绑定
@AfterThrowing(pointcut = "pointCut()", throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
System.err.println("后置异常通知:" + exception);
diff --git a/spring/spring-aop-annotation/src/test/java/com/heibaiying/aop/AopTest.java b/spring/spring-aop-annotation/src/test/java/com/heibaiying/aop/AopTest.java
index 0af02e2..16cd204 100644
--- a/spring/spring-aop-annotation/src/test/java/com/heibaiying/aop/AopTest.java
+++ b/spring/spring-aop-annotation/src/test/java/com/heibaiying/aop/AopTest.java
@@ -5,13 +5,11 @@ import com.heibaiying.service.OrderService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author : heibaiying
- * @description : 关于多个切面在同一个切入点上执行顺序的例子 可以在spring-aop-annotation 中查看
*/
@RunWith(SpringRunner.class)
diff --git a/spring/spring-aop/README.md b/spring/spring-aop/README.md
new file mode 100644
index 0000000..3680b9c
--- /dev/null
+++ b/spring/spring-aop/README.md
@@ -0,0 +1,162 @@
+# spring AOP(xml配置方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+切面配置位于resources下的aop.xml文件,其中CustomAdvice是自定义切面类,OrderService是待切入的方法。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入aop依赖包
+
+```xml
+
+
+ org.springframework
+ spring-aop
+ ${spring-base-version}
+
+```
+
+
+
+## 二、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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 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);
+ }
+}
+```
+
+
diff --git a/spring/spring-dubbo-annotation/README.md b/spring/spring-dubbo-annotation/README.md
new file mode 100644
index 0000000..bf9d8fb
--- /dev/null
+++ b/spring/spring-dubbo-annotation/README.md
@@ -0,0 +1,327 @@
+# spring 整合 dubbo(注解方式)
+
+## 一、 项目结构说明
+
+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)
+
+
+
+
+
+## 二、项目依赖
+
+**在父工程的项目中同一导入依赖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
+
+com.alibaba
+dubbo
+2.6.2
+
+
+ org.apache.curator
+ curator-framework
+ 4.0.0
+
+
+ org.apache.zookeeper
+ zookeeper
+
+
+
+
+ org.apache.zookeeper
+ zookeeper
+ 3.4.13
+
+```
+
+
+
+## 三、公共模块(dubbo-ano-common)
+
+- api 下为公共的调用接口;
+- bean 下为公共的实体类。
+
+
+
+## 四、 服务提供者(dubbo-ano-provider)
+
+
+
+#### 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 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 queryAllProducts() {
+ return productList;
+ }
+}
+```
+
+####
+
+## 五、服务消费者(dubbo-ano-consumer)
+
+
+
+#### 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 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
+
+
+
+ spring-dubbo-annotation
+ com.heibaiying
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ dubbo-ano-consumer
+
+
+
+ com.heibaiying
+ dubbo-ano-common
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+```
+
+provider中 pom.xml如下
+
+```xml
+
+
+
+ spring-dubbo-annotation
+ com.heibaiying
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ dubbo-ano-provider
+
+
+
+ com.heibaiying
+ dubbo-ano-common
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+```
+
+## 七、关于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
+```
\ No newline at end of file
diff --git a/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/config/DispatcherServletInitializer.java b/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/config/DispatcherServletInitializer.java
index 229e828..8323bf4 100644
--- a/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/config/DispatcherServletInitializer.java
+++ b/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/config/DispatcherServletInitializer.java
@@ -4,7 +4,7 @@ import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatche
/**
* @author : heibaiying
- * @description : 前端过滤器配置
+ * @description : 前端控制器配置
*/
public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
diff --git a/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/config/dubbo/DubboConfiguration.java b/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/config/dubbo/DubboConfiguration.java
index 4185405..59f3533 100644
--- a/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/config/dubbo/DubboConfiguration.java
+++ b/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/config/dubbo/DubboConfiguration.java
@@ -14,6 +14,9 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class DubboConfiguration {
+ /**
+ * 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
+ */
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
@@ -21,6 +24,10 @@ public class DubboConfiguration {
return applicationConfig;
}
+ /**
+ * 设置调用服务超时时间
+ * 关闭所有服务的启动时检查
+ */
@Bean
public ConsumerConfig consumerConfig() {
ConsumerConfig consumerConfig = new ConsumerConfig();
@@ -29,6 +36,9 @@ public class DubboConfiguration {
return consumerConfig;
}
+ /**
+ * 使用zookeeper注册中心暴露发现服务地址
+ */
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
@@ -37,11 +47,4 @@ public class DubboConfiguration {
return registryConfig;
}
- @Bean
- public ProtocolConfig protocolConfig() {
- ProtocolConfig protocolConfig = new ProtocolConfig();
- protocolConfig.setName("dubbo");
- protocolConfig.setPort(20880);
- return protocolConfig;
- }
}
diff --git a/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/controller/SellController.java b/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/controller/SellController.java
index 2444fa0..6f70b95 100644
--- a/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/controller/SellController.java
+++ b/spring/spring-dubbo-annotation/dubbo-ano-consumer/src/main/java/com/heibaiying/controller/SellController.java
@@ -12,7 +12,7 @@ import java.util.List;
/**
* @author : heibaiying
- * @description :
+ * @description :消费服务
*/
@Controller
@RequestMapping("sell")
diff --git a/spring/spring-dubbo-annotation/dubbo-ano-provider/src/main/java/com/heibaiying/config/dubbo/DubboConfiguration.java b/spring/spring-dubbo-annotation/dubbo-ano-provider/src/main/java/com/heibaiying/config/dubbo/DubboConfiguration.java
index 1d295f7..3c478e4 100644
--- a/spring/spring-dubbo-annotation/dubbo-ano-provider/src/main/java/com/heibaiying/config/dubbo/DubboConfiguration.java
+++ b/spring/spring-dubbo-annotation/dubbo-ano-provider/src/main/java/com/heibaiying/config/dubbo/DubboConfiguration.java
@@ -1,6 +1,7 @@
package com.heibaiying.config.dubbo;
import com.alibaba.dubbo.config.ApplicationConfig;
+import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -12,6 +13,9 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class DubboConfiguration {
+ /**
+ * 提供方应用信息,用于计算依赖关系
+ */
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
@@ -19,6 +23,9 @@ public class DubboConfiguration {
return applicationConfig;
}
+ /**
+ * 使用zookeeper注册中心暴露服务地址
+ */
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
@@ -26,4 +33,15 @@ public class DubboConfiguration {
registryConfig.setClient("curator");
return registryConfig;
}
+
+ /**
+ * 用dubbo协议在20880端口暴露服务
+ */
+ @Bean
+ public ProtocolConfig protocolConfig() {
+ ProtocolConfig protocolConfig = new ProtocolConfig();
+ protocolConfig.setName("dubbo");
+ protocolConfig.setPort(20880);
+ return protocolConfig;
+ }
}
diff --git a/spring/spring-dubbo/README.md b/spring/spring-dubbo/README.md
new file mode 100644
index 0000000..52b799b
--- /dev/null
+++ b/spring/spring-dubbo/README.md
@@ -0,0 +1,279 @@
+# spring 整合 dubbo(xml配置方式)
+
+## 一、 项目结构说明
+
+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)
+
+
+
+
+
+## 二、项目依赖
+
+**在父工程的项目中同一导入依赖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
+
+com.alibaba
+dubbo
+2.6.2
+
+
+ org.apache.curator
+ curator-framework
+ 4.0.0
+
+
+ org.apache.zookeeper
+ zookeeper
+
+
+
+
+ org.apache.zookeeper
+ zookeeper
+ 3.4.13
+
+```
+
+
+
+## 三、公共模块(dubbo-common)
+
+- api 下为公共的调用接口;
+- bean 下为公共的实体类。
+
+
+
+## 四、 服务提供者(dubbo-provider)
+
+
+
+#### 4.1 productService是服务的提供者( 商品数据用模拟数据展示)
+
+注:这里实现的接口IProductService来源于公共模块
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 产品提供接口实现类
+ */
+@Service
+public class ProductService implements IProductService {
+
+ private static List 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 queryAllProducts() {
+ return productList;
+ }
+}
+```
+
+#### 4.2 在dubbo.xml暴露服务
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## 五、服务消费者(dubbo-consumer)
+
+
+
+#### 1.在dubbo.xml调用远程的服务
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.消费服务
+
+```java
+@Controller
+@RequestMapping("sell")
+public class SellController {
+
+ @Autowired
+ private IProductService productService;
+
+ @RequestMapping
+ public String productList(Model model) {
+ List 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
+
+
+
+ spring-dubbo
+ com.heibaiying
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ dubbo-consumer
+
+
+
+ com.heibaiying
+ dubbo-common
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+```
+
+provider中 pom.xml如下
+
+```xml
+
+
+
+ spring-dubbo
+ com.heibaiying
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ dubbo-provider
+
+
+
+ com.heibaiying
+ dubbo-common
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+```
+
+## 七、关于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
+```
\ No newline at end of file
diff --git a/spring/spring-email-annotation/README.md b/spring/spring-email-annotation/README.md
new file mode 100644
index 0000000..2e0cfc9
--- /dev/null
+++ b/spring/spring-email-annotation/README.md
@@ -0,0 +1,287 @@
+# spring 邮件发送(xml配置方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 邮件发送配置类为com.heibaiying.config下EmailConfig.java;
+2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
+3. 项目以单元测试的方法进行测试,测试类为SendEmail。
+
+
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
+
+```xml
+
+
+ org.springframework
+ spring-context-support
+ ${spring-base-version}
+
+
+
+
+ com.ibeetl
+ beetl
+ 2.9.7
+
+```
+
+
+
+## 二、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 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 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("
", 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
+
+
+
+
+
+
+ 邮件主题: ${subject}
+ ${content}
+
+
+```
+
+
+
+#### 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 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!");
+ }
+}
+```
diff --git a/spring/spring-email-annotation/src/main/java/com/heibaiying/config/EmailConfig.java b/spring/spring-email-annotation/src/main/java/com/heibaiying/config/EmailConfig.java
index cdf5bec..1eede89 100644
--- a/spring/spring-email-annotation/src/main/java/com/heibaiying/config/EmailConfig.java
+++ b/spring/spring-email-annotation/src/main/java/com/heibaiying/config/EmailConfig.java
@@ -11,7 +11,7 @@ import java.io.IOException;
/**
* @author : heibaiying
- * @description :
+ * @description : 邮件发送配置类
*/
@Configuration
diff --git a/spring/spring-email/README.md b/spring/spring-email/README.md
new file mode 100644
index 0000000..1eff4b8
--- /dev/null
+++ b/spring/spring-email/README.md
@@ -0,0 +1,298 @@
+# spring 邮件发送(xml配置方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 邮件发送配置文件为springApplication.xml;
+2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
+3. 项目以单元测试的方法进行测试,测试类为SendEmail。
+
+
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
+
+```xml
+
+
+ org.springframework
+ spring-context-support
+ ${spring-base-version}
+
+
+
+
+ com.ibeetl
+ beetl
+ 2.9.7
+
+```
+
+
+
+## 二、spring email
+
+#### 2.1 邮件发送配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 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 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 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("
", 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
+
+
+
+
+
+
+ 邮件主题: ${subject}
+ ${content}
+
+
+```
+
+
+
+#### 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 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!");
+ }
+}
+
+```
diff --git a/spring/spring-email/src/main/java/com/heibaiying/email/SpringMail.java b/spring/spring-email/src/main/java/com/heibaiying/email/SpringMail.java
index 31229ca..74d5f84 100644
--- a/spring/spring-email/src/main/java/com/heibaiying/email/SpringMail.java
+++ b/spring/spring-email/src/main/java/com/heibaiying/email/SpringMail.java
@@ -17,7 +17,7 @@ import java.util.Map;
/**
* @author : heibaiying
- * @description :
+ * @description : 邮件发送基本类
*/
@Component
public class SpringMail {
diff --git a/spring/spring-email/src/main/resources/springApplication.xml b/spring/spring-email/src/main/resources/springApplication.xml
index cf5b2d6..5a12d3e 100644
--- a/spring/spring-email/src/main/resources/springApplication.xml
+++ b/spring/spring-email/src/main/resources/springApplication.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/spring/spring-email/src/test/java/com/heibaiying/SendEmail.java b/spring/spring-email/src/test/java/com/heibaiying/SendEmail.java
index da654cd..2e96161 100644
--- a/spring/spring-email/src/test/java/com/heibaiying/SendEmail.java
+++ b/spring/spring-email/src/test/java/com/heibaiying/SendEmail.java
@@ -29,13 +29,19 @@ public class SendEmail {
// 接收方邮箱地址
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 fileMap = new HashMap<>();
@@ -45,12 +51,18 @@ public class SendEmail {
, "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,
diff --git a/spring/spring-memcached-annotation/README.md b/spring/spring-memcached-annotation/README.md
new file mode 100644
index 0000000..5254836
--- /dev/null
+++ b/spring/spring-memcached-annotation/README.md
@@ -0,0 +1,150 @@
+# spring 整合 mecached(注解方式)
+
+## 一、说明
+
+### 1.1 XMemcached客户端说明
+
+XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
+
+### 1.2 项目结构说明
+
+1. memcached的整合配置位于com.heibaiying.config文件夹下。
+
+
+
+### 1.3 依赖说明
+
+除了spring的基本依赖外,需要导入xmemcached依赖包
+
+```xml
+
+
+ com.googlecode.xmemcached
+ xmemcached
+ 2.4.5
+
+```
+
+
+
+## 二、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 addressList = new ArrayList();
+ 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 | |
\ No newline at end of file
diff --git a/spring/spring-memcached/README.md b/spring/spring-memcached/README.md
new file mode 100644
index 0000000..0615160
--- /dev/null
+++ b/spring/spring-memcached/README.md
@@ -0,0 +1,191 @@
+# spring 整合 mecached(xml配置方式)
+
+## 一、说明
+
+### 1.1 XMemcached客户端说明
+
+XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
+
+### 1.2 项目结构说明
+
+1. memcached的整合配置位于resources下的memcached文件夹下,其中集群配置用cluster开头。所有配置按照需要在springApplication.xml用import导入。
+2. 实体类Programmer.java用于测试memcached序列化与反序列化
+
+
+
+**springapplication.xml文件:**
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+### 1.3 依赖说明
+
+除了spring的基本依赖外,需要导入xmemcached依赖包
+
+```xml
+
+
+ com.googlecode.xmemcached
+ xmemcached
+ 2.4.5
+
+```
+
+
+
+## 二、spring 整合 memcached
+
+#### 2.1 单机配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.2 集群配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 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 | |
\ No newline at end of file
diff --git a/spring/spring-mongodb-annotation/README.md b/spring/spring-mongodb-annotation/README.md
new file mode 100644
index 0000000..aec50c7
--- /dev/null
+++ b/spring/spring-mongodb-annotation/README.md
@@ -0,0 +1,161 @@
+# spring 整合 mongodb(注解方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+配置文件位于com.heibaiying.config下,项目以单元测试的方式进行测试。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入mongodb整合依赖包
+
+```xml
+
+
+ org.springframework.data
+ spring-data-mongodb
+ 2.1.3.RELEASE
+
+```
+
+
+
+## 二、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 programmers = new ArrayList();
+ // 批量插入
+ 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());
+ }
+}
+```
diff --git a/spring/spring-mongodb/README.md b/spring/spring-mongodb/README.md
new file mode 100644
index 0000000..7f5d03d
--- /dev/null
+++ b/spring/spring-mongodb/README.md
@@ -0,0 +1,144 @@
+# spring 整合 mongodb(xml配置方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+配置文件位于resources下,项目以单元测试的方式进行测试。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入mongodb整合依赖包
+
+```xml
+
+
+ org.springframework.data
+ spring-data-mongodb
+ 2.1.3.RELEASE
+
+```
+
+
+
+## 二、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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 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 programmers = new ArrayList();
+ // 批量插入
+ 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());
+ }
+
+}
+```
diff --git a/spring/spring-mongodb/src/main/resources/mongodb.xml b/spring/spring-mongodb/src/main/resources/mongodb.xml
index 7db314b..a1f86d8 100644
--- a/spring/spring-mongodb/src/main/resources/mongodb.xml
+++ b/spring/spring-mongodb/src/main/resources/mongodb.xml
@@ -10,9 +10,10 @@
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
-
+
+
+
+
diff --git a/spring/spring-rabbitmq-annotation/README.md b/spring/spring-rabbitmq-annotation/README.md
new file mode 100644
index 0000000..8ef27f7
--- /dev/null
+++ b/spring/spring-rabbitmq-annotation/README.md
@@ -0,0 +1,403 @@
+# spring 整合 rabbitmq(注解方式)
+
+## 一、说明
+
+### 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),里面有详细的配图说明。
+
+
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
+
+```xml
+
+
+ org.springframework.amqp
+ spring-rabbit
+ 2.1.2.RELEASE
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.9.8
+
+```
+
+
+
+## 二、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> 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);
+ }
+}
+```
\ No newline at end of file
diff --git a/spring/spring-rabbitmq-annotation/src/main/java/com/heibaiying/rabbit/RabbitBaseConfig.java b/spring/spring-rabbitmq-annotation/src/main/java/com/heibaiying/rabbit/RabbitBaseConfig.java
index c498ce8..c631ed4 100644
--- a/spring/spring-rabbitmq-annotation/src/main/java/com/heibaiying/rabbit/RabbitBaseConfig.java
+++ b/spring/spring-rabbitmq-annotation/src/main/java/com/heibaiying/rabbit/RabbitBaseConfig.java
@@ -16,7 +16,7 @@ import org.springframework.context.annotation.Configuration;
/**
* @author : heibaiying
- * @description : 声明队列、交换机、绑定关系、和队列消息监听
+ * @description : rabbitmq 基本配置
*/
@Configuration
diff --git a/spring/spring-rabbitmq/README.md b/spring/spring-rabbitmq/README.md
new file mode 100644
index 0000000..8c23748
--- /dev/null
+++ b/spring/spring-rabbitmq/README.md
@@ -0,0 +1,381 @@
+# spring 整合 rabbitmq(xml配置方式)
+
+## 一、说明
+
+### 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),里面有详细的配图说明。
+
+
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
+
+```xml
+
+
+ org.springframework.amqp
+ spring-rabbit
+ 2.1.2.RELEASE
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.9.8
+
+```
+
+
+
+## 二、spring rabbit 基本配置
+
+```properties
+rabbitmq.addresses=localhost:5672
+rabbitmq.username=guest
+rabbitmq.password=guest
+# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
+rabbitmq.virtualhost=/
+```
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## 三、简单消费的发送
+
+#### 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> 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);
+ }
+}
+```
\ No newline at end of file
diff --git a/spring/spring-rabbitmq/src/main/java/com/heibaiying/rabbit/RabbitBaseAnnotation.java b/spring/spring-rabbitmq/src/main/java/com/heibaiying/rabbit/RabbitBaseAnnotation.java
index 5581346..93b4919 100644
--- a/spring/spring-rabbitmq/src/main/java/com/heibaiying/rabbit/RabbitBaseAnnotation.java
+++ b/spring/spring-rabbitmq/src/main/java/com/heibaiying/rabbit/RabbitBaseAnnotation.java
@@ -75,7 +75,10 @@ public class RabbitBaseAnnotation {
//得到消息体内容
byte[] body = message.getBody();
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
- //第二个参数 代表是否一次签收多条
+ /*
+ * DeliveryTag 是一个单调递增的整数
+ * 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
+ */
channel.basicAck(properties.getDeliveryTag(), false);
}
});
diff --git a/spring/spring-rabbitmq/src/test/java/com/heibaiying/rabbit/RabbitTest.java b/spring/spring-rabbitmq/src/test/java/com/heibaiying/rabbit/RabbitTest.java
index 32c408d..0f21a3f 100644
--- a/spring/spring-rabbitmq/src/test/java/com/heibaiying/rabbit/RabbitTest.java
+++ b/spring/spring-rabbitmq/src/test/java/com/heibaiying/rabbit/RabbitTest.java
@@ -41,7 +41,7 @@ public class RabbitTest {
Message message4 = new Message(notReceived.getBytes(), properties);
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
- /*
+ /* 结果:
* SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
* FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
* FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
diff --git a/spring/spring-redis-annotation/README.md b/spring/spring-redis-annotation/README.md
index c821b72..d893696 100644
--- a/spring/spring-redis-annotation/README.md
+++ b/spring/spring-redis-annotation/README.md
@@ -24,7 +24,7 @@
1. jedis和redisson的配置类和单元测试分别位于config和test下对应的包中,其中集群的配置类以cluster开头。
2. 实体类Programmer.java用于测试Redisson序列化与反序列化
-
+
diff --git a/spring/spring-redis/README.md b/spring/spring-redis/README.md
index c2c8964..fafd639 100644
--- a/spring/spring-redis/README.md
+++ b/spring/spring-redis/README.md
@@ -24,7 +24,7 @@
1. jedis和redisson的配置和单元测试分别位于resources和test下对应的包中,其中集群的配置文件以cluster结尾。所有配置按照需要在springApplication.xml用import导入。
2. 实体类Programmer.java用于测试Redisson序列化与反序列化
-
+
**springapplication.xml文件:**
diff --git a/spring/spring-scheduling-annotation/README.md b/spring/spring-scheduling-annotation/README.md
new file mode 100644
index 0000000..ddaa651
--- /dev/null
+++ b/spring/spring-scheduling-annotation/README.md
@@ -0,0 +1,192 @@
+# spring 定时任务(注解方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+关于任务的调度配置定义在ServletConfig.java中,为方便观察项目定时执行的情况,项目以web的方式构建。
+
+
+
+
+
+### 1.2 依赖说明
+
+导入基本依赖
+
+```xml
+
+
+ 4.0.0
+
+ com.heibaiying
+ spring-scheduling
+ 1.0-SNAPSHOT
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
+ 5.1.3.RELEASE
+
+
+
+
+ org.springframework
+ spring-context
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-beans
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-core
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-web
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-webmvc
+ ${spring-base-version}
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+
+
+```
+
+
+
+## 二、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 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
+
diff --git a/spring/spring-scheduling/README.md b/spring/spring-scheduling/README.md
new file mode 100644
index 0000000..0cf9d90
--- /dev/null
+++ b/spring/spring-scheduling/README.md
@@ -0,0 +1,183 @@
+# spring 定时任务(xml配置方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+关于任务的调度配置定义在springApplication.xml中,为方便观察项目定时执行的情况,项目以web的方式构建。
+
+
+
+
+
+### 1.2 依赖说明
+
+导入基本依赖
+
+```xml
+
+
+ 4.0.0
+
+ com.heibaiying
+ spring-scheduling
+ 1.0-SNAPSHOT
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
+ 5.1.3.RELEASE
+
+
+
+
+ org.springframework
+ spring-context
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-beans
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-core
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-web
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-webmvc
+ ${spring-base-version}
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+
+
+```
+
+
+
+## 二、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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**关于调度程序线程池作用说明**:
+
+按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
+
+**关于任务执行线程池作用说明**:
+
+按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
+
diff --git a/spring/spring-scheduling/src/main/java/com/heibaiying/task/Task.java b/spring/spring-scheduling/src/main/java/com/heibaiying/task/Task.java
index 128e862..653fc57 100644
--- a/spring/spring-scheduling/src/main/java/com/heibaiying/task/Task.java
+++ b/spring/spring-scheduling/src/main/java/com/heibaiying/task/Task.java
@@ -14,7 +14,6 @@ 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",
@@ -24,7 +23,6 @@ public class Task {
/**
* 基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。
*/
- @Scheduled(fixedRate = 5000)
@Async
public void methodB() throws InterruptedException {
Thread thread = Thread.currentThread();
@@ -33,7 +31,6 @@ public class Task {
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",
diff --git a/spring/spring-websocket-annotation/README.md b/spring/spring-websocket-annotation/README.md
new file mode 100644
index 0000000..1306854
--- /dev/null
+++ b/spring/spring-websocket-annotation/README.md
@@ -0,0 +1,203 @@
+# spring websocket(注解方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候用临时用户名作为session的id;
+2. webconfig 包是基础注解的方式配置web,在spring-base-annotation项目中已经讲解过每个类作用;
+3. CustomHander为消息的自定义处理器;
+4. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
+5. webSocketConfig 是websocket 的主要配置类;
+6. 项目以web的方式构建。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了基本的spring 依赖外,还需要导入webSocket的依赖包
+
+```xml
+
+
+ org.springframework
+ spring-websocket
+ 5.1.3.RELEASE
+
+```
+
+
+
+## 二、spring websocket
+
+#### 2.1 创建消息处理类,继承自TextWebSocketHandler
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 自定义消息处理类
+ */
+public class CustomHandler extends TextWebSocketHandler {
+
+ private Map 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 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 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" %>
+
+
+ ${sessionScope.get("username")}您好!欢迎进入群聊大厅!
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.5 简单登录的实现
+
+```java
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Title
+
+
+
+
+
+```
+
+```java
+@Controller
+public class LoginController {
+
+ @PostMapping("login")
+ public String login(String username, HttpSession session){
+ session.setAttribute(Constant.USER_NAME,username);
+ return "chat";
+ }
+}
+```
+
diff --git a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/constant/Constant.java b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/constant/Constant.java
index a3417e6..70ef5a5 100644
--- a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/constant/Constant.java
+++ b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/constant/Constant.java
@@ -1,9 +1,7 @@
package com.heibaiying.constant;
/**
- * @author : 罗祥
- * @description :
- * @date :create in 2018/12/27
+ * @author : heibaiying
*/
public interface Constant {
diff --git a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/controller/LoginController.java b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/controller/LoginController.java
index f7c2276..f3e5541 100644
--- a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/controller/LoginController.java
+++ b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/controller/LoginController.java
@@ -8,9 +8,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
/**
- * @author : 罗祥
+ * @author : heibaiying
* @description : 简单登录
- * @date :create in 2018/12/27
*/
@Controller
public class LoginController {
diff --git a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/DispatcherServletInitializer.java b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/DispatcherServletInitializer.java
index 000b86f..f0360a6 100644
--- a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/DispatcherServletInitializer.java
+++ b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/DispatcherServletInitializer.java
@@ -3,9 +3,8 @@ package com.heibaiying.webconfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
- * @author : 罗祥
+ * @author : heibaiying
* @description : 等价于 web.xml 中配置前端控制器
- * @date :create in 2018/12/27
*/
public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
diff --git a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/ServletConfig.java b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/ServletConfig.java
index af45804..8f967d3 100644
--- a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/ServletConfig.java
+++ b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/ServletConfig.java
@@ -10,9 +10,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
- * @author : 罗祥
+ * @author : heibaiying
* @description : 主配置类
- * @date :create in 2018/12/27
*/
@Configuration
@EnableWebMvc
diff --git a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/characterEncodingFilter.java b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/characterEncodingFilter.java
index 0958a33..ddcce72 100644
--- a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/characterEncodingFilter.java
+++ b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/webconfig/characterEncodingFilter.java
@@ -6,9 +6,8 @@ import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
/**
- * @author : 罗祥
+ * @author : heibaiying
* @description : 编码过滤器 防止乱码
- * @date :create in 2018/12/27
*/
@WebFilter(filterName = "characterEncodingFilter", urlPatterns = "/*",
initParams = {
diff --git a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/CustomerHandler.java b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/CustomHandler.java
similarity index 97%
rename from spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/CustomerHandler.java
rename to spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/CustomHandler.java
index 5e41ab8..c842878 100644
--- a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/CustomerHandler.java
+++ b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/CustomHandler.java
@@ -15,7 +15,7 @@ import java.util.concurrent.ConcurrentHashMap;
* @author : heibaiying
* @description : 自定义消息处理类
*/
-public class CustomerHandler extends TextWebSocketHandler {
+public class CustomHandler extends TextWebSocketHandler {
private Map nameAndSession = new ConcurrentHashMap<>();
diff --git a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/WebSocketConfig.java b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/WebSocketConfig.java
index 7854d56..46cb88d 100644
--- a/spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/WebSocketConfig.java
+++ b/spring/spring-websocket-annotation/src/main/java/com/heibaiying/websocket/WebSocketConfig.java
@@ -6,9 +6,8 @@ import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
- * @author : 罗祥
+ * @author : heibaiying
* @description :websocket 配置
- * @date :create in 2018/12/27
*/
@Configuration
@EnableWebSocket
@@ -16,6 +15,6 @@ public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
- registry.addHandler(new CustomerHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
+ registry.addHandler(new CustomHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
}
}
diff --git a/spring/spring-websocket/README.md b/spring/spring-websocket/README.md
new file mode 100644
index 0000000..8f82b01
--- /dev/null
+++ b/spring/spring-websocket/README.md
@@ -0,0 +1,233 @@
+# spring websocket(xml配置方式)
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候用临时用户名作为session的id;
+2. CustomHander为消息的自定义处理器;
+3. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
+4. 项目以web的方式构建。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了基本的spring 依赖外,还需要导入webSocket的依赖包
+
+```xml
+
+
+ org.springframework
+ spring-websocket
+ 5.1.3.RELEASE
+
+```
+
+
+
+## 二、spring websocket
+
+#### 2.1 创建消息处理类,继承自TextWebSocketHandler
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 自定义消息处理类
+ */
+public class CustomHandler extends TextWebSocketHandler {
+
+ private Map 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 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 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.4 前端 websocket 的实现
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ ${sessionScope.get("username")}您好!欢迎进入群聊大厅!
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.5 简单登录的实现
+
+```java
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Title
+
+
+
+
+
+```
+
+```java
+@Controller
+public class LoginController {
+
+ @PostMapping("login")
+ public String login(String username, HttpSession session){
+ session.setAttribute(Constant.USER_NAME,username);
+ return "chat";
+ }
+}
+```
+
diff --git a/spring/spring-websocket/src/main/java/com/heibaiying/constant/Constant.java b/spring/spring-websocket/src/main/java/com/heibaiying/constant/Constant.java
index a3417e6..50fcc31 100644
--- a/spring/spring-websocket/src/main/java/com/heibaiying/constant/Constant.java
+++ b/spring/spring-websocket/src/main/java/com/heibaiying/constant/Constant.java
@@ -1,10 +1,5 @@
package com.heibaiying.constant;
-/**
- * @author : 罗祥
- * @description :
- * @date :create in 2018/12/27
- */
public interface Constant {
String USER_NAME="username";
diff --git a/spring/spring-websocket/src/main/java/com/heibaiying/controller/LoginController.java b/spring/spring-websocket/src/main/java/com/heibaiying/controller/LoginController.java
index f7c2276..c51a7f3 100644
--- a/spring/spring-websocket/src/main/java/com/heibaiying/controller/LoginController.java
+++ b/spring/spring-websocket/src/main/java/com/heibaiying/controller/LoginController.java
@@ -3,12 +3,10 @@ package com.heibaiying.controller;
import com.heibaiying.constant.Constant;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
/**
- * @author : 罗祥
* @description : 简单登录
* @date :create in 2018/12/27
*/
diff --git a/spring/spring-websocket/src/main/java/com/heibaiying/websocket/CustomerHandler.java b/spring/spring-websocket/src/main/java/com/heibaiying/websocket/CustomHandler.java
similarity index 94%
rename from spring/spring-websocket/src/main/java/com/heibaiying/websocket/CustomerHandler.java
rename to spring/spring-websocket/src/main/java/com/heibaiying/websocket/CustomHandler.java
index 6ee4e2e..9a78700 100644
--- a/spring/spring-websocket/src/main/java/com/heibaiying/websocket/CustomerHandler.java
+++ b/spring/spring-websocket/src/main/java/com/heibaiying/websocket/CustomHandler.java
@@ -3,7 +3,6 @@ package com.heibaiying.websocket;
import com.heibaiying.constant.Constant;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
-import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@@ -16,7 +15,7 @@ import java.util.concurrent.ConcurrentHashMap;
* @author : heibaiying
* @description : 自定义消息处理类
*/
-public class CustomerHandler extends TextWebSocketHandler {
+public class CustomHandler extends TextWebSocketHandler {
private Map nameAndSession = new ConcurrentHashMap<>();
diff --git a/spring/spring-websocket/src/main/resources/springApplication.xml b/spring/spring-websocket/src/main/resources/springApplication.xml
index 80023cc..5f4febe 100644
--- a/spring/spring-websocket/src/main/resources/springApplication.xml
+++ b/spring/spring-websocket/src/main/resources/springApplication.xml
@@ -32,10 +32,10 @@
-
+
-
+