增加项目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)<br/>[spring-websocket-annotation](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-websocket-annotation) | spring 整合 websocket | [Spring Websocket](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/web.html#websocket) |
|
||||
| [spring-mail](https://github.com/heibaiying/spring-samples-for-all/tree/master/spring/spring-email) <br/>[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)<br/>[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<br/>spring-kafka-annotation | spring 整合 kafka | [Spring for Apache Kafka](https://docs.spring.io/spring-kafka/docs/2.2.2.RELEASE/reference/html/) |
|
||||
|
||||
<br/>
|
||||
|
||||
@ -86,4 +85,5 @@ spring-cloud:Finchley.SR2
|
||||
代码涉及到的相关参考资料放在了仓库的referenced documents 目录下,文件清单如下:
|
||||
|
||||
- Servlet3.1规范(最终版).pdf
|
||||
- Thymeleaf中⽂参考⼿册.pdf
|
||||
|
||||
|
BIN
pictures/aop执行顺序.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
pictures/dubbo-ano-common.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
pictures/dubbo-ano-consumer.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
pictures/dubbo-ano-provider.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
pictures/dubbo-common.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
pictures/dubbo-consumer.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
pictures/dubbo-provider.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
pictures/spring-aop-annotation.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
pictures/spring-aop.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
pictures/spring-dubbo-annotation.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
pictures/spring-dubbo.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
pictures/spring-email-annotation.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
pictures/spring-email.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
pictures/spring-memcached-annotation.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
pictures/spring-memcached.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
pictures/spring-mongodb-annotation.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
pictures/spring-mongodb.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
pictures/spring-rabbitmq-annotation.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
pictures/spring-rabbitmq.png
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
BIN
pictures/spring-scheduling-annotation.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
pictures/spring-scheduling.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
pictures/spring-websocket-annotation.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
pictures/spring-websocket.png
Normal file
After Width: | Height: | Size: 18 KiB |
169
spring/spring-aop-annotation/README.md
Normal file
@ -0,0 +1,169 @@
|
||||
# spring AOP(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 切面配置位于com.heibaiying.config下AopConfig.java文件;
|
||||
2. 自定义切面位于advice下,其中CustomAdvice是标准的自定义切面,FirstAdvice和SecondAdvice用于测试多切面共同作用于同一个被切入点时的执行顺序;
|
||||
3. OrderService是待切入方法。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入aop依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
注:@Pointcut的值可以是多个切面表达式的组合。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
@Aspect
|
||||
@Component //除了加上@Aspect外 还需要声明为spring的组件 @Aspect只是一个切面声明
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
/**
|
||||
* 使用 || , or 表示或
|
||||
* 使用 && , and 表示与
|
||||
* ! 表示非
|
||||
*/
|
||||
@Pointcut("execution(* com.heibaiying.service.OrderService.*(..)) && !execution(* com.heibaiying.service.OrderService.deleteOrder(..))")
|
||||
private void pointCut() {
|
||||
|
||||
}
|
||||
|
||||
@Before("pointCut()")
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
// returning 参数用于指定返回结果与哪一个参数绑定
|
||||
@AfterReturning(pointcut = "pointCut()", returning = "result")
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
@Around("pointCut()")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
// throwing 参数用于指定抛出的异常与哪一个参数绑定
|
||||
@AfterThrowing(pointcut = "pointCut()", throwing = "exception")
|
||||
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
|
||||
@After("pointCut()")
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 开启切面配置
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.*")
|
||||
@EnableAspectJAutoProxy // 开启@Aspect注解支持 等价于<aop:aspectj-autoproxy>
|
||||
public class AopConfig {
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = AopConfig.class)
|
||||
public class AopTest {
|
||||
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void saveAndQuery() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
|
||||
* 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行
|
||||
*/
|
||||
@Test
|
||||
public void delete() {
|
||||
orderService.deleteOrder(12793179L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 切面执行顺序
|
||||
|
||||
- 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
|
||||
|
||||
- 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行,类似于同心圆原理。
|
||||
|
||||

|
@ -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);
|
||||
|
@ -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)
|
||||
|
162
spring/spring-aop/README.md
Normal file
@ -0,0 +1,162 @@
|
||||
# spring AOP(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
切面配置位于resources下的aop.xml文件,其中CustomAdvice是自定义切面类,OrderService是待切入的方法。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入aop依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
//前置通知
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
//后置通知(抛出异常后不会被执行)
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
//环绕通知
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
//异常通知
|
||||
public void afterException(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
// 后置通知 总会执行 但是不能访问到返回值
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
http://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!--开启后允许使用Spring AOP的@AspectJ注解 如果是纯xml配置 可以不用开启这个声明-->
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<!-- 1.配置目标对象 -->
|
||||
<bean name="orderService" class="com.heibaiying.service.OrderServiceImpl"/>
|
||||
<!-- 2.声明切面 -->
|
||||
<bean name="myAdvice" class="com.heibaiying.advice.CustomAdvice"/>
|
||||
<!-- 3.配置将通知织入目标对象 -->
|
||||
<aop:config>
|
||||
<!--命名切入点 关于切入点更多表达式写法可以参见README.md-->
|
||||
<aop:pointcut expression="execution(* com.heibaiying.service.OrderService.*(..))" id="cutPoint"/>
|
||||
<aop:aspect ref="myAdvice">
|
||||
<!-- 前置通知 -->
|
||||
<aop:before method="before" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置通知 如果需要拿到返回值 则要指明返回值对应的参数名称-->
|
||||
<aop:after-returning method="afterReturning" pointcut-ref="cutPoint" returning="result"/>
|
||||
<!-- 环绕通知 -->
|
||||
<aop:around method="around" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置异常 如果需要拿到异常 则要指明异常对应的参数名称 -->
|
||||
<aop:after-throwing method="afterException" pointcut-ref="cutPoint" throwing="exception"/>
|
||||
<!-- 最终通知 -->
|
||||
<aop:after method="after" pointcut-ref="cutPoint"/>
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration("classpath:aop.xml")
|
||||
public class AopTest {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
327
spring/spring-dubbo-annotation/README.md
Normal file
@ -0,0 +1,327 @@
|
||||
# spring 整合 dubbo(注解方式)
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo-annotation下构建三个子模块:
|
||||
|
||||
1. dubbo-ano-common 是公共模块,用于存放公共的接口和bean,被dubbo-ano-provider和dubbo-ano-provider在pom.xml中引用;
|
||||
2. dubbo-ano-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-ano-provider 是服务的消费者,调用provider提供的查询服务。
|
||||
|
||||
1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中同一导入依赖dubbo依赖的的jar包**
|
||||
|
||||
这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-ano-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||

|
||||
|
||||
## 四、 服务提供者(dubbo-ano-provider)
|
||||
|
||||

|
||||
|
||||
#### 4.1 提供方配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 提供方应用信息,用于计算依赖关系
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-provider");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用zookeeper注册中心暴露服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用dubbo协议在20880端口暴露服务
|
||||
*/
|
||||
@Bean
|
||||
public ProtocolConfig protocolConfig() {
|
||||
ProtocolConfig protocolConfig = new ProtocolConfig();
|
||||
protocolConfig.setName("dubbo");
|
||||
protocolConfig.setPort(20880);
|
||||
return protocolConfig;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 使用注解@Service暴露服务
|
||||
|
||||
需要注意的是这里的@Service注解不是spring的注解,而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
|
||||
|
||||
```java
|
||||
package com.heibaiying.service;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Service;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service(timeout = 5000)
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
####
|
||||
|
||||
## 五、服务消费者(dubbo-ano-consumer)
|
||||
|
||||

|
||||
|
||||
#### 1.消费方的配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-consumer");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置调用服务超时时间
|
||||
* 关闭所有服务的启动时检查
|
||||
*/
|
||||
@Bean
|
||||
public ConsumerConfig consumerConfig() {
|
||||
ConsumerConfig consumerConfig = new ConsumerConfig();
|
||||
consumerConfig.setTimeout(3000);
|
||||
consumerConfig.setCheck(false);
|
||||
return consumerConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用zookeeper注册中心暴露发现服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.使用注解@Reference引用远程服务
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
// dubbo远程引用注解
|
||||
@Reference
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer中 pom.xml如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider中 pom.xml如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
```
|
@ -4,7 +4,7 @@ import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatche
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 前端过滤器配置
|
||||
* @description : 前端控制器配置
|
||||
*/
|
||||
public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :
|
||||
* @description :消费服务
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
279
spring/spring-dubbo/README.md
Normal file
@ -0,0 +1,279 @@
|
||||
# spring 整合 dubbo(xml配置方式)
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo下构建三个子模块:
|
||||
|
||||
1. dubbo-common 是公共模块,用于存放公共的接口和bean,被dubbo-provider和dubbo-provider在pom.xml中引用;
|
||||
2. dubbo-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-provider 是服务的消费者,调用provider提供的查询服务。
|
||||
|
||||
1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中同一导入依赖dubbo依赖的的jar包**
|
||||
|
||||
这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||

|
||||
|
||||
## 四、 服务提供者(dubbo-provider)
|
||||
|
||||

|
||||
|
||||
#### 4.1 productService是服务的提供者( 商品数据用模拟数据展示)
|
||||
|
||||
注:这里实现的接口IProductService来源于公共模块
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 在dubbo.xml暴露服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 提供方应用信息,用于计算依赖关系 -->
|
||||
<dubbo:application name="dubbo-provider"/>
|
||||
|
||||
<!-- 使用zookeeper注册中心暴露服务地址 如果是集群配置 用, 分隔地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 用dubbo协议在20880端口暴露服务 -->
|
||||
<dubbo:protocol name="dubbo" port="20880"/>
|
||||
|
||||
<!-- 声明需要暴露的服务接口 -->
|
||||
<dubbo:service interface="com.heibaiying.api.IProductService" ref="productService"/>
|
||||
|
||||
<!-- 和本地bean一样实现服务 -->
|
||||
<bean id="productService" class="com.heibaiying.service.ProductService"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
## 五、服务消费者(dubbo-consumer)
|
||||
|
||||

|
||||
|
||||
#### 1.在dubbo.xml调用远程的服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
|
||||
<dubbo:application name="dubbo-consumer">
|
||||
<dubbo:parameter key="shutdown.timeout" value="60000"/> <!-- 单位毫秒 -->
|
||||
</dubbo:application>
|
||||
|
||||
<!--Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"。-->
|
||||
<!--可以关闭所有服务的启动时检查 -->
|
||||
<dubbo:consumer check="false" />
|
||||
|
||||
<!-- 使用zookeeper注册中心暴露发现服务地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
|
||||
<dubbo:reference id="sellService" interface="com.heibaiying.api.IProductService"/>
|
||||
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.消费服务
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
@Autowired
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer中 pom.xml如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider中 pom.xml如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
```
|
287
spring/spring-email-annotation/README.md
Normal file
@ -0,0 +1,287 @@
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置类为com.heibaiying.config下EmailConfig.java;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为SendEmail。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是beetl,beetl性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.email")
|
||||
public class EmailConfig {
|
||||
|
||||
/***
|
||||
* 在这里可以声明不同的邮件服务器主机,通常是SMTP主机,而具体的用户名和时授权码则建议在业务中从数据库查询
|
||||
*/
|
||||
@Bean(name = "qqMailSender")
|
||||
JavaMailSenderImpl javaMailSender() {
|
||||
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
|
||||
javaMailSender.setHost("smtp.qq.com");
|
||||
javaMailSender.setPassword("587");
|
||||
return javaMailSender;
|
||||
}
|
||||
|
||||
/***
|
||||
* 配置模板引擎
|
||||
*/
|
||||
@Bean
|
||||
GroupTemplate groupTemplate() throws IOException {
|
||||
//指定加载模板资源的位置 指定在classpath:beetl下-
|
||||
ClasspathResourceLoader loader = new ClasspathResourceLoader("beetl");
|
||||
//beetl配置 这里采用默认的配置-
|
||||
org.beetl.core.Configuration configuration = org.beetl.core.Configuration.defaultConfiguration();
|
||||
return new GroupTemplate(loader, configuration);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板template.html如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = EmailConfig.class)
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
|
||||
springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
```
|
@ -11,7 +11,7 @@ import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :
|
||||
* @description : 邮件发送配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
|
298
spring/spring-email/README.md
Normal file
@ -0,0 +1,298 @@
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置文件为springApplication.xml;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为SendEmail。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是beetl,beetl性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.email"/>
|
||||
|
||||
<!--在这里可以声明不同的邮件服务器主机,通常是SMTP主机,而具体的用户名和时授权码则建议在业务中从数据库查询-->
|
||||
<bean id="qqMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
||||
<!--qq 邮箱配置 <a href="https://service.mail.qq.com/cgi-bin/help?subtype=1&no=167&id=28"> -->
|
||||
<property name="host" value="smtp.qq.com"/>
|
||||
<property name="port" value="587"/>
|
||||
</bean>
|
||||
|
||||
<!--配置beetle模板引擎 如果不使用模板引擎,以下的配置不是必须的-->
|
||||
<bean id="resourceLoader" class="org.beetl.core.resource.ClasspathResourceLoader">
|
||||
<!--指定加载模板资源的位置 指定在classpath:beetl下-->
|
||||
<constructor-arg name="root" value="beetl"/>
|
||||
</bean>
|
||||
<!--beetl 配置 这里采用默认的配置-->
|
||||
<bean id="configuration" class="org.beetl.core.Configuration" init-method="defaultConfiguration"/>
|
||||
<bean id="groupTemplate" class="org.beetl.core.GroupTemplate">
|
||||
<constructor-arg name="loader" ref="resourceLoader"/>
|
||||
<constructor-arg name="conf" ref="configuration"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板template.html如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
|
||||
/**
|
||||
* 简单邮件测试
|
||||
*/
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送内嵌资源的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送模板邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
@ -17,7 +17,7 @@ import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
@ -17,7 +17,7 @@
|
||||
<property name="port" value="587"/>
|
||||
</bean>
|
||||
|
||||
<!--配置模板引擎-->
|
||||
<!--配置beetle模板引擎-->
|
||||
<bean id="resourceLoader" class="org.beetl.core.resource.ClasspathResourceLoader">
|
||||
<!--指定加载模板资源的位置 指定在classpath:beetl下-->
|
||||
<constructor-arg name="root" value="beetl"/>
|
||||
|
@ -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<String, File> 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,
|
||||
|
150
spring/spring-memcached-annotation/README.md
Normal file
@ -0,0 +1,150 @@
|
||||
# spring 整合 mecached(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached的整合配置位于com.heibaiying.config文件夹下。
|
||||
|
||||

|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入xmemcached依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClient() {
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClientForCluster() {
|
||||
|
||||
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11211));
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
|
||||
// 赋予权重
|
||||
int[] weights = {1, 2};
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
|
||||
// 设置连接池大小
|
||||
builder.setConnectionPoolSize(10);
|
||||
// 协议工厂
|
||||
builder.setCommandFactory(new TextCommandFactory());
|
||||
// 分布策略,一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
|
||||
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
|
||||
// 设置序列化器
|
||||
builder.setTranscoder(new SerializingTranscoder());
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
|
||||
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
|
||||
| 清库 | flush_all | |
|
191
spring/spring-memcached/README.md
Normal file
@ -0,0 +1,191 @@
|
||||
# spring 整合 mecached(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached的整合配置位于resources下的memcached文件夹下,其中集群配置用cluster开头。所有配置按照需要在springApplication.xml用import导入。
|
||||
2. 实体类Programmer.java用于测试memcached序列化与反序列化
|
||||
|
||||

|
||||
|
||||
**springapplication.xml文件:**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--memcached 单机版配置-->
|
||||
<!--<import resource="classpath*:memcached/singleConfig.xml"/>-->
|
||||
|
||||
<!--memcached 集群配置-->
|
||||
<import resource="classpath*:memcached/clusterConfig.xml"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入xmemcached依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<constructor-arg name="addressList" value="192.168.200.201:11211"/>
|
||||
</bean>
|
||||
|
||||
<bean id="memcachedClient" factory-bean="memcachedClientBuilder" factory-method="build"
|
||||
destroy-method="shutdown"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean name="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<!--memcached servers 节点列表-->
|
||||
<constructor-arg name="addressList">
|
||||
<list>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11211"/>
|
||||
</bean>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11212"/>
|
||||
</bean>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--与servers对应的节点的权重-->
|
||||
<constructor-arg name="weights">
|
||||
<list>
|
||||
<value>1</value>
|
||||
<value>2</value>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--连接池大小-->
|
||||
<property name="connectionPoolSize" value="10"/>
|
||||
<!--协议工厂-->
|
||||
<property name="commandFactory">
|
||||
<bean class="net.rubyeye.xmemcached.command.TextCommandFactory"/>
|
||||
</property>
|
||||
<!--分布策略,一致性哈希net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)-->
|
||||
<property name="sessionLocator">
|
||||
<bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator"/>
|
||||
</property>
|
||||
<!--序列化转换器,默认使用net.rubyeye.xmemcached.transcoders.SerializingTranscoder-->
|
||||
<property name="transcoder">
|
||||
<bean class="net.rubyeye.xmemcached.transcoders.SerializingTranscoder"/>
|
||||
</property>
|
||||
</bean>
|
||||
<!-- 集群配置 实例化bean -->
|
||||
<bean name="memcachedClientForCulster" factory-bean="memcachedClientBuilder"
|
||||
factory-method="build" destroy-method="shutdown"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
|
||||
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
|
||||
| 清库 | flush_all | |
|
161
spring/spring-mongodb-annotation/README.md
Normal file
@ -0,0 +1,161 @@
|
||||
# spring 整合 mongodb(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于com.heibaiying.config下,项目以单元测试的方式进行测试。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入mongodb整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件及其映射类
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置属性
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:mongodb.properties")
|
||||
public class MongoProperty {
|
||||
|
||||
@Value("${mongo.host}")
|
||||
private String host;
|
||||
@Value("${mongo.port}")
|
||||
private int port;
|
||||
@Value("${mongo.dbname}")
|
||||
private String dbname;
|
||||
@Value("${mongo.connectionsPerHost}")
|
||||
private int connectionsPerHost;
|
||||
@Value("${mongo.threadsAllowedToBlockForConnectionMultiplier}")
|
||||
private int multiplier;
|
||||
@Value("${mongo.connectTimeout}")
|
||||
private int connectTimeout;
|
||||
@Value("${mongo.maxWaitTime}")
|
||||
private int maxWaitTime;
|
||||
@Value("${mongo.socketKeepAlive}")
|
||||
private boolean socketKeepAlive;
|
||||
@Value("${mongo.socketTimeout}")
|
||||
private int socketTimeout;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.*")
|
||||
public class MongoConfig {
|
||||
|
||||
@Bean
|
||||
public MongoDbFactory mongoDbFactory(MongoProperty mongo) {
|
||||
MongoClientOptions options = MongoClientOptions.builder()
|
||||
.threadsAllowedToBlockForConnectionMultiplier(mongo.getMultiplier())
|
||||
.connectionsPerHost(mongo.getConnectionsPerHost())
|
||||
.connectTimeout(mongo.getConnectTimeout())
|
||||
.maxWaitTime(mongo.getMaxWaitTime())
|
||||
.socketTimeout(mongo.getSocketTimeout())
|
||||
.build();
|
||||
MongoClient client = new MongoClient(new ServerAddress(mongo.getHost(), mongo.getPort()), options);
|
||||
return new SimpleMongoDbFactory(client, mongo.getDbname());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) {
|
||||
return new MongoTemplate(mongoDbFactory);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = MongoConfig.class)
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
}
|
||||
```
|
144
spring/spring-mongodb/README.md
Normal file
@ -0,0 +1,144 @@
|
||||
# spring 整合 mongodb(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于resources下,项目以单元测试的方式进行测试。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入mongodb整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--扫描配置文件-->
|
||||
<context:property-placeholder location="classpath:mongodb.properties"/>
|
||||
|
||||
<!--定义用于访问MongoDB的MongoClient实例-->
|
||||
<mongo:mongo-client host="${mongo.host}" port="${mongo.port}">
|
||||
<mongo:client-options
|
||||
connections-per-host="${mongo.connectionsPerHost}"
|
||||
threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
|
||||
connect-timeout="${mongo.connectTimeout}"
|
||||
max-wait-time="${mongo.maxWaitTime}"
|
||||
socket-keep-alive="${mongo.socketKeepAlive}"
|
||||
socket-timeout="${mongo.socketTimeout}"
|
||||
/>
|
||||
</mongo:mongo-client>
|
||||
|
||||
<!--定义用于连接到数据库的连接工厂-->
|
||||
<mongo:db-factory dbname="${mongo.dbname}" mongo-ref="mongoClient"/>
|
||||
|
||||
<!--实际操作mongodb的template,在代码中注入-->
|
||||
<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
|
||||
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : MongoDB 查询
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:mongodb.xml")
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
@ -10,9 +10,10 @@
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
|
||||
<!--扫描配置文件-->
|
||||
<context:property-placeholder location="classpath:mongodb.properties"/>
|
||||
|
||||
<!--定义用于访问MongoDB的MongoClient实例-->
|
||||
<mongo:mongo-client host="${mongo.host}" port="${mongo.port}">
|
||||
<mongo:client-options
|
||||
connections-per-host="${mongo.connectionsPerHost}"
|
||||
@ -24,8 +25,10 @@
|
||||
/>
|
||||
</mongo:mongo-client>
|
||||
|
||||
<!--定义用于连接到数据库的连接工厂-->
|
||||
<mongo:db-factory dbname="${mongo.dbname}" mongo-ref="mongoClient"/>
|
||||
|
||||
<!--实际操作mongodb的template,在代码中注入-->
|
||||
<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
|
||||
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
|
||||
</bean>
|
||||
|
403
spring/spring-rabbitmq-annotation/README.md
Normal file
@ -0,0 +1,403 @@
|
||||
# spring 整合 rabbitmq(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
#### 2.1 基本配置属性及其映射类
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : rabbit 属性配置
|
||||
*/
|
||||
@Data
|
||||
@PropertySource(value = "classpath:rabbitmq.properties")
|
||||
@Configuration
|
||||
public class RabbitProperty {
|
||||
|
||||
|
||||
@Value("${rabbitmq.addresses}")
|
||||
private String addresses;
|
||||
|
||||
@Value("${rabbitmq.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${rabbitmq.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${rabbitmq.virtualhost}")
|
||||
private String virtualhost;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 创建连接工厂、管理器
|
||||
|
||||
```java
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.rabbit.config")
|
||||
public class RabbitBaseConfig {
|
||||
|
||||
/**
|
||||
* 声明连接工厂
|
||||
*/
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory(RabbitProperty property) {
|
||||
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
|
||||
connectionFactory.setAddresses(property.getAddresses());
|
||||
connectionFactory.setUsername(property.getUsername());
|
||||
connectionFactory.setPassword(property.getPassword());
|
||||
connectionFactory.setVirtualHost(property.getVirtualhost());
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
* auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为true。
|
||||
*/
|
||||
@Bean
|
||||
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
rabbitAdmin.setAutoStartup(true);
|
||||
return rabbitAdmin;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
|
||||
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
|
||||
return rabbitTemplate;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试topic这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列1消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列2消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
|
||||
SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
```
|
@ -16,7 +16,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
* @description : rabbitmq 基本配置
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
|
381
spring/spring-rabbitmq/README.md
Normal file
@ -0,0 +1,381 @@
|
||||
# spring 整合 rabbitmq(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/rabbit
|
||||
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
|
||||
|
||||
<context:property-placeholder location="rabbitmq.properties"/>
|
||||
|
||||
<!--声明连接工厂-->
|
||||
<rabbit:connection-factory id="connectionFactory"
|
||||
addresses="${rabbitmq.addresses}"
|
||||
username="${rabbitmq.username}"
|
||||
password="${rabbitmq.password}"
|
||||
virtual-host="${rabbitmq.virtualhost}"/>
|
||||
|
||||
<!--创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为true。-->
|
||||
<rabbit:admin connection-factory="connectionFactory" auto-startup="true"/>
|
||||
|
||||
<!--声明 template 的时候需要声明id 不然会抛出异常-->
|
||||
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
|
||||
|
||||
|
||||
<!--可以在xml采用如下方式声明交换机、队列、绑定管理 但是建议使用代码方式声明 方法更加灵活且可以采用链调用-->
|
||||
<rabbit:queue name="remoting.queue"/>
|
||||
|
||||
<rabbit:direct-exchange name="remoting.exchange">
|
||||
<rabbit:bindings>
|
||||
<rabbit:binding queue="remoting.queue" key="remoting.binding"/>
|
||||
</rabbit:bindings>
|
||||
</rabbit:direct-exchange>
|
||||
|
||||
<!--扫描rabbit包 自动声明交换器、队列、绑定关系-->
|
||||
<context:component-scan base-package="com.heibaiying.rabbit"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试topic这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列1消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列2消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
|
||||
SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
```
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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 接收到
|
||||
|
@ -24,7 +24,7 @@
|
||||
1. jedis和redisson的配置类和单元测试分别位于config和test下对应的包中,其中集群的配置类以cluster开头。
|
||||
2. 实体类Programmer.java用于测试Redisson序列化与反序列化
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
1. jedis和redisson的配置和单元测试分别位于resources和test下对应的包中,其中集群的配置文件以cluster结尾。所有配置按照需要在springApplication.xml用import导入。
|
||||
2. 实体类Programmer.java用于测试Redisson序列化与反序列化
|
||||
|
||||

|
||||

|
||||
|
||||
**springapplication.xml文件:**
|
||||
|
||||
|
192
spring/spring-scheduling-annotation/README.md
Normal file
@ -0,0 +1,192 @@
|
||||
# spring 定时任务(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在ServletConfig.java中,为方便观察项目定时执行的情况,项目以web的方式构建。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Component
|
||||
public class Task {
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。
|
||||
*/
|
||||
@Scheduled(fixedDelay = 5000, initialDelay = 1000)
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。
|
||||
*/
|
||||
@Scheduled(fixedRate = 5000)
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0/10 * * * * ?")
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* spring 主配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableScheduling //启用Spring的计划任务执行功能
|
||||
@EnableAsync //启用Spring的异步方法执行功能
|
||||
@ComponentScan(basePackages = {"com.heibaiying.task"})
|
||||
public class ServletConfig implements WebMvcConfigurer, AsyncConfigurer, SchedulingConfigurer {
|
||||
|
||||
private ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// 任务执行器线程池配置
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
executor.setCorePoolSize(5);
|
||||
executor.setMaxPoolSize(10);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("MyExecutor-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
// 这个方法可以监听到异步程序发生的错误
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new AsyncUncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
||||
System.out.println(method.getName() + "发生错误:" + ex.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池 不然程序无法完全退出 只能kill才能完全退出
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// 调度程序线程池配置
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.setScheduler(taskExecutor());
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public Executor taskExecutor() {
|
||||
return Executors.newScheduledThreadPool(50);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
|
||||
|
183
spring/spring-scheduling/README.md
Normal file
@ -0,0 +1,183 @@
|
||||
# spring 定时任务(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在springApplication.xml中,为方便观察项目定时执行的情况,项目以web的方式构建。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
package com.heibaiying.task;
|
||||
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
public class Task {
|
||||
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/task
|
||||
http://www.springframework.org/schema/task/spring-task.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!--配置定时任务-->
|
||||
<bean id="task" class="com.heibaiying.task.Task"/>
|
||||
<task:scheduled-tasks scheduler="myScheduler">
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。-->
|
||||
<task:scheduled ref="task" method="methodA" fixed-delay="5000" initial-delay="1000"/>
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。-->
|
||||
<task:scheduled ref="task" method="methodB" fixed-rate="5000"/>
|
||||
<!-- cron 表达式-->
|
||||
<task:scheduled ref="task" method="methodC" cron="0/10 * * * * ?"/>
|
||||
</task:scheduled-tasks>
|
||||
|
||||
<!--定义任务调度器线程池的大小-->
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
|
||||
<!--定义任务执行器的线程池大小、等待队列的容量、和拒绝策略-->
|
||||
<task:executor
|
||||
id="executorWithPoolSizeRange"
|
||||
pool-size="5-25"
|
||||
queue-capacity="100"
|
||||
rejection-policy="CALLER_RUNS"/>
|
||||
<!--拒绝策略默认值为ABORT
|
||||
CALLER_RUNS来限制入栈任务
|
||||
DISCARD删除当前任务
|
||||
DISCARD_OLDEST将任务放在队列的头部。-->
|
||||
|
||||
<!--允许在任何Spring管理的对象上检测@Async和@Scheduled注释, 如果存在,将生成用于异步执行带注释的方法的代理。-->
|
||||
<task:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
|
||||
|
@ -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",
|
||||
|
203
spring/spring-websocket-annotation/README.md
Normal file
@ -0,0 +1,203 @@
|
||||
# spring websocket(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候用临时用户名作为session的id;
|
||||
2. webconfig 包是基础注解的方式配置web,在spring-base-annotation项目中已经讲解过每个类作用;
|
||||
3. CustomHander为消息的自定义处理器;
|
||||
4. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
|
||||
5. webSocketConfig 是websocket 的主要配置类;
|
||||
6. 项目以web的方式构建。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的spring 依赖外,还需要导入webSocket的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的beforeHandshake方法
|
||||
* 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
|
||||
* 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 创建websocket的配置类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :websocket 配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new CustomHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -1,9 +1,7 @@
|
||||
package com.heibaiying.constant;
|
||||
|
||||
/**
|
||||
* @author : 罗祥
|
||||
* @description :
|
||||
* @date :create in 2018/12/27
|
||||
* @author : heibaiying
|
||||
*/
|
||||
public interface Constant {
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 = {
|
||||
|
@ -15,7 +15,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomerHandler extends TextWebSocketHandler {
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
233
spring/spring-websocket/README.md
Normal file
@ -0,0 +1,233 @@
|
||||
# spring websocket(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候用临时用户名作为session的id;
|
||||
2. CustomHander为消息的自定义处理器;
|
||||
3. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
|
||||
4. 项目以web的方式构建。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的spring 依赖外,还需要导入webSocket的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的beforeHandshake方法
|
||||
* 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
|
||||
* 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 配置websocket
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--使用默认的Servlet来响应静态文件 -->
|
||||
<mvc:default-servlet-handler/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!-- 配置视图解析器 -->
|
||||
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
|
||||
id="internalResourceViewResolver">
|
||||
<!-- 前缀 -->
|
||||
<property name="prefix" value="/WEB-INF/jsp/"/>
|
||||
<!-- 后缀 -->
|
||||
<property name="suffix" value=".jsp"/>
|
||||
</bean>
|
||||
|
||||
<!--配置webSocket-->
|
||||
<bean id="customHandler" class="com.heibaiying.websocket.CustomHandler"/>
|
||||
<websocket:handlers>
|
||||
<!--指定webSocket 地址-->
|
||||
<websocket:mapping path="/socket" handler="customHandler"/>
|
||||
<!--webSocket握手-->
|
||||
<websocket:handshake-interceptors>
|
||||
<bean class="com.heibaiying.websocket.CustomHandshakeInterceptor"/>
|
||||
</websocket:handshake-interceptors>
|
||||
</websocket:handlers>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -1,10 +1,5 @@
|
||||
package com.heibaiying.constant;
|
||||
|
||||
/**
|
||||
* @author : 罗祥
|
||||
* @description :
|
||||
* @date :create in 2018/12/27
|
||||
*/
|
||||
public interface Constant {
|
||||
|
||||
String USER_NAME="username";
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
@ -32,10 +32,10 @@
|
||||
</bean>
|
||||
|
||||
<!--配置webSocket-->
|
||||
<bean id="customerHandler" class="com.heibaiying.websocket.CustomerHandler"/>
|
||||
<bean id="customHandler" class="com.heibaiying.websocket.CustomHandler"/>
|
||||
<websocket:handlers>
|
||||
<!--指定webSocket 地址-->
|
||||
<websocket:mapping path="/socket" handler="customerHandler"/>
|
||||
<websocket:mapping path="/socket" handler="customHandler"/>
|
||||
<!--webSocket握手-->
|
||||
<websocket:handshake-interceptors>
|
||||
<bean class="com.heibaiying.websocket.CustomHandshakeInterceptor"/>
|
||||
|