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是待切入方法。 + +![spring+redis项目目录结构](D:\spring-samples-for-all\pictures\spring-aop-annotation.png) + + + +### 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)反而会延后执行,类似于同心圆原理。 + + ![aop执行顺序](D:\spring-samples-for-all\pictures\aop执行顺序.png) \ 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是待切入的方法。 + +![spring+redis项目目录结构](D:\spring-samples-for-all\pictures\spring-aop.png) + + + +### 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) + +![spring-scheduling](D:\spring-samples-for-all\pictures\spring-dubbo.png) + + + +## 二、项目依赖 + +**在父工程的项目中同一导入依赖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 下为公共的实体类。 + +![spring-scheduling](D:\spring-samples-for-all\pictures\dubbo-ano-common.png) + +## 四、 服务提供者(dubbo-ano-provider) + +![spring-scheduling](D:\spring-samples-for-all\pictures\dubbo-ano-provider.png) + +#### 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) + +![spring-scheduling](D:\spring-samples-for-all\pictures\dubbo-ano-consumer.png) + +#### 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) + +![spring-scheduling](D:\spring-samples-for-all\pictures\spring-dubbo.png) + + + +## 二、项目依赖 + +**在父工程的项目中同一导入依赖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 下为公共的实体类。 + +![spring-scheduling](D:\spring-samples-for-all\pictures\dubbo-common.png) + +## 四、 服务提供者(dubbo-provider) + +![spring-scheduling](D:\spring-samples-for-all\pictures\dubbo-provider.png) + +#### 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) + +![spring-scheduling](D:\spring-samples-for-all\pictures\dubbo-consumer.png) + +#### 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。 + + + +![spring-email](D:\spring-samples-for-all\pictures\spring-email-annotation.png) + + + +### 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。 + + + +![spring-email](D:\spring-samples-for-all\pictures\spring-email.png) + + + +### 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文件夹下。 + +![spring+redis项目目录结构](D:\spring-samples-for-all\pictures\spring-memcached-annotation.png) + +### 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序列化与反序列化 + +![spring+redis项目目录结构](D:\spring-samples-for-all\pictures\spring-memcached.png) + +**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下,项目以单元测试的方式进行测试。 + +![spring-mongodb-annotation](D:\spring-samples-for-all\pictures\spring-mongodb-annotation.png) + + + +### 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下,项目以单元测试的方式进行测试。 + +![spring+redis项目目录结构](D:\spring-samples-for-all\pictures\spring-mongodb.png) + + + +### 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),里面有详细的配图说明。 + + + +![spring-rabbitmq](D:\spring-samples-for-all\pictures\spring-rabbitmq-annotation.png) + + + +### 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),里面有详细的配图说明。 + + + +![spring-rabbitmq](D:\spring-samples-for-all\pictures\spring-rabbitmq.png) + + + +### 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序列化与反序列化 -![spring+redis+ano项目目录结构](D:\spring-samples-for-all\pictures\spring+redis+ano项目目录结构.png) +![spring+redis+ano项目目录结构](D:\spring-samples-for-all\pictures\spring-redis-annotation.png) 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序列化与反序列化 -![spring+redis项目目录结构](D:\spring-samples-for-all\pictures\spring+redis项目目录结构.png) +![spring+redis项目目录结构](D:\spring-samples-for-all\pictures\spring-redis.png) **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的方式构建。 + +![spring-scheduling](D:\spring-samples-for-all\pictures\spring-scheduling-annotation.png) + + + +### 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的方式构建。 + +![spring-scheduling](D:\spring-samples-for-all\pictures\spring-scheduling.png) + + + +### 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的方式构建。 + +![spring-scheduling](D:\spring-samples-for-all\pictures\spring-websocket-annotation.png) + + + +### 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的方式构建。 + +![spring-scheduling](D:\spring-samples-for-all\pictures\spring-websocket.png) + + + +### 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 @@
- + - +