优化阅读格式
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
# spring AOP(注解方式)
|
||||
|
||||
# spring AOP(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -13,237 +14,237 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 切面配置位于com.heibaiying.config下AopConfig.java文件;
|
||||
2. 自定义切面位于advice下,其中CustomAdvice是标准的自定义切面,FirstAdvice和SecondAdvice用于测试多切面共同作用于同一个被切入点时的执行顺序;
|
||||
3. OrderService是待切入方法。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-aop-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入aop依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
注:@Pointcut的值可以是多个切面表达式的组合。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
@Aspect
|
||||
@Component //除了加上@Aspect外 还需要声明为spring的组件 @Aspect只是一个切面声明
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
/**
|
||||
* 使用 || , or 表示或
|
||||
* 使用 && , and 表示与
|
||||
* ! 表示非
|
||||
*/
|
||||
@Pointcut("execution(* com.heibaiying.service.OrderService.*(..)) && !execution(* com.heibaiying.service.OrderService.deleteOrder(..))")
|
||||
private void pointCut() {
|
||||
|
||||
}
|
||||
|
||||
@Before("pointCut()")
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
// returning 参数用于指定返回结果与哪一个参数绑定
|
||||
@AfterReturning(pointcut = "pointCut()", returning = "result")
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
@Around("pointCut()")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
// throwing 参数用于指定抛出的异常与哪一个参数绑定
|
||||
@AfterThrowing(pointcut = "pointCut()", throwing = "exception")
|
||||
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
|
||||
@After("pointCut()")
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 开启切面配置
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.*")
|
||||
@EnableAspectJAutoProxy // 开启@Aspect注解支持 等价于<aop:aspectj-autoproxy>
|
||||
public class AopConfig {
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = AopConfig.class)
|
||||
public class AopTest {
|
||||
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void saveAndQuery() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
|
||||
* 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行
|
||||
*/
|
||||
@Test
|
||||
public void delete() {
|
||||
orderService.deleteOrder(12793179L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 切面执行顺序
|
||||
|
||||
- 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
|
||||
|
||||
- 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行,类似于同心圆原理。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/aop执行顺序.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 附: 关于切面表达式的说明
|
||||
|
||||
切面表达式遵循以下格式:
|
||||
|
||||
```shell
|
||||
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
```
|
||||
|
||||
- 除了返回类型模式,名字模式和参数模式以外,所有的部分都是可选的;
|
||||
- `*`,它代表了匹配任意的返回类型;
|
||||
- `()` 匹配了一个不接受任何参数的方法, 而 `(..)` 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 `(*)` 匹配了一个接受一个任何类型的参数的方法。 模式 `(*,String)` 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型。
|
||||
|
||||
下面给出一些常见切入点表达式的例子。
|
||||
|
||||
- 任意公共方法的执行:
|
||||
|
||||
```java
|
||||
execution(public * *(..))
|
||||
```
|
||||
|
||||
- 任何一个以“set”开始的方法的执行:
|
||||
|
||||
```java
|
||||
execution(* set*(..))
|
||||
```
|
||||
|
||||
- `AccountService` 接口的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
```
|
||||
|
||||
- 定义在service包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
```
|
||||
|
||||
- 定义在service包或者子包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
```
|
||||
|
||||
- 在service包里的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```java
|
||||
within(com.xyz.service.*)
|
||||
```
|
||||
|
||||
- 在service包或者子包里的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```
|
||||
within(com.xyz.service..*)
|
||||
```
|
||||
|
||||
- 实现了 `AccountService` 接口的代理对象的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```
|
||||
this(com.xyz.service.AccountService)
|
||||
```
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 切面配置位于 com.heibaiying.config 下 AopConfig.java 文件;
|
||||
2. 自定义切面位于 advice 下,其中 CustomAdvice 是标准的自定义切面,FirstAdvice 和 SecondAdvice 用于测试多切面共同作用于同一个被切入点时的执行顺序;
|
||||
3. OrderService 是待切入方法。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-aop-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 aop 依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
注:@Pointcut 的值可以是多个切面表达式的组合。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
@Aspect
|
||||
@Component //除了加上@Aspect 外 还需要声明为 spring 的组件 @Aspect 只是一个切面声明
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
/**
|
||||
* 使用 || , or 表示或
|
||||
* 使用 && , and 表示与
|
||||
* ! 表示非
|
||||
*/
|
||||
@Pointcut("execution(* com.heibaiying.service.OrderService.*(..)) && !execution(* com.heibaiying.service.OrderService.deleteOrder(..))")
|
||||
private void pointCut() {
|
||||
|
||||
}
|
||||
|
||||
@Before("pointCut()")
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
// returning 参数用于指定返回结果与哪一个参数绑定
|
||||
@AfterReturning(pointcut = "pointCut()", returning = "result")
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
@Around("pointCut()")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
// throwing 参数用于指定抛出的异常与哪一个参数绑定
|
||||
@AfterThrowing(pointcut = "pointCut()", throwing = "exception")
|
||||
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
|
||||
@After("pointCut()")
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 开启切面配置
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.*")
|
||||
@EnableAspectJAutoProxy // 开启@Aspect 注解支持 等价于<aop:aspectj-autoproxy>
|
||||
public class AopConfig {
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = AopConfig.class)
|
||||
public class AopTest {
|
||||
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void saveAndQuery() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个切面作用于同一个切入点时,可以用@Order 指定切面的执行顺序
|
||||
* 优先级高的切面在切入方法前执行的通知 (before) 会优先执行,但是位于方法后执行的通知 (after,afterReturning) 反而会延后执行
|
||||
*/
|
||||
@Test
|
||||
public void delete() {
|
||||
orderService.deleteOrder(12793179L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 切面执行顺序
|
||||
|
||||
- 多个切面作用于同一个切入点时,可以用@Order 指定切面的执行顺序
|
||||
|
||||
- 优先级高的切面在切入方法前执行的通知 (before) 会优先执行,但是位于方法后执行的通知 (after,afterReturning) 反而会延后执行,类似于同心圆原理。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/aop 执行顺序.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 附: 关于切面表达式的说明
|
||||
|
||||
切面表达式遵循以下格式:
|
||||
|
||||
```shell
|
||||
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
```
|
||||
|
||||
- 除了返回类型模式,名字模式和参数模式以外,所有的部分都是可选的;
|
||||
- `*`,它代表了匹配任意的返回类型;
|
||||
- `()` 匹配了一个不接受任何参数的方法, 而 `(..)` 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 `(*)` 匹配了一个接受一个任何类型的参数的方法。 模式 `(*,String)` 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是 String 类型。
|
||||
|
||||
下面给出一些常见切入点表达式的例子。
|
||||
|
||||
- 任意公共方法的执行:
|
||||
|
||||
```java
|
||||
execution(public * *(..))
|
||||
```
|
||||
|
||||
- 任何一个以“set”开始的方法的执行:
|
||||
|
||||
```java
|
||||
execution(* set*(..))
|
||||
```
|
||||
|
||||
- `AccountService` 接口的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
```
|
||||
|
||||
- 定义在 service 包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
```
|
||||
|
||||
- 定义在 service 包或者子包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
```
|
||||
|
||||
- 在 service 包里的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```java
|
||||
within(com.xyz.service.*)
|
||||
```
|
||||
|
||||
- 在 service 包或者子包里的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```
|
||||
within(com.xyz.service..*)
|
||||
```
|
||||
|
||||
- 实现了 `AccountService` 接口的代理对象的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```
|
||||
this(com.xyz.service.AccountService)
|
||||
```
|
||||
|
||||
更多表达式可以参考官方文档:[Declaring a Pointcut](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#aop-pointcuts)
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring AOP(xml配置方式)
|
||||
|
||||
# spring AOP(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -12,228 +13,228 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
切面配置位于resources下的aop.xml文件,其中CustomAdvice是自定义切面类,OrderService是待切入的方法。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-aop.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入aop依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
//前置通知
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
//后置通知(抛出异常后不会被执行)
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
//环绕通知
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
//异常通知
|
||||
public void afterException(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
// 后置通知 总会执行 但是不能访问到返回值
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
http://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!--开启后允许使用Spring AOP的@AspectJ注解 如果是纯xml配置 可以不用开启这个声明-->
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<!-- 1.配置目标对象 -->
|
||||
<bean name="orderService" class="com.heibaiying.service.OrderServiceImpl"/>
|
||||
<!-- 2.声明切面 -->
|
||||
<bean name="myAdvice" class="com.heibaiying.advice.CustomAdvice"/>
|
||||
<!-- 3.配置将通知织入目标对象 -->
|
||||
<aop:config>
|
||||
<!--命名切入点 关于切入点更多表达式写法可以参见README.md-->
|
||||
<aop:pointcut expression="execution(* com.heibaiying.service.OrderService.*(..))" id="cutPoint"/>
|
||||
<aop:aspect ref="myAdvice">
|
||||
<!-- 前置通知 -->
|
||||
<aop:before method="before" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置通知 如果需要拿到返回值 则要指明返回值对应的参数名称-->
|
||||
<aop:after-returning method="afterReturning" pointcut-ref="cutPoint" returning="result"/>
|
||||
<!-- 环绕通知 -->
|
||||
<aop:around method="around" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置异常 如果需要拿到异常 则要指明异常对应的参数名称 -->
|
||||
<aop:after-throwing method="afterException" pointcut-ref="cutPoint" throwing="exception"/>
|
||||
<!-- 最终通知 -->
|
||||
<aop:after method="after" pointcut-ref="cutPoint"/>
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration("classpath:aop.xml")
|
||||
public class AopTest {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附: 关于切面表达式的说明
|
||||
|
||||
切面表达式遵循以下格式:
|
||||
|
||||
```shell
|
||||
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
```
|
||||
|
||||
- 除了返回类型模式,名字模式和参数模式以外,所有的部分都是可选的;
|
||||
- `*`,它代表了匹配任意的返回类型;
|
||||
- `()` 匹配了一个不接受任何参数的方法, 而 `(..)` 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 `(*)` 匹配了一个接受一个任何类型的参数的方法。 模式 `(*,String)` 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型。
|
||||
|
||||
下面给出一些常见切入点表达式的例子。
|
||||
|
||||
- 任意公共方法的执行:
|
||||
|
||||
```java
|
||||
execution(public * *(..))
|
||||
```
|
||||
|
||||
- 任何一个以“set”开始的方法的执行:
|
||||
|
||||
```java
|
||||
execution(* set*(..))
|
||||
```
|
||||
|
||||
- `AccountService` 接口的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
```
|
||||
|
||||
- 定义在service包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
```
|
||||
|
||||
- 定义在service包或者子包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
```
|
||||
|
||||
- 在service包里的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```java
|
||||
within(com.xyz.service.*)
|
||||
```
|
||||
|
||||
- 在service包或者子包里的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```
|
||||
within(com.xyz.service..*)
|
||||
```
|
||||
|
||||
- 实现了 `AccountService` 接口的代理对象的任意连接点(在Spring AOP中只是方法执行) :
|
||||
|
||||
```
|
||||
this(com.xyz.service.AccountService)
|
||||
```
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
切面配置位于 resources 下的 aop.xml 文件,其中 CustomAdvice 是自定义切面类,OrderService 是待切入的方法。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-aop.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 aop 依赖包
|
||||
|
||||
```xml
|
||||
<!--aop 相关依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aop</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring aop
|
||||
|
||||
#### 2.1 创建待切入接口及其实现类
|
||||
|
||||
```java
|
||||
public interface OrderService {
|
||||
|
||||
Order queryOrder(Long id);
|
||||
|
||||
Order createOrder(Long id, String productName);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class OrderServiceImpl implements OrderService {
|
||||
|
||||
public Order queryOrder(Long id) {
|
||||
return new Order(id, "product", new Date());
|
||||
}
|
||||
|
||||
public Order createOrder(Long id, String productName) {
|
||||
// 模拟抛出异常
|
||||
// int j = 1 / 0;
|
||||
return new Order(id, "new Product", new Date());
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建自定义切面类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义切面
|
||||
*/
|
||||
public class CustomAdvice {
|
||||
|
||||
|
||||
//前置通知
|
||||
public void before(JoinPoint joinPoint) {
|
||||
//获取节点名称
|
||||
String name = joinPoint.getSignature().getName();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
|
||||
}
|
||||
|
||||
//后置通知 (抛出异常后不会被执行)
|
||||
public void afterReturning(JoinPoint joinPoint, Object result) {
|
||||
System.out.println("后置返回通知结果" + result);
|
||||
}
|
||||
|
||||
//环绕通知
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
System.out.println("环绕通知-前");
|
||||
//调用目标方法
|
||||
Object proceed = joinPoint.proceed();
|
||||
System.out.println("环绕通知-后");
|
||||
return proceed;
|
||||
}
|
||||
|
||||
//异常通知
|
||||
public void afterException(JoinPoint joinPoint, Exception exception) {
|
||||
System.err.println("后置异常通知:" + exception);
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
// 后置通知 总会执行 但是不能访问到返回值
|
||||
public void after(JoinPoint joinPoint) {
|
||||
System.out.println("后置通知");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 配置切面
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
http://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!--开启后允许使用 Spring AOP 的@AspectJ 注解 如果是纯 xml 配置 可以不用开启这个声明-->
|
||||
<aop:aspectj-autoproxy/>
|
||||
|
||||
<!-- 1.配置目标对象 -->
|
||||
<bean name="orderService" class="com.heibaiying.service.OrderServiceImpl"/>
|
||||
<!-- 2.声明切面 -->
|
||||
<bean name="myAdvice" class="com.heibaiying.advice.CustomAdvice"/>
|
||||
<!-- 3.配置将通知织入目标对象 -->
|
||||
<aop:config>
|
||||
<!--命名切入点 关于切入点更多表达式写法可以参见 README.md-->
|
||||
<aop:pointcut expression="execution(* com.heibaiying.service.OrderService.*(..))" id="cutPoint"/>
|
||||
<aop:aspect ref="myAdvice">
|
||||
<!-- 前置通知 -->
|
||||
<aop:before method="before" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置通知 如果需要拿到返回值 则要指明返回值对应的参数名称-->
|
||||
<aop:after-returning method="afterReturning" pointcut-ref="cutPoint" returning="result"/>
|
||||
<!-- 环绕通知 -->
|
||||
<aop:around method="around" pointcut-ref="cutPoint"/>
|
||||
<!-- 后置异常 如果需要拿到异常 则要指明异常对应的参数名称 -->
|
||||
<aop:after-throwing method="afterException" pointcut-ref="cutPoint" throwing="exception"/>
|
||||
<!-- 最终通知 -->
|
||||
<aop:after method="after" pointcut-ref="cutPoint"/>
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 测试切面
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration("classpath:aop.xml")
|
||||
public class AopTest {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@Test
|
||||
public void save() {
|
||||
orderService.createOrder(1283929319L, "手机");
|
||||
orderService.queryOrder(4891894129L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附: 关于切面表达式的说明
|
||||
|
||||
切面表达式遵循以下格式:
|
||||
|
||||
```shell
|
||||
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
|
||||
throws-pattern?)
|
||||
```
|
||||
|
||||
- 除了返回类型模式,名字模式和参数模式以外,所有的部分都是可选的;
|
||||
- `*`,它代表了匹配任意的返回类型;
|
||||
- `()` 匹配了一个不接受任何参数的方法, 而 `(..)` 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 `(*)` 匹配了一个接受一个任何类型的参数的方法。 模式 `(*,String)` 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是 String 类型。
|
||||
|
||||
下面给出一些常见切入点表达式的例子。
|
||||
|
||||
- 任意公共方法的执行:
|
||||
|
||||
```java
|
||||
execution(public * *(..))
|
||||
```
|
||||
|
||||
- 任何一个以“set”开始的方法的执行:
|
||||
|
||||
```java
|
||||
execution(* set*(..))
|
||||
```
|
||||
|
||||
- `AccountService` 接口的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.AccountService.*(..))
|
||||
```
|
||||
|
||||
- 定义在 service 包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service.*.*(..))
|
||||
```
|
||||
|
||||
- 定义在 service 包或者子包里的任意方法的执行:
|
||||
|
||||
```java
|
||||
execution(* com.xyz.service..*.*(..))
|
||||
```
|
||||
|
||||
- 在 service 包里的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```java
|
||||
within(com.xyz.service.*)
|
||||
```
|
||||
|
||||
- 在 service 包或者子包里的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```
|
||||
within(com.xyz.service..*)
|
||||
```
|
||||
|
||||
- 实现了 `AccountService` 接口的代理对象的任意连接点(在 Spring AOP 中只是方法执行) :
|
||||
|
||||
```
|
||||
this(com.xyz.service.AccountService)
|
||||
```
|
||||
|
||||
更多表达式可以参考官方文档:[Declaring a Pointcut](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#aop-pointcuts)
|
||||
|
@ -76,7 +76,7 @@ public class DispatcherServletInitializer extends AbstractAnnotationConfigDispat
|
||||
|
||||
#### 3、基于servlet 3.0的支持,可以采用注解的方式注册druid的servlet和filter
|
||||
|
||||
注:关于servlet 更多注解支持可以查看[Servlet 规范文档](https://github.com/heibaiying/spring-samples-for-all/blob/master/referenced%20documents/Servlet3.1%E8%A7%84%E8%8C%83%EF%BC%88%E6%9C%80%E7%BB%88%E7%89%88%EF%BC%89.pdf)中**8.1小节 注解和可插拔性**
|
||||
注:关于 servlet 更多注解支持可以查看[Servlet 规范文档](https://github.com/heibaiying/spring-samples-for-all/blob/master/referenced%20documents/Servlet3.1%E8%A7%84%E8%8C%83%EF%BC%88%E6%9C%80%E7%BB%88%E7%89%88%EF%BC%89.pdf) 中**8.1 小节 注解和可插拔性**
|
||||
|
||||
```java
|
||||
/**
|
||||
@ -98,7 +98,7 @@ public class DruidStatViewServlet extends StatViewServlet {
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : WebStatFilter用于采集web-jdbc关联监控的数据
|
||||
* @description : WebStatFilter 用于采集 web-jdbc 关联监控的数据
|
||||
*/
|
||||
@WebFilter(filterName="druidWebStatFilter",urlPatterns="/*",
|
||||
initParams={
|
||||
@ -124,7 +124,7 @@ mysql.password=root
|
||||
```properties
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
@ -157,7 +157,7 @@ public class DataSourceConfig {
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于xml中<tx:annotation-driven/>
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于 xml 中<tx:annotation-driven/>
|
||||
@EnableWebMvc
|
||||
@ComponentScan(basePackages = {"com.heibaiying.*"})
|
||||
public class ServletConfig implements WebMvcConfigurer {
|
||||
@ -170,7 +170,7 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置druid 数据源
|
||||
* 配置 druid 数据源
|
||||
*/
|
||||
@Bean
|
||||
public DruidDataSource dataSource(DataSourceConfig sourceConfig) throws SQLException {
|
||||
@ -189,24 +189,24 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
dataSource.setMinEvictableIdleTimeMillis(600000);
|
||||
dataSource.setMaxEvictableIdleTimeMillis(900000);
|
||||
|
||||
/* validationQuery 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。
|
||||
/* validationQuery 用来检测连接是否有效的 sql,要求是一个查询语句,常用 select 'x'。
|
||||
* 但是在 oracle 数据库下需要写成 select 'x' from dual 不然实例化数据源的时候就会失败,
|
||||
* 这是由于oracle 和 mysql 语法间的差异造成的
|
||||
* 这是由于 oracle 和 mysql 语法间的差异造成的
|
||||
*/
|
||||
dataSource.setValidationQuery("select 'x'");
|
||||
|
||||
// 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
|
||||
// 建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
|
||||
dataSource.setTestWhileIdle(true);
|
||||
// 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
|
||||
// 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
|
||||
dataSource.setTestOnBorrow(false);
|
||||
// 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
|
||||
// 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能
|
||||
dataSource.setTestOnReturn(false);
|
||||
|
||||
// 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。
|
||||
// 连接池中的 minIdle 数量以内的连接,空闲时间超过 minEvictableIdleTimeMillis,则会执行 keepAlive 操作。
|
||||
dataSource.setPhyMaxUseCount(100000);
|
||||
|
||||
/*配置监控统计拦截的filters Druid连接池的监控信息主要是通过StatFilter 采集的,
|
||||
采集的信息非常全面,包括SQL执行、并发、慢查、执行时间区间分布等*/
|
||||
/*配置监控统计拦截的 filters Druid 连接池的监控信息主要是通过 StatFilter 采集的,
|
||||
采集的信息非常全面,包括 SQL 执行、并发、慢查、执行时间区间分布等*/
|
||||
dataSource.setFilters("stat");
|
||||
|
||||
return dataSource;
|
||||
@ -214,10 +214,10 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
|
||||
|
||||
/**
|
||||
* 配置mybatis 会话工厂
|
||||
* 配置 mybatis 会话工厂
|
||||
*
|
||||
* @param dataSource 这个参数的名称需要保持和上面方法名一致 才能自动注入,因为
|
||||
* 采用@Bean注解生成的bean 默认采用方法名为名称,当然也可以在使用@Bean时指定name属性
|
||||
* 采用@Bean 注解生成的 bean 默认采用方法名为名称,当然也可以在使用@Bean 时指定 name 属性
|
||||
*/
|
||||
@Bean
|
||||
public SqlSessionFactoryBean sessionFactoryBean(DruidDataSource dataSource) throws IOException {
|
||||
@ -230,7 +230,7 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置mybatis 会话工厂
|
||||
* 配置 mybatis 会话工厂
|
||||
*/
|
||||
@Bean
|
||||
public MapperScannerConfigurer MapperScannerConfigurer() {
|
||||
@ -267,7 +267,7 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询sql -->
|
||||
<!-- 打印查询 sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
@ -358,6 +358,6 @@ public class OracleController {
|
||||
|
||||
#### 10、druid 监控页面访问地址http://localhost:8080/druid/index.html
|
||||
|
||||

|
||||

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

|
||||

|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 dubbo(注解方式)
|
||||
|
||||
# spring 整合 dubbo(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
|
||||
<a href="#二项目依赖">二、项目依赖</a><br/>
|
||||
<a href="#三公共模块dubbo-ano-common">三、公共模块(dubbo-ano-common)</a><br/>
|
||||
@ -14,328 +15,328 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo-annotation下构建三个子模块:
|
||||
|
||||
1. dubbo-ano-common 是公共模块,用于存放公共的接口和bean,被dubbo-ano-provider和dubbo-ano-provider在pom.xml中引用;
|
||||
2. dubbo-ano-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-ano-provider 是服务的消费者,调用provider提供的查询服务。
|
||||
|
||||
1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中统一导入依赖dubbo依赖的的jar包**
|
||||
|
||||
这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-ano-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(dubbo-ano-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-provider.png"/> </div>
|
||||
|
||||
#### 4.1 提供方配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 提供方应用信息,用于计算依赖关系
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-provider");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用zookeeper注册中心暴露服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用dubbo协议在20880端口暴露服务
|
||||
*/
|
||||
@Bean
|
||||
public ProtocolConfig protocolConfig() {
|
||||
ProtocolConfig protocolConfig = new ProtocolConfig();
|
||||
protocolConfig.setName("dubbo");
|
||||
protocolConfig.setPort(20880);
|
||||
return protocolConfig;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 使用注解@Service暴露服务
|
||||
|
||||
需要注意的是这里的@Service注解不是spring的注解,而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
|
||||
|
||||
```java
|
||||
package com.heibaiying.service;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Service;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service(timeout = 5000)
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 五、服务消费者(dubbo-ano-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-consumer.png"/> </div>
|
||||
|
||||
#### 1.消费方的配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-consumer");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置调用服务超时时间
|
||||
* 关闭所有服务的启动时检查
|
||||
*/
|
||||
@Bean
|
||||
public ConsumerConfig consumerConfig() {
|
||||
ConsumerConfig consumerConfig = new ConsumerConfig();
|
||||
consumerConfig.setTimeout(3000);
|
||||
consumerConfig.setCheck(false);
|
||||
return consumerConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用zookeeper注册中心暴露发现服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.使用注解@Reference引用远程服务
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
// dubbo远程引用注解
|
||||
@Reference
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer中 pom.xml如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider中 pom.xml如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照 dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用 maven 多模块的构建方式,在 spring-dubbo-annotation 下构建三个子模块:
|
||||
|
||||
1. dubbo-ano-common 是公共模块,用于存放公共的接口和 bean,被 dubbo-ano-provider 和 dubbo-ano-provider 在 pom.xml 中引用;
|
||||
2. dubbo-ano-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-ano-provider 是服务的消费者,调用 provider 提供的查询服务。
|
||||
|
||||
1.2 本项目 dubbo 的搭建采用 zookeeper 作为注册中心, 关于 zookeeper 的安装和基本操作可以参见我的手记[Zookeeper 基础命令与 Java 客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中统一导入依赖 dubbo 依赖的的 jar 包**
|
||||
|
||||
这里需要注意的是 ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是 beta 版本,所以 zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework 自动引用的最新的 3.5 的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-ano-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(dubbo-ano-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-provider.png"/> </div>
|
||||
|
||||
#### 4.1 提供方配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 提供方应用信息,用于计算依赖关系
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-provider");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 zookeeper 注册中心暴露服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用 dubbo 协议在 20880 端口暴露服务
|
||||
*/
|
||||
@Bean
|
||||
public ProtocolConfig protocolConfig() {
|
||||
ProtocolConfig protocolConfig = new ProtocolConfig();
|
||||
protocolConfig.setName("dubbo");
|
||||
protocolConfig.setPort(20880);
|
||||
return protocolConfig;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 使用注解@Service暴露服务
|
||||
|
||||
需要注意的是这里的@Service 注解不是 spring 的注解,而是 dubbo 的注解 com.alibaba.dubbo.config.annotation.Service
|
||||
|
||||
```java
|
||||
package com.heibaiying.service;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Service;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service(timeout = 5000)
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 五、服务消费者(dubbo-ano-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-ano-consumer.png"/> </div>
|
||||
|
||||
#### 1.消费方的配置
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class DubboConfiguration {
|
||||
|
||||
/**
|
||||
* 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
|
||||
*/
|
||||
@Bean
|
||||
public ApplicationConfig applicationConfig() {
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig();
|
||||
applicationConfig.setName("dubbo-ano-consumer");
|
||||
return applicationConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置调用服务超时时间
|
||||
* 关闭所有服务的启动时检查
|
||||
*/
|
||||
@Bean
|
||||
public ConsumerConfig consumerConfig() {
|
||||
ConsumerConfig consumerConfig = new ConsumerConfig();
|
||||
consumerConfig.setTimeout(3000);
|
||||
consumerConfig.setCheck(false);
|
||||
return consumerConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 zookeeper 注册中心暴露发现服务地址
|
||||
*/
|
||||
@Bean
|
||||
public RegistryConfig registryConfig() {
|
||||
RegistryConfig registryConfig = new RegistryConfig();
|
||||
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
|
||||
registryConfig.setClient("curator");
|
||||
return registryConfig;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.使用注解@Reference引用远程服务
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import com.heibaiying.api.IProductService;
|
||||
import com.heibaiying.bean.Product;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
// dubbo 远程引用注解
|
||||
@Reference
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer 和 provider 模块均依赖公共模块,所以在构建 consumer 和 provider 项目前需要将 common 模块安装到本地仓库,**依次**对**父工程**和**common 模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer 中 pom.xml 如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider 中 pom.xml 如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo-annotation</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-ano-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-ano-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是 127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
```
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 dubbo(xml配置方式)
|
||||
|
||||
# spring 整合 dubbo(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
|
||||
<a href="#二项目依赖">二、项目依赖</a><br/>
|
||||
<a href="#三公共模块dubbo-common">三、公共模块(dubbo-common)</a><br/>
|
||||
@ -14,280 +15,280 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo下构建三个子模块:
|
||||
|
||||
1. dubbo-common 是公共模块,用于存放公共的接口和bean,被dubbo-provider和dubbo-provider在pom.xml中引用;
|
||||
2. dubbo-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-provider 是服务的消费者,调用provider提供的查询服务。
|
||||
|
||||
1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中统一导入依赖dubbo依赖的的jar包**
|
||||
|
||||
这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(dubbo-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-provider.png"/> </div>
|
||||
|
||||
#### 4.1 productService是服务的提供者( 商品数据用模拟数据展示)
|
||||
|
||||
注:这里实现的接口IProductService来源于公共模块
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 在dubbo.xml暴露服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 提供方应用信息,用于计算依赖关系 -->
|
||||
<dubbo:application name="dubbo-provider"/>
|
||||
|
||||
<!-- 使用zookeeper注册中心暴露服务地址 如果是集群配置 用, 分隔地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 用dubbo协议在20880端口暴露服务 -->
|
||||
<dubbo:protocol name="dubbo" port="20880"/>
|
||||
|
||||
<!-- 声明需要暴露的服务接口 -->
|
||||
<dubbo:service interface="com.heibaiying.api.IProductService" ref="productService"/>
|
||||
|
||||
<!-- 和本地bean一样实现服务 -->
|
||||
<bean id="productService" class="com.heibaiying.service.ProductService"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
## 五、服务消费者(dubbo-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-consumer.png"/> </div>
|
||||
|
||||
#### 1.在dubbo.xml调用远程的服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
|
||||
<dubbo:application name="dubbo-consumer">
|
||||
<dubbo:parameter key="shutdown.timeout" value="60000"/> <!-- 单位毫秒 -->
|
||||
</dubbo:application>
|
||||
|
||||
<!--Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"。-->
|
||||
<!--可以关闭所有服务的启动时检查 -->
|
||||
<dubbo:consumer check="false" />
|
||||
|
||||
<!-- 使用zookeeper注册中心暴露发现服务地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
|
||||
<dubbo:reference id="sellService" interface="com.heibaiying.api.IProductService"/>
|
||||
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.消费服务
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
@Autowired
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer中 pom.xml如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider中 pom.xml如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照 dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用 maven 多模块的构建方式,在 spring-dubbo 下构建三个子模块:
|
||||
|
||||
1. dubbo-common 是公共模块,用于存放公共的接口和 bean,被 dubbo-provider 和 dubbo-provider 在 pom.xml 中引用;
|
||||
2. dubbo-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-provider 是服务的消费者,调用 provider 提供的查询服务。
|
||||
|
||||
1.2 本项目 dubbo 的搭建采用 zookeeper 作为注册中心, 关于 zookeeper 的安装和基本操作可以参见我的手记[Zookeeper 基础命令与 Java 客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-dubbo.png"/> </div>
|
||||
|
||||
|
||||
|
||||
## 二、项目依赖
|
||||
|
||||
**在父工程的项目中统一导入依赖 dubbo 依赖的的 jar 包**
|
||||
|
||||
这里需要注意的是 ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是 beta 版本,所以 zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework 自动引用的最新的 3.5 的版本客户端,会出现 KeeperException$UnimplementedException 异常
|
||||
|
||||
```xml
|
||||
<!--dubbo 依赖-->
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>dubbo</artifactId>
|
||||
<version>2.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.zookeeper</groupId>
|
||||
<artifactId>zookeeper</artifactId>
|
||||
<version>3.4.13</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、公共模块(dubbo-common)
|
||||
|
||||
- api 下为公共的调用接口;
|
||||
- bean 下为公共的实体类。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-common.png"/> </div>
|
||||
|
||||
## 四、 服务提供者(dubbo-provider)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-provider.png"/> </div>
|
||||
|
||||
#### 4.1 productService是服务的提供者( 商品数据用模拟数据展示)
|
||||
|
||||
注:这里实现的接口 IProductService 来源于公共模块
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品提供接口实现类
|
||||
*/
|
||||
@Service
|
||||
public class ProductService implements IProductService {
|
||||
|
||||
private static List<Product> productList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
|
||||
}
|
||||
}
|
||||
|
||||
public Product queryProductById(int id) {
|
||||
for (Product product : productList) {
|
||||
if (product.getId() == id) {
|
||||
return product;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public List<Product> queryAllProducts() {
|
||||
return productList;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 在dubbo.xml暴露服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 提供方应用信息,用于计算依赖关系 -->
|
||||
<dubbo:application name="dubbo-provider"/>
|
||||
|
||||
<!-- 使用 zookeeper 注册中心暴露服务地址 如果是集群配置 用, 分隔地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 用 dubbo 协议在 20880 端口暴露服务 -->
|
||||
<dubbo:protocol name="dubbo" port="20880"/>
|
||||
|
||||
<!-- 声明需要暴露的服务接口 -->
|
||||
<dubbo:service interface="com.heibaiying.api.IProductService" ref="productService"/>
|
||||
|
||||
<!-- 和本地 bean 一样实现服务 -->
|
||||
<bean id="productService" class="com.heibaiying.service.ProductService"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
## 五、服务消费者(dubbo-consumer)
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/dubbo-consumer.png"/> </div>
|
||||
|
||||
#### 1.在dubbo.xml调用远程的服务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
|
||||
http://dubbo.apache.org/schema/dubbo
|
||||
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
|
||||
|
||||
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
|
||||
<dubbo:application name="dubbo-consumer">
|
||||
<dubbo:parameter key="shutdown.timeout" value="60000"/> <!-- 单位毫秒 -->
|
||||
</dubbo:application>
|
||||
|
||||
<!--Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"。-->
|
||||
<!--可以关闭所有服务的启动时检查 -->
|
||||
<dubbo:consumer check="false" />
|
||||
|
||||
<!-- 使用 zookeeper 注册中心暴露发现服务地址 -->
|
||||
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
|
||||
|
||||
<!-- 生成远程服务代理,可以和本地 bean 一样使用 demoService -->
|
||||
<dubbo:reference id="sellService" interface="com.heibaiying.api.IProductService"/>
|
||||
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.消费服务
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@RequestMapping("sell")
|
||||
public class SellController {
|
||||
|
||||
@Autowired
|
||||
private IProductService productService;
|
||||
|
||||
@RequestMapping
|
||||
public String productList(Model model) {
|
||||
List<Product> products = productService.queryAllProducts();
|
||||
model.addAttribute("products", products);
|
||||
return "products";
|
||||
}
|
||||
|
||||
@RequestMapping("product/{id}")
|
||||
public String productDetail(@PathVariable int id, Model model) {
|
||||
Product product = productService.queryProductById(id);
|
||||
model.addAttribute("product", product);
|
||||
return "product";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 六、项目构建的说明
|
||||
|
||||
因为在项目中,consumer 和 provider 模块均依赖公共模块,所以在构建 consumer 和 provider 项目前需要将 common 模块安装到本地仓库,**依次**对**父工程**和**common 模块**执行:
|
||||
|
||||
```shell
|
||||
mvn install -Dmaven.test.skip = true
|
||||
```
|
||||
|
||||
consumer 中 pom.xml 如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-consumer</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
provider 中 pom.xml 如下
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>spring-dubbo</artifactId>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>dubbo-provider</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
## 七、关于dubbo新版本管理控制台的安装说明
|
||||
|
||||
安装:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
|
||||
cd /var/tmp/dubbo-ops
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
配置:
|
||||
|
||||
```sh
|
||||
配置文件为:
|
||||
dubbo-admin-backend/src/main/resources/application.properties
|
||||
主要的配置有 默认的配置就是 127.0.0.1:2181:
|
||||
dubbo.registry.address=zookeeper://127.0.0.1:2181
|
||||
```
|
||||
|
||||
启动:
|
||||
|
||||
```sh
|
||||
mvn --projects dubbo-admin-backend spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://127.0.0.1:8080
|
||||
```
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -10,288 +11,288 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置类为com.heibaiying.config下EmailConfig.java;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为SendEmail。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-email-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是beetl,beetl性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.email")
|
||||
public class EmailConfig {
|
||||
|
||||
/***
|
||||
* 在这里可以声明不同的邮件服务器主机,通常是SMTP主机,而具体的用户名和时授权码则建议在业务中从数据库查询
|
||||
*/
|
||||
@Bean(name = "qqMailSender")
|
||||
JavaMailSenderImpl javaMailSender() {
|
||||
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
|
||||
javaMailSender.setHost("smtp.qq.com");
|
||||
javaMailSender.setPassword("587");
|
||||
return javaMailSender;
|
||||
}
|
||||
|
||||
/***
|
||||
* 配置模板引擎
|
||||
*/
|
||||
@Bean
|
||||
GroupTemplate groupTemplate() throws IOException {
|
||||
//指定加载模板资源的位置 指定在classpath:beetl下-
|
||||
ClasspathResourceLoader loader = new ClasspathResourceLoader("beetl");
|
||||
//beetl配置 这里采用默认的配置-
|
||||
org.beetl.core.Configuration configuration = org.beetl.core.Configuration.defaultConfiguration();
|
||||
return new GroupTemplate(loader, configuration);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板template.html如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = EmailConfig.class)
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
|
||||
springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置类为 com.heibaiying.config 下 EmailConfig.java;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在 SpringMail 类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为 SendEmail。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-email-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入邮件发送的支持包 spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是 beetl,beetl 性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.email")
|
||||
public class EmailConfig {
|
||||
|
||||
/***
|
||||
* 在这里可以声明不同的邮件服务器主机,通常是 SMTP 主机,而具体的用户名和时授权码则建议在业务中从数据库查询
|
||||
*/
|
||||
@Bean(name = "qqMailSender")
|
||||
JavaMailSenderImpl javaMailSender() {
|
||||
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
|
||||
javaMailSender.setHost("smtp.qq.com");
|
||||
javaMailSender.setPassword("587");
|
||||
return javaMailSender;
|
||||
}
|
||||
|
||||
/***
|
||||
* 配置模板引擎
|
||||
*/
|
||||
@Bean
|
||||
GroupTemplate groupTemplate() throws IOException {
|
||||
//指定加载模板资源的位置 指定在 classpath:beetl 下-
|
||||
ClasspathResourceLoader loader = new ClasspathResourceLoader("beetl");
|
||||
//beetl 配置 这里采用默认的配置-
|
||||
org.beetl.core.Configuration configuration = org.beetl.core.Configuration.defaultConfiguration();
|
||||
return new GroupTemplate(loader, configuration);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在 qq 邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为 true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为 true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用 true 标志来指示包含的文本是 HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为 true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的 beetl 模板引擎,原因是其性能优异,官网是介绍其性能 6 倍与 freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板 template.html 如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = EmailConfig.class)
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
|
||||
springMail.sendTextMessage(from, authWord, to, "spring 简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm 相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ 模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring 多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring 内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ 模型架构.png"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring 模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -10,299 +11,299 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置文件为springApplication.xml;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为SendEmail。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-email.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是beetl,beetl性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.email"/>
|
||||
|
||||
<!--在这里可以声明不同的邮件服务器主机,通常是SMTP主机,而具体的用户名和时授权码则建议在业务中从数据库查询-->
|
||||
<bean id="qqMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
||||
<!--qq 邮箱配置 <a href="https://service.mail.qq.com/cgi-bin/help?subtype=1&no=167&id=28"> -->
|
||||
<property name="host" value="smtp.qq.com"/>
|
||||
<property name="port" value="587"/>
|
||||
</bean>
|
||||
|
||||
<!--配置beetle模板引擎 如果不使用模板引擎,以下的配置不是必须的-->
|
||||
<bean id="resourceLoader" class="org.beetl.core.resource.ClasspathResourceLoader">
|
||||
<!--指定加载模板资源的位置 指定在classpath:beetl下-->
|
||||
<constructor-arg name="root" value="beetl"/>
|
||||
</bean>
|
||||
<!--beetl 配置 这里采用默认的配置-->
|
||||
<bean id="configuration" class="org.beetl.core.Configuration" init-method="defaultConfiguration"/>
|
||||
<bean id="groupTemplate" class="org.beetl.core.GroupTemplate">
|
||||
<constructor-arg name="loader" ref="resourceLoader"/>
|
||||
<constructor-arg name="conf" ref="configuration"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板template.html如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
|
||||
/**
|
||||
* 简单邮件测试
|
||||
*/
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送内嵌资源的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送模板邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置文件为 springApplication.xml;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在 SpringMail 类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为 SendEmail。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-email.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入邮件发送的支持包 spring-context-support
|
||||
|
||||
```xml
|
||||
<!--邮件发送依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<!--模板引擎-->
|
||||
<!--这里采用的是 beetl,beetl 性能很卓越并且功能也很全面 官方文档地址 <a href="http://ibeetl.com/guide/#beetl">-->
|
||||
<dependency>
|
||||
<groupId>com.ibeetl</groupId>
|
||||
<artifactId>beetl</artifactId>
|
||||
<version>2.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring email
|
||||
|
||||
#### 2.1 邮件发送配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.email"/>
|
||||
|
||||
<!--在这里可以声明不同的邮件服务器主机,通常是 SMTP 主机,而具体的用户名和时授权码则建议在业务中从数据库查询-->
|
||||
<bean id="qqMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
|
||||
<!--qq 邮箱配置 <a href="https://service.mail.qq.com/cgi-bin/help?subtype=1&no=167&id=28"> -->
|
||||
<property name="host" value="smtp.qq.com"/>
|
||||
<property name="port" value="587"/>
|
||||
</bean>
|
||||
|
||||
<!--配置 beetle 模板引擎 如果不使用模板引擎,以下的配置不是必须的-->
|
||||
<bean id="resourceLoader" class="org.beetl.core.resource.ClasspathResourceLoader">
|
||||
<!--指定加载模板资源的位置 指定在 classpath:beetl 下-->
|
||||
<constructor-arg name="root" value="beetl"/>
|
||||
</bean>
|
||||
<!--beetl 配置 这里采用默认的配置-->
|
||||
<bean id="configuration" class="org.beetl.core.Configuration" init-method="defaultConfiguration"/>
|
||||
<bean id="groupTemplate" class="org.beetl.core.GroupTemplate">
|
||||
<constructor-arg name="loader" ref="resourceLoader"/>
|
||||
<constructor-arg name="conf" ref="configuration"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 新建邮件发送基本类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 邮件发送基本类
|
||||
*/
|
||||
@Component
|
||||
public class SpringMail {
|
||||
|
||||
@Autowired
|
||||
private JavaMailSenderImpl qqMailSender;
|
||||
@Autowired
|
||||
private GroupTemplate groupTemplate;
|
||||
|
||||
/**
|
||||
* 发送简单邮件
|
||||
* 在 qq 邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
|
||||
* 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
|
||||
* qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务 中开启服务后获取
|
||||
*/
|
||||
public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
SimpleMailMessage msg = new SimpleMailMessage();
|
||||
msg.setFrom(from);
|
||||
msg.setTo(to);
|
||||
msg.setSubject(subject);
|
||||
msg.setText(content);
|
||||
try {
|
||||
// 发送消息
|
||||
this.qqMailSender.send(msg);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MailException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
public void sendEmailWithAttachments(String from, String authWord, String to,
|
||||
String subject, String content, Map<String, File> files) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为 true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
helper.setText(content);
|
||||
// 传入附件
|
||||
for (Map.Entry<String, File> entry : files.entrySet()) {
|
||||
helper.addAttachment(entry.getKey(), entry.getValue());
|
||||
}
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送带内嵌资源的邮件
|
||||
*/
|
||||
public void sendEmailWithInline(String from, String authWord, String to,
|
||||
String subject, String content, File file) {
|
||||
try {
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 需要指定第二个参数为 true 代表创建支持可选文本,内联元素和附件的多部分消息
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 使用 true 标志来指示包含的文本是 HTML 固定格式资源前缀 cid:
|
||||
helper.setText("<html><body><img src='cid:image'></body></html>", true);
|
||||
// 需要先指定文本 再指定资源文件
|
||||
FileSystemResource res = new FileSystemResource(file);
|
||||
helper.addInline("image", res);
|
||||
// 发送消息
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用模板邮件
|
||||
*/
|
||||
public void sendEmailByTemplate(String from, String authWord, String to,
|
||||
String subject, String content) {
|
||||
try {
|
||||
Template t = groupTemplate.getTemplate("template.html");
|
||||
t.binding("subject", subject);
|
||||
t.binding("content", content);
|
||||
String text = t.render();
|
||||
// 设置发送人邮箱和授权码
|
||||
qqMailSender.setUsername(from);
|
||||
qqMailSender.setPassword(authWord);
|
||||
// 实例化消息对象
|
||||
MimeMessage message = qqMailSender.createMimeMessage();
|
||||
// 指定 utf-8 防止乱码
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
|
||||
helper.setFrom(from);
|
||||
helper.setTo(to);
|
||||
helper.setSubject(subject);
|
||||
// 为 true 时候 表示文本内容以 html 渲染
|
||||
helper.setText(text, true);
|
||||
this.qqMailSender.send(message);
|
||||
System.out.println("发送邮件成功");
|
||||
} catch (MessagingException ex) {
|
||||
// 消息发送失败可以做对应的处理
|
||||
System.err.println("发送邮件失败" + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**关于模板邮件的说明:**
|
||||
|
||||
- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
|
||||
|
||||
- 这里我们使用的 beetl 模板引擎,原因是其性能优异,官网是介绍其性能 6 倍与 freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
|
||||
|
||||
一个简单的模板 template.html 如下:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<h1>邮件主题:<span style="color: chartreuse"> ${subject}</span></h1>
|
||||
<h4 style="color: blueviolet">${content}</h4>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2.3 邮件发送的测试
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 发送邮件测试类
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class SendEmail {
|
||||
|
||||
@Autowired
|
||||
private SpringMail springMail;
|
||||
|
||||
// 发送方邮箱地址
|
||||
private static final String from = "发送方邮箱地址@qq.com";
|
||||
// 发送方邮箱地址对应的授权码
|
||||
private static final String authWord = "授权码";
|
||||
// 接收方邮箱地址
|
||||
private static final String to = "接收方邮箱地址@qq.com";
|
||||
|
||||
|
||||
/**
|
||||
* 简单邮件测试
|
||||
*/
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
springMail.sendTextMessage(from, authWord, to, "spring 简单邮件", "Hello Spring Email!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送带附件的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendComplexMessage() {
|
||||
Map<String, File> fileMap = new HashMap<>();
|
||||
fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm 相关依赖.png"));
|
||||
fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ 模型架构.png"));
|
||||
springMail.sendEmailWithAttachments(from, authWord, to, "spring 多附件邮件"
|
||||
, "Hello Spring Email!", fileMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送内嵌资源的邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailWithInline() {
|
||||
springMail.sendEmailWithInline(from, authWord, to, "spring 内嵌资源邮件"
|
||||
, "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ 模型架构.png"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送模板邮件
|
||||
*/
|
||||
@Test
|
||||
public void sendEmailByTemplate() {
|
||||
springMail.sendEmailByTemplate(from, authWord, to,
|
||||
"spring 模板邮件", "Hello Spring Email!");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
#### 1.1 项目目录结构
|
||||
|
||||
1. 数据源配置位于config目录下的DatabaseConfig.java和DataSourceConfig.java
|
||||
1. 数据源配置位于 config 目录下的 DatabaseConfig.java 和 DataSourceConfig.java
|
||||
2. 项目以单元测试的方法进行测试
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-jdbc-annotation.png"/> </div>
|
||||
@ -59,7 +59,7 @@ mysql.password=root
|
||||
```properties
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
@ -87,7 +87,7 @@ public class DataSourceConfig {
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于xml中<tx:annotation-driven/>
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于 xml 中<tx:annotation-driven/>
|
||||
@ComponentScan(basePackages = {"com.heibaiying.*"})
|
||||
public class DatabaseConfig {
|
||||
|
||||
@ -106,9 +106,9 @@ public class DatabaseConfig {
|
||||
|
||||
|
||||
/**
|
||||
* 配置jdbcTemplate
|
||||
* 配置 jdbcTemplate
|
||||
* @param dataSource 这个参数的名称需要保持和上面方法名一致 才能自动注入,因为
|
||||
* 采用@Bean注解生成的bean 默认采用方法名为名称,当然也可以在使用@Bean时指定name属性
|
||||
* 采用@Bean 注解生成的 bean 默认采用方法名为名称,当然也可以在使用@Bean 时指定 name 属性
|
||||
*/
|
||||
@Bean
|
||||
public JdbcTemplate jdbcTemplate(DriverManagerDataSource dataSource) {
|
||||
@ -141,7 +141,7 @@ public class MysqlDaoImpl implements MysqlDao {
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 更多JDBC 的使用可以参考官方文档
|
||||
* 更多 JDBC 的使用可以参考官方文档
|
||||
* @see <a href="https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#jdbc-JdbcTemplate">JdbcTemplate</a>
|
||||
*/
|
||||
public List<Relation> get() {
|
||||
@ -171,7 +171,7 @@ public class OracleDaoImpl implements OracleDao {
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 更多JDBC 的使用可以参考官方文档
|
||||
* 更多 JDBC 的使用可以参考官方文档
|
||||
* @see <a href="https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#jdbc-JdbcTemplate">JdbcTemplate</a>
|
||||
*/
|
||||
public List<Flow> get() {
|
||||
|
@ -55,7 +55,7 @@ mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
@ -102,7 +102,7 @@ oracle.password=密码
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- 开启事务注解@Transactional支持 -->
|
||||
<!-- 开启事务注解@Transactional 支持 -->
|
||||
<tx:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
@ -118,7 +118,7 @@ public class MysqlDaoImpl implements MysqlDao {
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 更多JDBC 的使用可以参考官方文档
|
||||
* 更多 JDBC 的使用可以参考官方文档
|
||||
* @see <a href="https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#jdbc-JdbcTemplate">JdbcTemplate</a>
|
||||
*/
|
||||
public List<Relation> get() {
|
||||
@ -146,7 +146,7 @@ public class OracleDaoImpl implements OracleDao {
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 更多JDBC 的使用可以参考官方文档
|
||||
* 更多 JDBC 的使用可以参考官方文档
|
||||
* @see <a href="https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#jdbc-JdbcTemplate">JdbcTemplate</a>
|
||||
*/
|
||||
public List<Flow> get() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 mecached(注解方式)
|
||||
|
||||
# spring 整合 mecached(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11--XMemcached客户端说明">1.1 XMemcached客户端说明</a><br/>
|
||||
<a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
|
||||
@ -13,151 +14,151 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached的整合配置位于com.heibaiying.config文件夹下。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-memcached-annotation.png"/> </div>
|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入xmemcached依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClient() {
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClientForCluster() {
|
||||
|
||||
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11211));
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
|
||||
// 赋予权重
|
||||
int[] weights = {1, 2};
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
|
||||
// 设置连接池大小
|
||||
builder.setConnectionPoolSize(10);
|
||||
// 协议工厂
|
||||
builder.setCommandFactory(new TextCommandFactory());
|
||||
// 分布策略,一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
|
||||
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
|
||||
// 设置序列化器
|
||||
builder.setTranscoder(new SerializingTranscoder());
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
|
||||
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached 是基于 java nio 的 memcached 高性能客户端,支持完整的 memcached 协议,支持客户端分布并且提供了一致性哈希 (consistent hash) 算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached 的整合配置位于 com.heibaiying.config 文件夹下。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-memcached-annotation.png"/> </div>
|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 xmemcached 依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClient() {
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MemcachedClient memcachedClientForCluster() {
|
||||
|
||||
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>();
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11211));
|
||||
addressList.add(new InetSocketAddress("192.168.200.201", 11212));
|
||||
// 赋予权重
|
||||
int[] weights = {1, 2};
|
||||
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
|
||||
// 设置连接池大小
|
||||
builder.setConnectionPoolSize(10);
|
||||
// 协议工厂
|
||||
builder.setCommandFactory(new TextCommandFactory());
|
||||
// 分布策略,一致性哈希 KetamaMemcachedSessionLocator 或者 ArraySessionLocator(默认)
|
||||
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
|
||||
// 设置序列化器
|
||||
builder.setTranscoder(new SerializingTranscoder());
|
||||
MemcachedClient memcachedClient = null;
|
||||
try {
|
||||
memcachedClient = builder.build();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return memcachedClient;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached 单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {MemcacheConfig.class})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当 key 不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在 key 的 value 值 |
|
||||
| 追加内容 append | append key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 前面追加 prepend | prepend key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr 和 decr 只能操作能转换为数字的 Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr 不能将数字减少至 0 以下 |
|
||||
| 清库 | flush_all | |
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 mecached(xml配置方式)
|
||||
|
||||
# spring 整合 mecached(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11--XMemcached客户端说明">1.1 XMemcached客户端说明</a><br/>
|
||||
<a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
|
||||
@ -13,192 +14,192 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached的整合配置位于resources下的memcached文件夹下,其中集群配置用cluster开头。所有配置按照需要在springApplication.xml用import导入。
|
||||
2. 实体类Programmer.java用于测试memcached序列化与反序列化
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-memcached.png"/> </div>
|
||||
|
||||
**springapplication.xml文件:**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--memcached 单机版配置-->
|
||||
<!--<import resource="classpath*:memcached/singleConfig.xml"/>-->
|
||||
|
||||
<!--memcached 集群配置-->
|
||||
<import resource="classpath*:memcached/clusterConfig.xml"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入xmemcached依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<constructor-arg name="addressList" value="192.168.200.201:11211"/>
|
||||
</bean>
|
||||
|
||||
<bean id="memcachedClient" factory-bean="memcachedClientBuilder" factory-method="build"
|
||||
destroy-method="shutdown"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean name="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<!--memcached servers 节点列表-->
|
||||
<constructor-arg name="addressList">
|
||||
<list>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11211"/>
|
||||
</bean>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11212"/>
|
||||
</bean>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--与servers对应的节点的权重-->
|
||||
<constructor-arg name="weights">
|
||||
<list>
|
||||
<value>1</value>
|
||||
<value>2</value>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--连接池大小-->
|
||||
<property name="connectionPoolSize" value="10"/>
|
||||
<!--协议工厂-->
|
||||
<property name="commandFactory">
|
||||
<bean class="net.rubyeye.xmemcached.command.TextCommandFactory"/>
|
||||
</property>
|
||||
<!--分布策略,一致性哈希net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)-->
|
||||
<property name="sessionLocator">
|
||||
<bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator"/>
|
||||
</property>
|
||||
<!--序列化转换器,默认使用net.rubyeye.xmemcached.transcoders.SerializingTranscoder-->
|
||||
<property name="transcoder">
|
||||
<bean class="net.rubyeye.xmemcached.transcoders.SerializingTranscoder"/>
|
||||
</property>
|
||||
</bean>
|
||||
<!-- 集群配置 实例化bean -->
|
||||
<bean name="memcachedClientForCulster" factory-bean="memcachedClientBuilder"
|
||||
factory-method="build" destroy-method="shutdown"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
|
||||
| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached 是基于 java nio 的 memcached 高性能客户端,支持完整的 memcached 协议,支持客户端分布并且提供了一致性哈希 (consistent hash) 算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached 的整合配置位于 resources 下的 memcached 文件夹下,其中集群配置用 cluster 开头。所有配置按照需要在 springApplication.xml 用 import 导入。
|
||||
2. 实体类 Programmer.java 用于测试 memcached 序列化与反序列化
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-memcached.png"/> </div>
|
||||
|
||||
**springapplication.xml 文件:**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--memcached 单机版配置-->
|
||||
<!--<import resource="classpath*:memcached/singleConfig.xml"/>-->
|
||||
|
||||
<!--memcached 集群配置-->
|
||||
<import resource="classpath*:memcached/clusterConfig.xml"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
### 1.3 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 xmemcached 依赖包
|
||||
|
||||
```xml
|
||||
<!--memcached java 客户端-->
|
||||
<dependency>
|
||||
<groupId>com.googlecode.xmemcached</groupId>
|
||||
<artifactId>xmemcached</artifactId>
|
||||
<version>2.4.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring 整合 memcached
|
||||
|
||||
#### 2.1 单机配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<constructor-arg name="addressList" value="192.168.200.201:11211"/>
|
||||
</bean>
|
||||
|
||||
<bean id="memcachedClient" factory-bean="memcachedClientBuilder" factory-method="build"
|
||||
destroy-method="shutdown"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.2 集群配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean name="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder">
|
||||
<!--memcached servers 节点列表-->
|
||||
<constructor-arg name="addressList">
|
||||
<list>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11211"/>
|
||||
</bean>
|
||||
<bean class="java.net.InetSocketAddress">
|
||||
<constructor-arg value="192.168.200.201"/>
|
||||
<constructor-arg value="11212"/>
|
||||
</bean>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--与 servers 对应的节点的权重-->
|
||||
<constructor-arg name="weights">
|
||||
<list>
|
||||
<value>1</value>
|
||||
<value>2</value>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
<!--连接池大小-->
|
||||
<property name="connectionPoolSize" value="10"/>
|
||||
<!--协议工厂-->
|
||||
<property name="commandFactory">
|
||||
<bean class="net.rubyeye.xmemcached.command.TextCommandFactory"/>
|
||||
</property>
|
||||
<!--分布策略,一致性哈希 net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator 或者 ArraySessionLocator(默认)-->
|
||||
<property name="sessionLocator">
|
||||
<bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator"/>
|
||||
</property>
|
||||
<!--序列化转换器,默认使用 net.rubyeye.xmemcached.transcoders.SerializingTranscoder-->
|
||||
<property name="transcoder">
|
||||
<bean class="net.rubyeye.xmemcached.transcoders.SerializingTranscoder"/>
|
||||
</property>
|
||||
</bean>
|
||||
<!-- 集群配置 实例化 bean -->
|
||||
<bean name="memcachedClientForCulster" factory-bean="memcachedClientBuilder"
|
||||
factory-method="build" destroy-method="shutdown"/>
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 存储基本类型测试用例
|
||||
|
||||
xmemcached 单机版本和集群版本注入的实例是相同的;
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Memcached 操作基本对象
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
|
||||
String value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
memcachedClient.delete("hello");
|
||||
value = memcachedClient.get("hello");
|
||||
System.out.println("hello=" + value);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 存储实体对象测试用例
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :Memcached 序列化与反序列化
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MemObjectSamples {
|
||||
|
||||
@Autowired
|
||||
private MemcachedClient memcachedClient;
|
||||
|
||||
@Test
|
||||
public void operate() throws InterruptedException, MemcachedException, TimeoutException {
|
||||
memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
Programmer programmer = memcachedClient.get("programmer");
|
||||
System.out.println("hello ," + programmer.getName());
|
||||
memcachedClient.delete("programmer");
|
||||
programmer = memcachedClient.get("programmer");
|
||||
Assert.assertNull(programmer);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 附:memcached 基本命令
|
||||
|
||||
| 命令 | 格式 | 说明 |
|
||||
| --------------- | -------------------------------------------------- | ------------------------------------- |
|
||||
| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
|
||||
| 新增 add | add key flags exTime length -> value | 只有当 key 不存在的情况下,才可以插入 |
|
||||
| 替换 replace | replace key flags exTime length -> value | 只修改已存在 key 的 value 值 |
|
||||
| 追加内容 append | append key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 前面追加 prepend | prepend key flags exTime length -> value | length 表示追加的长度而不是总长度 |
|
||||
| 查询操作 get | get key | |
|
||||
| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
|
||||
| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
|
||||
| 删除 delete | delete key | 将数据打一个删除标记 |
|
||||
| 自增 incr | incr key 增加偏移量 | incr 和 decr 只能操作能转换为数字的 Value |
|
||||
| 自减 decr | decr key 减少偏移量 | desr 不能将数字减少至 0 以下 |
|
||||
| 清库 | flush_all | |
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 mongodb(注解方式)
|
||||
|
||||
# spring 整合 mongodb(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -10,162 +11,162 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于com.heibaiying.config下,项目以单元测试的方式进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mongodb-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入mongodb整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件及其映射类
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置属性
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:mongodb.properties")
|
||||
public class MongoProperty {
|
||||
|
||||
@Value("${mongo.host}")
|
||||
private String host;
|
||||
@Value("${mongo.port}")
|
||||
private int port;
|
||||
@Value("${mongo.dbname}")
|
||||
private String dbname;
|
||||
@Value("${mongo.connectionsPerHost}")
|
||||
private int connectionsPerHost;
|
||||
@Value("${mongo.threadsAllowedToBlockForConnectionMultiplier}")
|
||||
private int multiplier;
|
||||
@Value("${mongo.connectTimeout}")
|
||||
private int connectTimeout;
|
||||
@Value("${mongo.maxWaitTime}")
|
||||
private int maxWaitTime;
|
||||
@Value("${mongo.socketKeepAlive}")
|
||||
private boolean socketKeepAlive;
|
||||
@Value("${mongo.socketTimeout}")
|
||||
private int socketTimeout;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.*")
|
||||
public class MongoConfig {
|
||||
|
||||
@Bean
|
||||
public MongoDbFactory mongoDbFactory(MongoProperty mongo) {
|
||||
MongoClientOptions options = MongoClientOptions.builder()
|
||||
.threadsAllowedToBlockForConnectionMultiplier(mongo.getMultiplier())
|
||||
.connectionsPerHost(mongo.getConnectionsPerHost())
|
||||
.connectTimeout(mongo.getConnectTimeout())
|
||||
.maxWaitTime(mongo.getMaxWaitTime())
|
||||
.socketTimeout(mongo.getSocketTimeout())
|
||||
.build();
|
||||
MongoClient client = new MongoClient(new ServerAddress(mongo.getHost(), mongo.getPort()), options);
|
||||
return new SimpleMongoDbFactory(client, mongo.getDbname());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) {
|
||||
return new MongoTemplate(mongoDbFactory);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = MongoConfig.class)
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于 com.heibaiying.config 下,项目以单元测试的方式进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mongodb-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 mongodb 整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件及其映射类
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置属性
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:mongodb.properties")
|
||||
public class MongoProperty {
|
||||
|
||||
@Value("${mongo.host}")
|
||||
private String host;
|
||||
@Value("${mongo.port}")
|
||||
private int port;
|
||||
@Value("${mongo.dbname}")
|
||||
private String dbname;
|
||||
@Value("${mongo.connectionsPerHost}")
|
||||
private int connectionsPerHost;
|
||||
@Value("${mongo.threadsAllowedToBlockForConnectionMultiplier}")
|
||||
private int multiplier;
|
||||
@Value("${mongo.connectTimeout}")
|
||||
private int connectTimeout;
|
||||
@Value("${mongo.maxWaitTime}")
|
||||
private int maxWaitTime;
|
||||
@Value("${mongo.socketKeepAlive}")
|
||||
private boolean socketKeepAlive;
|
||||
@Value("${mongo.socketTimeout}")
|
||||
private int socketTimeout;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Mongo 配置类
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(value = "com.heibaiying.*")
|
||||
public class MongoConfig {
|
||||
|
||||
@Bean
|
||||
public MongoDbFactory mongoDbFactory(MongoProperty mongo) {
|
||||
MongoClientOptions options = MongoClientOptions.builder()
|
||||
.threadsAllowedToBlockForConnectionMultiplier(mongo.getMultiplier())
|
||||
.connectionsPerHost(mongo.getConnectionsPerHost())
|
||||
.connectTimeout(mongo.getConnectTimeout())
|
||||
.maxWaitTime(mongo.getMaxWaitTime())
|
||||
.socketTimeout(mongo.getSocketTimeout())
|
||||
.build();
|
||||
MongoClient client = new MongoClient(new ServerAddress(mongo.getHost(), mongo.getPort()), options);
|
||||
return new SimpleMongoDbFactory(client, mongo.getDbname());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) {
|
||||
return new MongoTemplate(mongoDbFactory);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = MongoConfig.class)
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 mongodb(xml配置方式)
|
||||
|
||||
# spring 整合 mongodb(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -10,145 +11,145 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于resources下,项目以单元测试的方式进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mongodb.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入mongodb整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--扫描配置文件-->
|
||||
<context:property-placeholder location="classpath:mongodb.properties"/>
|
||||
|
||||
<!--定义用于访问MongoDB的MongoClient实例-->
|
||||
<mongo:mongo-client host="${mongo.host}" port="${mongo.port}">
|
||||
<mongo:client-options
|
||||
connections-per-host="${mongo.connectionsPerHost}"
|
||||
threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
|
||||
connect-timeout="${mongo.connectTimeout}"
|
||||
max-wait-time="${mongo.maxWaitTime}"
|
||||
socket-keep-alive="${mongo.socketKeepAlive}"
|
||||
socket-timeout="${mongo.socketTimeout}"
|
||||
/>
|
||||
</mongo:mongo-client>
|
||||
|
||||
<!--定义用于连接到数据库的连接工厂-->
|
||||
<mongo:db-factory dbname="${mongo.dbname}" mongo-ref="mongoClient"/>
|
||||
|
||||
<!--实际操作mongodb的template,在代码中注入-->
|
||||
<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
|
||||
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : MongoDB 查询
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:mongodb.xml")
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于 resources 下,项目以单元测试的方式进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mongodb.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 mongodb 整合依赖包
|
||||
|
||||
```xml
|
||||
<!--spring mongodb 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
<version>2.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring mongodb
|
||||
|
||||
#### 2.1 新建配置文件
|
||||
|
||||
```properties
|
||||
mongo.host=192.168.200.228
|
||||
mongo.port=27017
|
||||
# 数据库名称. 默认是'db'.
|
||||
mongo.dbname=database
|
||||
# 每个主机允许的连接数
|
||||
mongo.connectionsPerHost=10
|
||||
# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
|
||||
mongo.threadsAllowedToBlockForConnectionMultiplier=5
|
||||
# 连接超时的毫秒 0是默认值且无限大。
|
||||
mongo.connectTimeout=1000
|
||||
# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
|
||||
mongo.maxWaitTime=1500
|
||||
# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
|
||||
mongo.socketKeepAlive=true
|
||||
# 用于群集心跳的连接的套接字超时。
|
||||
mongo.socketTimeout=1500
|
||||
```
|
||||
|
||||
#### 2.2 整合配置
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<!--扫描配置文件-->
|
||||
<context:property-placeholder location="classpath:mongodb.properties"/>
|
||||
|
||||
<!--定义用于访问 MongoDB 的 MongoClient 实例-->
|
||||
<mongo:mongo-client host="${mongo.host}" port="${mongo.port}">
|
||||
<mongo:client-options
|
||||
connections-per-host="${mongo.connectionsPerHost}"
|
||||
threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
|
||||
connect-timeout="${mongo.connectTimeout}"
|
||||
max-wait-time="${mongo.maxWaitTime}"
|
||||
socket-keep-alive="${mongo.socketKeepAlive}"
|
||||
socket-timeout="${mongo.socketTimeout}"
|
||||
/>
|
||||
</mongo:mongo-client>
|
||||
|
||||
<!--定义用于连接到数据库的连接工厂-->
|
||||
<mongo:db-factory dbname="${mongo.dbname}" mongo-ref="mongoClient"/>
|
||||
|
||||
<!--实际操作 mongodb 的 template,在代码中注入-->
|
||||
<bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
|
||||
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 测试整合
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : MongoDB 查询
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:mongodb.xml")
|
||||
public class MongoDBTest {
|
||||
|
||||
@Autowired
|
||||
private MongoTemplate mongoTemplate;
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
// 单条插入
|
||||
mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
|
||||
List<Programmer> programmers = new ArrayList<Programmer>();
|
||||
// 批量插入
|
||||
programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
|
||||
programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
|
||||
mongoTemplate.insert(programmers, Programmer.class);
|
||||
}
|
||||
|
||||
// 条件查询
|
||||
@Test
|
||||
public void select() {
|
||||
Criteria criteria = new Criteria();
|
||||
criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
|
||||
Query query = new Query(criteria);
|
||||
Programmer one = mongoTemplate.findOne(query, Programmer.class);
|
||||
System.out.println(one);
|
||||
}
|
||||
|
||||
|
||||
// 更新数据
|
||||
@Test
|
||||
public void MUpdate() {
|
||||
UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
|
||||
System.out.println("更新记录数:" + updateResult.getModifiedCount());
|
||||
}
|
||||
|
||||
// 删除指定数据
|
||||
@Test
|
||||
public void delete() {
|
||||
DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
|
||||
System.out.println("影响记录数:" + result.getDeletedCount());
|
||||
System.out.println("是否成功:" + result.wasAcknowledged());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 mybatis(注解方式)
|
||||
|
||||
# spring 整合 mybatis(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目依赖">1.2 项目依赖</a><br/>
|
||||
@ -12,263 +13,263 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mybatis-annotation.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
除了spring相关依赖外,还需要导入数据库驱动和对应的mybatis依赖包
|
||||
|
||||
```xml
|
||||
<!--jdbc 相关依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3.0</version>
|
||||
</dependency>
|
||||
<!--mybatis 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.4.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、spring 整合 mybatis
|
||||
|
||||
#### 2.1 在resources文件夹下新建数据库配置文件jdbc.properties及其映射类
|
||||
|
||||
```properties
|
||||
# mysql 数据库配置
|
||||
mysql.driverClassName=com.mysql.jdbc.Driver
|
||||
mysql.url=jdbc:mysql://localhost:3306/mysql
|
||||
mysql.username=root
|
||||
mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:mysql.properties")
|
||||
@Data
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Value("${mysql.driverClassName}")
|
||||
private String driverClassName;
|
||||
@Value("${mysql.url}")
|
||||
private String url;
|
||||
@Value("${mysql.username}")
|
||||
private String username;
|
||||
@Value("${mysql.password}")
|
||||
private String password;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 配置数据源和mybatis会话工厂、定义事务管理器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于xml中<tx:annotation-driven/>
|
||||
@ComponentScan(basePackages = {"com.heibaiying.*"})
|
||||
public class DatabaseConfig {
|
||||
|
||||
/* @Autowired
|
||||
* private DataSourceConfig sourceConfig;
|
||||
* 不要采用这种方式注入DataSourceConfig,由于类的加载顺序影响会报空指针异常
|
||||
* 最好的方式是在DriverManagerDataSource构造中采用参数注入
|
||||
*/
|
||||
|
||||
/**
|
||||
* 配置数据源
|
||||
*/
|
||||
@Bean
|
||||
public DriverManagerDataSource dataSource(DataSourceConfig sourceConfig) {
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(sourceConfig.getDriverClassName());
|
||||
dataSource.setUrl(sourceConfig.getUrl());
|
||||
dataSource.setUsername(sourceConfig.getUsername());
|
||||
dataSource.setPassword(sourceConfig.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置mybatis 会话工厂
|
||||
*
|
||||
* @param dataSource 这个参数的名称需要保持和上面方法名一致 才能自动注入,因为
|
||||
* 采用@Bean注解生成的bean 默认采用方法名为名称,当然也可以在使用@Bean时指定name属性
|
||||
*/
|
||||
@Bean
|
||||
public SqlSessionFactoryBean sessionFactoryBean(DriverManagerDataSource dataSource) throws IOException {
|
||||
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
|
||||
sessionFactoryBean.setDataSource(dataSource);
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
sessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:/mappers/**/*.xml"));
|
||||
sessionFactoryBean.setConfigLocation(resolver.getResource("classpath:mybatisConfig.xml"));
|
||||
return sessionFactoryBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置mybatis 会话工厂
|
||||
*/
|
||||
@Bean
|
||||
public MapperScannerConfigurer MapperScannerConfigurer() {
|
||||
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
|
||||
configurer.setSqlSessionFactoryBeanName("sessionFactoryBean");
|
||||
configurer.setBasePackage("com.heibaiying.dao");
|
||||
return configurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义事务管理器
|
||||
*/
|
||||
@Bean
|
||||
public DataSourceTransactionManager transactionManager(DriverManagerDataSource dataSource) {
|
||||
DataSourceTransactionManager manager = new DataSourceTransactionManager();
|
||||
manager.setDataSource(dataSource);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
|
||||
<!-- mybatis 配置文件 -->
|
||||
<configuration>
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
</configuration>
|
||||
|
||||
<!--更多settings配置项可以参考官方文档: <a href="http://www.mybatis.org/mybatis-3/zh/configuration.html"/>-->
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 新建查询接口及其对应的mapper文件
|
||||
|
||||
```java
|
||||
public interface MysqlDao {
|
||||
|
||||
List<Relation> get();
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.MysqlDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Relation">
|
||||
SELECT help_keyword_id AS id,name
|
||||
FROM HELP_KEYWORD
|
||||
WHERE HELP_KEYWORD_ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
```mysql
|
||||
public interface OracleDao {
|
||||
|
||||
List<Flow> queryById(long id);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.OracleDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Flow">
|
||||
select * from APEX_030200.WWV_FLOW_CALS where ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 2.5 新建测试类进行测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {DatabaseConfig.class})
|
||||
public class MysqlDaoTest {
|
||||
|
||||
@Autowired
|
||||
private MysqlDao mysqlDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Relation> relations = mysqlDao.queryById("691");
|
||||
if (relations != null) {
|
||||
for (Relation relation : relations) {
|
||||
System.out.println(relation.getId() + " " + relation.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {DatabaseConfig.class})
|
||||
public class OracleDaoTest {
|
||||
|
||||
@Autowired
|
||||
private OracleDao oracleDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Flow> flows = oracleDao.queryById(217584603977429772L);
|
||||
if (flows != null) {
|
||||
for (Flow flow : flows) {
|
||||
System.out.println(flow.getId() + " " + flow.getPlugId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mybatis-annotation.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
除了 spring 相关依赖外,还需要导入数据库驱动和对应的 mybatis 依赖包
|
||||
|
||||
```xml
|
||||
<!--jdbc 相关依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3.0</version>
|
||||
</dependency>
|
||||
<!--mybatis 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.4.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、spring 整合 mybatis
|
||||
|
||||
#### 2.1 在resources文件夹下新建数据库配置文件jdbc.properties及其映射类
|
||||
|
||||
```properties
|
||||
# mysql 数据库配置
|
||||
mysql.driverClassName=com.mysql.jdbc.Driver
|
||||
mysql.url=jdbc:mysql://localhost:3306/mysql
|
||||
mysql.username=root
|
||||
mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@PropertySource(value = "classpath:mysql.properties")
|
||||
@Data
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Value("${mysql.driverClassName}")
|
||||
private String driverClassName;
|
||||
@Value("${mysql.url}")
|
||||
private String url;
|
||||
@Value("${mysql.username}")
|
||||
private String username;
|
||||
@Value("${mysql.password}")
|
||||
private String password;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 配置数据源和mybatis会话工厂、定义事务管理器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement // 开启声明式事务处理 等价于 xml 中<tx:annotation-driven/>
|
||||
@ComponentScan(basePackages = {"com.heibaiying.*"})
|
||||
public class DatabaseConfig {
|
||||
|
||||
/* @Autowired
|
||||
* private DataSourceConfig sourceConfig;
|
||||
* 不要采用这种方式注入 DataSourceConfig,由于类的加载顺序影响会报空指针异常
|
||||
* 最好的方式是在 DriverManagerDataSource 构造中采用参数注入
|
||||
*/
|
||||
|
||||
/**
|
||||
* 配置数据源
|
||||
*/
|
||||
@Bean
|
||||
public DriverManagerDataSource dataSource(DataSourceConfig sourceConfig) {
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName(sourceConfig.getDriverClassName());
|
||||
dataSource.setUrl(sourceConfig.getUrl());
|
||||
dataSource.setUsername(sourceConfig.getUsername());
|
||||
dataSource.setPassword(sourceConfig.getPassword());
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置 mybatis 会话工厂
|
||||
*
|
||||
* @param dataSource 这个参数的名称需要保持和上面方法名一致 才能自动注入,因为
|
||||
* 采用@Bean 注解生成的 bean 默认采用方法名为名称,当然也可以在使用@Bean 时指定 name 属性
|
||||
*/
|
||||
@Bean
|
||||
public SqlSessionFactoryBean sessionFactoryBean(DriverManagerDataSource dataSource) throws IOException {
|
||||
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
|
||||
sessionFactoryBean.setDataSource(dataSource);
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
sessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:/mappers/**/*.xml"));
|
||||
sessionFactoryBean.setConfigLocation(resolver.getResource("classpath:mybatisConfig.xml"));
|
||||
return sessionFactoryBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置 mybatis 会话工厂
|
||||
*/
|
||||
@Bean
|
||||
public MapperScannerConfigurer MapperScannerConfigurer() {
|
||||
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
|
||||
configurer.setSqlSessionFactoryBeanName("sessionFactoryBean");
|
||||
configurer.setBasePackage("com.heibaiying.dao");
|
||||
return configurer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义事务管理器
|
||||
*/
|
||||
@Bean
|
||||
public DataSourceTransactionManager transactionManager(DriverManagerDataSource dataSource) {
|
||||
DataSourceTransactionManager manager = new DataSourceTransactionManager();
|
||||
manager.setDataSource(dataSource);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
|
||||
<!-- mybatis 配置文件 -->
|
||||
<configuration>
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询 sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
</configuration>
|
||||
|
||||
<!--更多settings配置项可以参考官方文档: <a href="http://www.mybatis.org/mybatis-3/zh/configuration.html"/>-->
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 新建查询接口及其对应的mapper文件
|
||||
|
||||
```java
|
||||
public interface MysqlDao {
|
||||
|
||||
List<Relation> get();
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.MysqlDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Relation">
|
||||
SELECT help_keyword_id AS id,name
|
||||
FROM HELP_KEYWORD
|
||||
WHERE HELP_KEYWORD_ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
```mysql
|
||||
public interface OracleDao {
|
||||
|
||||
List<Flow> queryById(long id);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.OracleDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Flow">
|
||||
select * from APEX_030200.WWV_FLOW_CALS where ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 2.5 新建测试类进行测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {DatabaseConfig.class})
|
||||
public class MysqlDaoTest {
|
||||
|
||||
@Autowired
|
||||
private MysqlDao mysqlDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Relation> relations = mysqlDao.queryById("691");
|
||||
if (relations != null) {
|
||||
for (Relation relation : relations) {
|
||||
System.out.println(relation.getId() + " " + relation.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {DatabaseConfig.class})
|
||||
public class OracleDaoTest {
|
||||
|
||||
@Autowired
|
||||
private OracleDao oracleDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Flow> flows = oracleDao.queryById(217584603977429772L);
|
||||
if (flows != null) {
|
||||
for (Flow flow : flows) {
|
||||
System.out.println(flow.getId() + " " + flow.getPlugId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 mybatis(xml配置方式)
|
||||
|
||||
# spring 整合 mybatis(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目依赖">1.2 项目依赖</a><br/>
|
||||
@ -12,237 +13,237 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mybatis.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
除了spring相关依赖外,还需要导入数据库驱动和对应的mybatis依赖包
|
||||
|
||||
```xml
|
||||
<!--jdbc 相关依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3.0</version>
|
||||
</dependency>
|
||||
<!--mybatis 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.4.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、spring 整合 mybatis
|
||||
|
||||
#### 2.1 在resources文件夹下新建数据库配置文件jdbc.properties
|
||||
|
||||
```properties
|
||||
# mysql 数据库配置
|
||||
mysql.driverClassName=com.mysql.jdbc.Driver
|
||||
mysql.url=jdbc:mysql://localhost:3306/mysql
|
||||
mysql.username=root
|
||||
mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
|
||||
#### 2.2 配置数据源和mybatis会话工厂、定义事务管理器
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--指定配置文件的位置-->
|
||||
<context:property-placeholder location="classpath:jdbc.properties"/>
|
||||
|
||||
<!--配置数据源-->
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<!--Mysql 配置-->
|
||||
<property name="driverClassName" value="${mysql.driverClassName}"/>
|
||||
<property name="url" value="${mysql.url}"/>
|
||||
<property name="username" value="${mysql.username}"/>
|
||||
<property name="password" value="${mysql.password}"/>
|
||||
<!--Oracle 配置-->
|
||||
<!--<property name="driverClassName" value="${oracle.driverClassName}"/>
|
||||
<property name="url" value="${oracle.url}"/>
|
||||
<property name="username" value="${oracle.username}"/>
|
||||
<property name="password" value="${oracle.password}"/>-->
|
||||
</bean>
|
||||
|
||||
<!--配置 mybatis 会话工厂 -->
|
||||
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<!--指定mapper文件所在的位置-->
|
||||
<property name="mapperLocations" value="classpath*:/mappers/**/*.xml"/>
|
||||
<property name="configLocation" value="classpath:mybatisConfig.xml"/>
|
||||
</bean>
|
||||
|
||||
<!--扫描注册接口 -->
|
||||
<!--作用:从接口的基础包开始递归搜索,并将它们注册为 MapperFactoryBean(只有至少一种方法的接口才会被注册;, 具体类将被忽略)-->
|
||||
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
|
||||
<!--指定会话工厂 -->
|
||||
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
|
||||
<!-- 指定mybatis接口所在的包 -->
|
||||
<property name="basePackage" value="com.heibaiying.dao"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<!--定义事务管理器-->
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- 开启事务注解@Transactional支持 -->
|
||||
<tx:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
|
||||
<!-- mybatis 配置文件 -->
|
||||
<configuration>
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
</configuration>
|
||||
|
||||
<!--更多settings配置项可以参考官方文档: <a href="http://www.mybatis.org/mybatis-3/zh/configuration.html"/>-->
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 新建查询接口及其对应的mapper文件
|
||||
|
||||
```java
|
||||
public interface MysqlDao {
|
||||
|
||||
List<Relation> get();
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.MysqlDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Relation">
|
||||
SELECT help_keyword_id AS id,name
|
||||
FROM HELP_KEYWORD
|
||||
WHERE HELP_KEYWORD_ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
```mysql
|
||||
public interface OracleDao {
|
||||
|
||||
List<Flow> queryById(long id);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.OracleDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Flow">
|
||||
select * from APEX_030200.WWV_FLOW_CALS where ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 2.5 新建测试类进行测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MysqlDaoTest {
|
||||
|
||||
@Autowired
|
||||
private MysqlDao mysqlDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Relation> relations = mysqlDao.get();
|
||||
if (relations != null) {
|
||||
for (Relation relation : relations) {
|
||||
System.out.println(relation.getId() + " " + relation.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class OracleDaoTest {
|
||||
|
||||
/*注入接口时: 如果接口有多个实现类 可以用这个注解指定具体的实现类*/
|
||||
@Qualifier("oracleDaoImpl")
|
||||
@Autowired
|
||||
private OracleDao oracleDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Flow> flows = oracleDao.get();
|
||||
if (flows != null) {
|
||||
for (Flow flow : flows) {
|
||||
System.out.println(flow.getId() + " " + flow.getPlugId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mybatis.png"/> </div>
|
||||
|
||||
#### 1.2 项目依赖
|
||||
|
||||
除了 spring 相关依赖外,还需要导入数据库驱动和对应的 mybatis 依赖包
|
||||
|
||||
```xml
|
||||
<!--jdbc 相关依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jdbc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle</groupId>
|
||||
<artifactId>ojdbc6</artifactId>
|
||||
<version>11.2.0.3.0</version>
|
||||
</dependency>
|
||||
<!--mybatis 依赖包-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis-spring</artifactId>
|
||||
<version>1.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.4.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## 二、spring 整合 mybatis
|
||||
|
||||
#### 2.1 在resources文件夹下新建数据库配置文件jdbc.properties
|
||||
|
||||
```properties
|
||||
# mysql 数据库配置
|
||||
mysql.driverClassName=com.mysql.jdbc.Driver
|
||||
mysql.url=jdbc:mysql://localhost:3306/mysql
|
||||
mysql.username=root
|
||||
mysql.password=root
|
||||
|
||||
# oracle 数据库配置
|
||||
oracle.driverClassName=oracle.jdbc.driver.OracleDriver
|
||||
oracle.url=jdbc:oracle:thin:@//IP 地址:端口号/数据库实例名
|
||||
oracle.username=用户名
|
||||
oracle.password=密码
|
||||
```
|
||||
|
||||
#### 2.2 配置数据源和mybatis会话工厂、定义事务管理器
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--指定配置文件的位置-->
|
||||
<context:property-placeholder location="classpath:jdbc.properties"/>
|
||||
|
||||
<!--配置数据源-->
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<!--Mysql 配置-->
|
||||
<property name="driverClassName" value="${mysql.driverClassName}"/>
|
||||
<property name="url" value="${mysql.url}"/>
|
||||
<property name="username" value="${mysql.username}"/>
|
||||
<property name="password" value="${mysql.password}"/>
|
||||
<!--Oracle 配置-->
|
||||
<!--<property name="driverClassName" value="${oracle.driverClassName}"/>
|
||||
<property name="url" value="${oracle.url}"/>
|
||||
<property name="username" value="${oracle.username}"/>
|
||||
<property name="password" value="${oracle.password}"/>-->
|
||||
</bean>
|
||||
|
||||
<!--配置 mybatis 会话工厂 -->
|
||||
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<!--指定 mapper 文件所在的位置-->
|
||||
<property name="mapperLocations" value="classpath*:/mappers/**/*.xml"/>
|
||||
<property name="configLocation" value="classpath:mybatisConfig.xml"/>
|
||||
</bean>
|
||||
|
||||
<!--扫描注册接口 -->
|
||||
<!--作用:从接口的基础包开始递归搜索,并将它们注册为 MapperFactoryBean(只有至少一种方法的接口才会被注册;, 具体类将被忽略)-->
|
||||
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
|
||||
<!--指定会话工厂 -->
|
||||
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
|
||||
<!-- 指定 mybatis 接口所在的包 -->
|
||||
<property name="basePackage" value="com.heibaiying.dao"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<!--定义事务管理器-->
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- 开启事务注解@Transactional 支持 -->
|
||||
<tx:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
|
||||
<!-- mybatis 配置文件 -->
|
||||
<configuration>
|
||||
<settings>
|
||||
<!-- 开启驼峰命名 -->
|
||||
<setting name="mapUnderscoreToCamelCase" value="true"/>
|
||||
<!-- 打印查询 sql -->
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>
|
||||
|
||||
</configuration>
|
||||
|
||||
<!--更多settings配置项可以参考官方文档: <a href="http://www.mybatis.org/mybatis-3/zh/configuration.html"/>-->
|
||||
|
||||
```
|
||||
|
||||
#### 2.4 新建查询接口及其对应的mapper文件
|
||||
|
||||
```java
|
||||
public interface MysqlDao {
|
||||
|
||||
List<Relation> get();
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.MysqlDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Relation">
|
||||
SELECT help_keyword_id AS id,name
|
||||
FROM HELP_KEYWORD
|
||||
WHERE HELP_KEYWORD_ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
```mysql
|
||||
public interface OracleDao {
|
||||
|
||||
List<Flow> queryById(long id);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```xml
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.heibaiying.dao.OracleDao">
|
||||
|
||||
<select id="queryById" resultType="com.heibaiying.bean.Flow">
|
||||
select * from APEX_030200.WWV_FLOW_CALS where ID = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 2.5 新建测试类进行测试
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class MysqlDaoTest {
|
||||
|
||||
@Autowired
|
||||
private MysqlDao mysqlDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Relation> relations = mysqlDao.get();
|
||||
if (relations != null) {
|
||||
for (Relation relation : relations) {
|
||||
System.out.println(relation.getId() + " " + relation.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration({"classpath:springApplication.xml"})
|
||||
public class OracleDaoTest {
|
||||
|
||||
/*注入接口时: 如果接口有多个实现类 可以用这个注解指定具体的实现类*/
|
||||
@Qualifier("oracleDaoImpl")
|
||||
@Autowired
|
||||
private OracleDao oracleDao;
|
||||
|
||||
@Test
|
||||
public void get() {
|
||||
List<Flow> flows = oracleDao.get();
|
||||
if (flows != null) {
|
||||
for (Flow flow : flows) {
|
||||
System.out.println(flow.getId() + " " + flow.getPlugId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 rabbitmq(注解方式)
|
||||
|
||||
# spring 整合 rabbitmq(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -16,404 +17,404 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-rabbitmq-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
#### 2.1 基本配置属性及其映射类
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : rabbit 属性配置
|
||||
*/
|
||||
@Data
|
||||
@PropertySource(value = "classpath:rabbitmq.properties")
|
||||
@Configuration
|
||||
public class RabbitProperty {
|
||||
|
||||
|
||||
@Value("${rabbitmq.addresses}")
|
||||
private String addresses;
|
||||
|
||||
@Value("${rabbitmq.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${rabbitmq.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${rabbitmq.virtualhost}")
|
||||
private String virtualhost;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 创建连接工厂、管理器
|
||||
|
||||
```java
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.rabbit.config")
|
||||
public class RabbitBaseConfig {
|
||||
|
||||
/**
|
||||
* 声明连接工厂
|
||||
*/
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory(RabbitProperty property) {
|
||||
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
|
||||
connectionFactory.setAddresses(property.getAddresses());
|
||||
connectionFactory.setUsername(property.getUsername());
|
||||
connectionFactory.setPassword(property.getPassword());
|
||||
connectionFactory.setVirtualHost(property.getVirtualhost());
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
* auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为true。
|
||||
*/
|
||||
@Bean
|
||||
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
rabbitAdmin.setAutoStartup(true);
|
||||
return rabbitAdmin;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
|
||||
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
|
||||
return rabbitTemplate;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试topic这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列1消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列2消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
|
||||
SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于 rabbitmq 的整合提供**简单消息发送**和**对象消费发送**两种情况下的 sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java 中声明了 topic 类型的交换机、持久化队列、及其绑定关系,用于测试说明 topic 交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java 中声明了 direct 类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于 rabbitmq 安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ 实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-rabbitmq-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
#### 2.1 基本配置属性及其映射类
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : rabbit 属性配置
|
||||
*/
|
||||
@Data
|
||||
@PropertySource(value = "classpath:rabbitmq.properties")
|
||||
@Configuration
|
||||
public class RabbitProperty {
|
||||
|
||||
|
||||
@Value("${rabbitmq.addresses}")
|
||||
private String addresses;
|
||||
|
||||
@Value("${rabbitmq.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${rabbitmq.password}")
|
||||
private String password;
|
||||
|
||||
@Value("${rabbitmq.virtualhost}")
|
||||
private String virtualhost;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 创建连接工厂、管理器
|
||||
|
||||
```java
|
||||
|
||||
@Configuration
|
||||
@ComponentScan("com.heibaiying.rabbit.config")
|
||||
public class RabbitBaseConfig {
|
||||
|
||||
/**
|
||||
* 声明连接工厂
|
||||
*/
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory(RabbitProperty property) {
|
||||
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
|
||||
connectionFactory.setAddresses(property.getAddresses());
|
||||
connectionFactory.setUsername(property.getUsername());
|
||||
connectionFactory.setPassword(property.getPassword());
|
||||
connectionFactory.setVirtualHost(property.getVirtualhost());
|
||||
return connectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
* auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为 true。
|
||||
*/
|
||||
@Bean
|
||||
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
|
||||
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
|
||||
rabbitAdmin.setAutoStartup(true);
|
||||
return rabbitAdmin;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
|
||||
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
|
||||
return rabbitTemplate;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列 1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列 2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试 topic 这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列 1 消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为 true,则所有 DeliveryTag 小于该 DeliveryTag 的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列 2 消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合 queue1 的要求,只能被 queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合 queue2 的要求,只能被 queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue 收到消息:我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue 收到消息:我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue 收到消息:我的路由键 quick.orange.fox 只符合 queue1 的要求,只能被 queue 1 接收到
|
||||
SecondQueue 收到消息:我的路由键 lazy.brown.fox 只符合 queue2 的要求,只能被 queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的 handleMessage 方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是 handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = RabbitBaseConfig.class)
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType 为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的 Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 整合 rabbitmq(xml配置方式)
|
||||
|
||||
# spring 整合 rabbitmq(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -14,382 +15,382 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-rabbitmq.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/rabbit
|
||||
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
|
||||
|
||||
<context:property-placeholder location="rabbitmq.properties"/>
|
||||
|
||||
<!--声明连接工厂-->
|
||||
<rabbit:connection-factory id="connectionFactory"
|
||||
addresses="${rabbitmq.addresses}"
|
||||
username="${rabbitmq.username}"
|
||||
password="${rabbitmq.password}"
|
||||
virtual-host="${rabbitmq.virtualhost}"/>
|
||||
|
||||
<!--创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为true。-->
|
||||
<rabbit:admin connection-factory="connectionFactory" auto-startup="true"/>
|
||||
|
||||
<!--声明 template 的时候需要声明id 不然会抛出异常-->
|
||||
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
|
||||
|
||||
|
||||
<!--可以在xml采用如下方式声明交换机、队列、绑定管理 但是建议使用代码方式声明 方法更加灵活且可以采用链调用-->
|
||||
<rabbit:queue name="remoting.queue"/>
|
||||
|
||||
<rabbit:direct-exchange name="remoting.exchange">
|
||||
<rabbit:bindings>
|
||||
<rabbit:binding queue="remoting.queue" key="remoting.binding"/>
|
||||
</rabbit:bindings>
|
||||
</rabbit:direct-exchange>
|
||||
|
||||
<!--扫描rabbit包 自动声明交换器、队列、绑定关系-->
|
||||
<context:component-scan base-package="com.heibaiying.rabbit"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试topic这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列1消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列2消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
|
||||
SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于 rabbitmq 的整合提供**简单消息发送**和**对象消费发送**两种情况下的 sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java 中声明了 topic 类型的交换机、持久化队列、及其绑定关系,用于测试说明 topic 交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java 中声明了 direct 类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于 rabbitmq 安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ 实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-rabbitmq.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了 spring 的基本依赖外,需要导入 spring rabbitmq 整合依赖
|
||||
|
||||
```xml
|
||||
<!--spring rabbitmq 整合依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
</dependency>
|
||||
<!--rabbitmq 传输对象序列化依赖了这个包-->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring rabbit 基本配置
|
||||
|
||||
```properties
|
||||
rabbitmq.addresses=localhost:5672
|
||||
rabbitmq.username=guest
|
||||
rabbitmq.password=guest
|
||||
# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
|
||||
rabbitmq.virtualhost=/
|
||||
```
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
|
||||
xsi:schemaLocation=
|
||||
"http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/rabbit
|
||||
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
|
||||
|
||||
<context:property-placeholder location="rabbitmq.properties"/>
|
||||
|
||||
<!--声明连接工厂-->
|
||||
<rabbit:connection-factory id="connectionFactory"
|
||||
addresses="${rabbitmq.addresses}"
|
||||
username="${rabbitmq.username}"
|
||||
password="${rabbitmq.password}"
|
||||
virtual-host="${rabbitmq.virtualhost}"/>
|
||||
|
||||
<!--创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
|
||||
auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为 true。-->
|
||||
<rabbit:admin connection-factory="connectionFactory" auto-startup="true"/>
|
||||
|
||||
<!--声明 template 的时候需要声明 id 不然会抛出异常-->
|
||||
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
|
||||
|
||||
|
||||
<!--可以在 xml 采用如下方式声明交换机、队列、绑定管理 但是建议使用代码方式声明 方法更加灵活且可以采用链调用-->
|
||||
<rabbit:queue name="remoting.queue"/>
|
||||
|
||||
<rabbit:direct-exchange name="remoting.exchange">
|
||||
<rabbit:bindings>
|
||||
<rabbit:binding queue="remoting.queue" key="remoting.binding"/>
|
||||
</rabbit:bindings>
|
||||
</rabbit:direct-exchange>
|
||||
|
||||
<!--扫描 rabbit 包 自动声明交换器、队列、绑定关系-->
|
||||
<context:component-scan base-package="com.heibaiying.rabbit"/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 三、简单消费的发送
|
||||
|
||||
#### 3.1 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
|
||||
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
|
||||
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、和队列消息监听
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitBaseAnnotation {
|
||||
|
||||
@Bean
|
||||
public TopicExchange exchange() {
|
||||
// 创建一个持久化的交换机
|
||||
return new TopicExchange("topic01", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue firstQueue() {
|
||||
// 创建一个持久化的队列 1
|
||||
return new Queue("FirstQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue secondQueue() {
|
||||
// 创建一个持久化的队列 2
|
||||
return new Queue("SecondQueue", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
|
||||
* 这里我们声明三个绑定关系用于测试 topic 这种类型交换器
|
||||
*/
|
||||
@Bean
|
||||
public Binding orange() {
|
||||
return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding rabbit() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding lazy() {
|
||||
return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列 1 消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(firstQueue());
|
||||
// 指定要创建的并发使用者数。
|
||||
container.setConcurrentConsumers(1);
|
||||
// 设置消费者数量的上限
|
||||
container.setMaxConcurrentConsumers(5);
|
||||
// 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
|
||||
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
// 可以在这个地方得到消息额外属性
|
||||
MessageProperties properties = message.getMessageProperties();
|
||||
//得到消息体内容
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
|
||||
/*
|
||||
* DeliveryTag 是一个单调递增的整数
|
||||
* 第二个参数 代表是否一次签收多条,如果设置为 true,则所有 DeliveryTag 小于该 DeliveryTag 的消息都会被签收
|
||||
*/
|
||||
channel.basicAck(properties.getDeliveryTag(), false);
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
/*创建队列 2 消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
container.setQueues(secondQueue());
|
||||
container.setMessageListener(new ChannelAwareMessageListener() {
|
||||
@Override
|
||||
public void onMessage(Message message, Channel channel) throws Exception {
|
||||
byte[] body = message.getBody();
|
||||
System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 测试简单消息的发送
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 传输简单字符串
|
||||
*/
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendMessage() {
|
||||
MessageProperties properties = new MessageProperties();
|
||||
|
||||
String allReceived = "我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到";
|
||||
Message message1 = new Message(allReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
|
||||
|
||||
String firstReceived = "我的路由键 quick.orange.fox 只符合 queue1 的要求,只能被 queue 1 接收到";
|
||||
Message message2 = new Message(firstReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.orange.fox", message2);
|
||||
|
||||
String secondReceived = "我的路由键 lazy.brown.fox 只符合 queue2 的要求,只能被 queue 2 接收到";
|
||||
Message message3 = new Message(secondReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
|
||||
|
||||
String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
|
||||
Message message4 = new Message(notReceived.getBytes(), properties);
|
||||
rabbitTemplate.send("topic01", "quick.brown.fox", message4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
结果:
|
||||
SecondQueue 收到消息:我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue 收到消息:我的路由键 quick.orange.rabbit 符合 queue1 和 queue2 的要求,我应该被两个监听器接收到
|
||||
FirstQueue 收到消息:我的路由键 quick.orange.fox 只符合 queue1 的要求,只能被 queue 1 接收到
|
||||
SecondQueue 收到消息:我的路由键 lazy.brown.fox 只符合 queue2 的要求,只能被 queue 2 接收到
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、传输对象
|
||||
|
||||
#### 4.1 创建消息的委托处理器
|
||||
|
||||
这里为了增强用例的实用性,我们创建的处理器的 handleMessage 方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :消息委派处理类
|
||||
*/
|
||||
public class MessageDelegate {
|
||||
|
||||
public void handleMessage(ProductManager manager) {
|
||||
System.out.println("收到一个产品经理" + manager);
|
||||
}
|
||||
|
||||
public void handleMessage(Programmer programmer) {
|
||||
System.out.println("收到一个程序员" + programmer);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 声明交换机、队列、绑定关系和消费者监听器
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
|
||||
*/
|
||||
|
||||
@Configuration
|
||||
public class RabbitObjectAnnotation {
|
||||
|
||||
@Bean
|
||||
public DirectExchange objectTopic() {
|
||||
// 创建一个持久化的交换机
|
||||
return new DirectExchange("objectTopic", true, false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Queue objectQueue() {
|
||||
// 创建一个持久化的队列
|
||||
return new Queue("objectQueue", true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Binding binding() {
|
||||
return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
|
||||
}
|
||||
|
||||
|
||||
/*创建队列消费者监听*/
|
||||
@Bean
|
||||
public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
|
||||
|
||||
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
|
||||
// 设置监听的队列
|
||||
container.setQueues(objectQueue());
|
||||
// 将监听到的消息委派给实际的处理类
|
||||
MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
|
||||
// 指定由哪个方法来处理消息 默认就是 handleMessage
|
||||
adapter.setDefaultListenerMethod("handleMessage");
|
||||
|
||||
// 消息转换
|
||||
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
|
||||
DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
|
||||
|
||||
Map<String, Class<?>> idClassMapping = new HashMap<>();
|
||||
// 针对不同的消息体调用不同的重载方法
|
||||
idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
|
||||
idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
|
||||
|
||||
javaTypeMapper.setIdClassMapping(idClassMapping);
|
||||
|
||||
jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
|
||||
adapter.setMessageConverter(jackson2JsonMessageConverter);
|
||||
container.setMessageListener(adapter);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.3 测试对象消息的发送
|
||||
|
||||
```java
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(locations = "classpath:rabbitmq.xml")
|
||||
public class RabbitSendObjectTest {
|
||||
|
||||
@Autowired
|
||||
private RabbitTemplate rabbitTemplate;
|
||||
|
||||
@Test
|
||||
public void sendProgrammer() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
//必须设置 contentType 为 application/json
|
||||
messageProperties.setContentType("application/json");
|
||||
// 必须指定类型
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
|
||||
Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
|
||||
// 序列化与反序列化都使用的 Jackson
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String programmerJson = mapper.writeValueAsString(programmer);
|
||||
Message message = new Message(programmerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void sendProductManager() throws JsonProcessingException {
|
||||
MessageProperties messageProperties = new MessageProperties();
|
||||
messageProperties.setContentType("application/json");
|
||||
messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
|
||||
ProductManager manager = new ProductManager("xiaohong", 21, new Date());
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String managerJson = mapper.writeValueAsString(manager);
|
||||
Message message = new Message(managerJson.getBytes(), messageProperties);
|
||||
rabbitTemplate.send("objectTopic", "object", message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
||||
# spring 定时任务(注解方式)
|
||||
|
||||
# spring 定时任务(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -9,193 +10,193 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在ServletConfig.java中,为方便观察项目定时执行的情况,项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-scheduling-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Component
|
||||
public class Task {
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。
|
||||
*/
|
||||
@Scheduled(fixedDelay = 5000, initialDelay = 1000)
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。
|
||||
*/
|
||||
@Scheduled(fixedRate = 5000)
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0/10 * * * * ?")
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* spring 主配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableScheduling //启用Spring的计划任务执行功能
|
||||
@EnableAsync //启用Spring的异步方法执行功能
|
||||
@ComponentScan(basePackages = {"com.heibaiying.task"})
|
||||
public class ServletConfig implements WebMvcConfigurer, AsyncConfigurer, SchedulingConfigurer {
|
||||
|
||||
private ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// 任务执行器线程池配置
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
executor.setCorePoolSize(5);
|
||||
executor.setMaxPoolSize(10);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("MyExecutor-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
// 这个方法可以监听到异步程序发生的错误
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new AsyncUncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
||||
System.out.println(method.getName() + "发生错误:" + ex.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池 不然程序无法完全退出 只能kill才能完全退出
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// 调度程序线程池配置
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.setScheduler(taskExecutor());
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public Executor taskExecutor() {
|
||||
return Executors.newScheduledThreadPool(50);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在 ServletConfig.java 中,为方便观察项目定时执行的情况,项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-scheduling-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
@Component
|
||||
public class Task {
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。
|
||||
*/
|
||||
@Scheduled(fixedDelay = 5000, initialDelay = 1000)
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA 方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。
|
||||
*/
|
||||
@Scheduled(fixedRate = 5000)
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB 方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0/10 * * * * ?")
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC 方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* spring 主配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableScheduling //启用 Spring 的计划任务执行功能
|
||||
@EnableAsync //启用 Spring 的异步方法执行功能
|
||||
@ComponentScan(basePackages = {"com.heibaiying.task"})
|
||||
public class ServletConfig implements WebMvcConfigurer, AsyncConfigurer, SchedulingConfigurer {
|
||||
|
||||
private ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// 任务执行器线程池配置
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
executor.setCorePoolSize(5);
|
||||
executor.setMaxPoolSize(10);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("MyExecutor-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
// 这个方法可以监听到异步程序发生的错误
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new AsyncUncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
|
||||
System.out.println(method.getName() + "发生错误:" + ex.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池 不然程序无法完全退出 只能 kill 才能完全退出
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// 调度程序线程池配置
|
||||
@Override
|
||||
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||
taskRegistrar.setScheduler(taskExecutor());
|
||||
}
|
||||
|
||||
// 如果程序结束,需要关闭线程池
|
||||
@Bean(destroyMethod = "shutdown")
|
||||
public Executor taskExecutor() {
|
||||
return Executors.newScheduledThreadPool(50);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有 methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则 methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每 5 秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池 (如果不配置就使用默认的线程池配置)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring 定时任务(xml配置方式)
|
||||
|
||||
# spring 定时任务(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -9,184 +10,184 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在springApplication.xml中,为方便观察项目定时执行的情况,项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-scheduling.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
package com.heibaiying.task;
|
||||
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
public class Task {
|
||||
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/task
|
||||
http://www.springframework.org/schema/task/spring-task.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!--配置定时任务-->
|
||||
<bean id="task" class="com.heibaiying.task.Task"/>
|
||||
<task:scheduled-tasks scheduler="myScheduler">
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。-->
|
||||
<task:scheduled ref="task" method="methodA" fixed-delay="5000" initial-delay="1000"/>
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。-->
|
||||
<task:scheduled ref="task" method="methodB" fixed-rate="5000"/>
|
||||
<!-- cron 表达式-->
|
||||
<task:scheduled ref="task" method="methodC" cron="0/10 * * * * ?"/>
|
||||
</task:scheduled-tasks>
|
||||
|
||||
<!--定义任务调度器线程池的大小-->
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
|
||||
<!--定义任务执行器的线程池大小、等待队列的容量、和拒绝策略-->
|
||||
<task:executor
|
||||
id="executorWithPoolSizeRange"
|
||||
pool-size="5-25"
|
||||
queue-capacity="100"
|
||||
rejection-policy="CALLER_RUNS"/>
|
||||
<!--拒绝策略默认值为ABORT
|
||||
CALLER_RUNS来限制入栈任务
|
||||
DISCARD删除当前任务
|
||||
DISCARD_OLDEST将任务放在队列的头部。-->
|
||||
|
||||
<!--允许在任何Spring管理的对象上检测@Async和@Scheduled注释, 如果存在,将生成用于异步执行带注释的方法的代理。-->
|
||||
<task:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
关于任务的调度配置定义在 springApplication.xml 中,为方便观察项目定时执行的情况,项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-scheduling.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
导入基本依赖
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.heibaiying</groupId>
|
||||
<artifactId>spring-scheduling</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<spring-base-version>5.1.3.RELEASE</spring-base-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>${spring-base-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring scheduling
|
||||
|
||||
#### 2.1 创建定时任务
|
||||
|
||||
```java
|
||||
package com.heibaiying.task;
|
||||
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
*/
|
||||
public class Task {
|
||||
|
||||
public void methodA() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodA 方法执行", LocalDateTime.now()));
|
||||
}
|
||||
|
||||
@Async
|
||||
public void methodB() throws InterruptedException {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodB 方法执行", LocalDateTime.now()));
|
||||
Thread.sleep(10 * 1000);
|
||||
}
|
||||
|
||||
public void methodC() {
|
||||
Thread thread = Thread.currentThread();
|
||||
System.out.println(String.format("线程名称:%s ; 线程 ID:%s ; 调用方法:%s ; 调用时间:%s",
|
||||
thread.getName(), thread.getId(), "methodC 方法执行", LocalDateTime.now()));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 配置定时任务
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/task
|
||||
http://www.springframework.org/schema/task/spring-task.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!--配置定时任务-->
|
||||
<bean id="task" class="com.heibaiying.task.Task"/>
|
||||
<task:scheduled-tasks scheduler="myScheduler">
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。-->
|
||||
<task:scheduled ref="task" method="methodA" fixed-delay="5000" initial-delay="1000"/>
|
||||
<!--基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。-->
|
||||
<task:scheduled ref="task" method="methodB" fixed-rate="5000"/>
|
||||
<!-- cron 表达式-->
|
||||
<task:scheduled ref="task" method="methodC" cron="0/10 * * * * ?"/>
|
||||
</task:scheduled-tasks>
|
||||
|
||||
<!--定义任务调度器线程池的大小-->
|
||||
<task:scheduler id="myScheduler" pool-size="10"/>
|
||||
|
||||
<!--定义任务执行器的线程池大小、等待队列的容量、和拒绝策略-->
|
||||
<task:executor
|
||||
id="executorWithPoolSizeRange"
|
||||
pool-size="5-25"
|
||||
queue-capacity="100"
|
||||
rejection-policy="CALLER_RUNS"/>
|
||||
<!--拒绝策略默认值为 ABORT
|
||||
CALLER_RUNS 来限制入栈任务
|
||||
DISCARD 删除当前任务
|
||||
DISCARD_OLDEST 将任务放在队列的头部。-->
|
||||
|
||||
<!--允许在任何 Spring 管理的对象上检测@Async 和@Scheduled 注释, 如果存在,将生成用于异步执行带注释的方法的代理。-->
|
||||
<task:annotation-driven/>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
**关于调度程序线程池作用说明**:
|
||||
|
||||
按照例子 我们有 methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则 methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
|
||||
|
||||
**关于任务执行线程池作用说明**:
|
||||
|
||||
按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每 5 秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池 (如果不配置就使用默认的线程池配置)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring websocket(注解方式)
|
||||
|
||||
# spring websocket(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -12,204 +13,204 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
|
||||
2. webconfig 包是基础注解的方式配置web,在spring-base-annotation项目中已经讲解过每个类作用;
|
||||
3. CustomHander为消息的自定义处理器;
|
||||
4. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
|
||||
5. webSocketConfig 是websocket 的主要配置类;
|
||||
6. 项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-websocket-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的spring 依赖外,还需要导入webSocket的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的beforeHandshake方法
|
||||
* 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
|
||||
* 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 创建websocket的配置类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :websocket 配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new CustomHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在 session 当中;
|
||||
2. webconfig 包是基础注解的方式配置 web,在 spring-base-annotation 项目中已经讲解过每个类作用;
|
||||
3. CustomHander 为消息的自定义处理器;
|
||||
4. CustomHandershakerInterceptor 为自定义的 websocket 的握手拦截器;
|
||||
5. webSocketConfig 是 websocket 的主要配置类;
|
||||
6. 项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-websocket-annotation.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的 spring 依赖外,还需要导入 webSocket 的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的 beforeHandshake 方法
|
||||
* 把 ServerHttpRequest 中 session 中对应的值拷贝到 WebSocketSession 中。
|
||||
* 如果我们没有实现这个方法,我们在最后的 handler 处理中 是拿不到 session 中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中 session 的 username 总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 创建websocket的配置类
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :websocket 配置
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
|
||||
registry.addHandler(new CustomHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭 ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
# spring websocket(xml配置方式)
|
||||
|
||||
# spring websocket(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
@ -12,234 +13,234 @@
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
|
||||
2. CustomHander为消息的自定义处理器;
|
||||
3. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
|
||||
4. 项目以web的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-websocket.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的spring 依赖外,还需要导入webSocket的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的beforeHandshake方法
|
||||
* 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
|
||||
* 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 配置websocket
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--使用默认的Servlet来响应静态文件 -->
|
||||
<mvc:default-servlet-handler/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!-- 配置视图解析器 -->
|
||||
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
|
||||
id="internalResourceViewResolver">
|
||||
<!-- 前缀 -->
|
||||
<property name="prefix" value="/WEB-INF/jsp/"/>
|
||||
<!-- 后缀 -->
|
||||
<property name="suffix" value=".jsp"/>
|
||||
</bean>
|
||||
|
||||
<!--配置webSocket-->
|
||||
<bean id="customHandler" class="com.heibaiying.websocket.CustomHandler"/>
|
||||
<websocket:handlers>
|
||||
<!--指定webSocket 地址-->
|
||||
<websocket:mapping path="/socket" handler="customHandler"/>
|
||||
<!--webSocket握手-->
|
||||
<websocket:handshake-interceptors>
|
||||
<bean class="com.heibaiying.websocket.CustomHandshakeInterceptor"/>
|
||||
</websocket:handshake-interceptors>
|
||||
</websocket:handlers>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在 session 当中;
|
||||
2. CustomHander 为消息的自定义处理器;
|
||||
3. CustomHandershakerInterceptor 为自定义的 websocket 的握手拦截器;
|
||||
4. 项目以 web 的方式构建。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-websocket.png"/> </div>
|
||||
|
||||
|
||||
|
||||
### 1.2 依赖说明
|
||||
|
||||
除了基本的 spring 依赖外,还需要导入 webSocket 的依赖包
|
||||
|
||||
```xml
|
||||
<!--spring webSocket 的依赖包 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
<version>5.1.3.RELEASE</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、spring websocket
|
||||
|
||||
#### 2.1 创建消息处理类,继承自TextWebSocketHandler
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 自定义消息处理类
|
||||
*/
|
||||
public class CustomHandler extends TextWebSocketHandler {
|
||||
|
||||
private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
|
||||
|
||||
// 建立连接时候触发
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.putIfAbsent(username, session);
|
||||
}
|
||||
|
||||
|
||||
// 关闭连接时候触发
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String username = getNameFromSession(session);
|
||||
nameAndSession.remove(username);
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
|
||||
// 防止中文乱码
|
||||
String msg = URLDecoder.decode(message.getPayload(), "utf-8");
|
||||
String username = getNameFromSession(session);
|
||||
// 简单模拟群发消息
|
||||
TextMessage reply = new TextMessage(username + " : " + msg);
|
||||
nameAndSession.forEach((s, webSocketSession)
|
||||
-> {
|
||||
try {
|
||||
webSocketSession.sendMessage(reply);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String getNameFromSession(WebSocketSession session) {
|
||||
Map<String, Object> attributes = session.getAttributes();
|
||||
return (String) attributes.get(Constant.USER_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
|
||||
|
||||
```java
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 可以按照需求实现权限拦截等功能
|
||||
*/
|
||||
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
InetAddress address = remoteAddress.getAddress();
|
||||
System.out.println(address);
|
||||
/*
|
||||
* 最后需要要显示调用父类方法,父类的 beforeHandshake 方法
|
||||
* 把 ServerHttpRequest 中 session 中对应的值拷贝到 WebSocketSession 中。
|
||||
* 如果我们没有实现这个方法,我们在最后的 handler 处理中 是拿不到 session 中的值
|
||||
* 作为测试 可以注释掉下面这一行 可以发现自定义处理器中 session 的 username 总是为空
|
||||
*/
|
||||
return super.beforeHandshake(request, response, wsHandler, attributes);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 配置websocket
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:mvc="http://www.springframework.org/schema/mvc"
|
||||
xmlns:websocket="http://www.springframework.org/schema/websocket"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context-4.1.xsd
|
||||
http://www.springframework.org/schema/mvc
|
||||
http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
|
||||
http://www.springframework.org/schema/websocket
|
||||
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
|
||||
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--使用默认的 Servlet 来响应静态文件 -->
|
||||
<mvc:default-servlet-handler/>
|
||||
|
||||
<!-- 开启注解驱动 -->
|
||||
<mvc:annotation-driven/>
|
||||
|
||||
<!-- 配置视图解析器 -->
|
||||
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
|
||||
id="internalResourceViewResolver">
|
||||
<!-- 前缀 -->
|
||||
<property name="prefix" value="/WEB-INF/jsp/"/>
|
||||
<!-- 后缀 -->
|
||||
<property name="suffix" value=".jsp"/>
|
||||
</bean>
|
||||
|
||||
<!--配置 webSocket-->
|
||||
<bean id="customHandler" class="com.heibaiying.websocket.CustomHandler"/>
|
||||
<websocket:handlers>
|
||||
<!--指定 webSocket 地址-->
|
||||
<websocket:mapping path="/socket" handler="customHandler"/>
|
||||
<!--webSocket 握手-->
|
||||
<websocket:handshake-interceptors>
|
||||
<bean class="com.heibaiying.websocket.CustomHandshakeInterceptor"/>
|
||||
</websocket:handshake-interceptors>
|
||||
</websocket:handlers>
|
||||
|
||||
</beans>
|
||||
```
|
||||
|
||||
#### 2.4 前端 websocket 的实现
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
|
||||
</head>
|
||||
<body>
|
||||
<input id="message" type="text">
|
||||
<button id="btn">发送消息</button>
|
||||
<div id="show">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let btn = document.getElementById("btn");
|
||||
let message = document.getElementById("message");
|
||||
let show = document.getElementById("show");
|
||||
let ws = new WebSocket("ws://localhost:8080/socket");
|
||||
ws.onmessage = function (evt) {
|
||||
let node = document.createElement("div");
|
||||
node.innerHTML = "<h5>" + evt.data + "</h5>";
|
||||
show.appendChild(node);
|
||||
};
|
||||
btn.addEventListener("click", function () {
|
||||
let data = message.value;
|
||||
console.log(data);
|
||||
if (data) {
|
||||
ws.send(encodeURI(data));
|
||||
} else {
|
||||
alert("请输入消息后发送");
|
||||
}
|
||||
message.value = "";
|
||||
});
|
||||
// 关闭页面时候关闭 ws
|
||||
window.addEventListener("beforeunload", function(event) {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
```
|
||||
|
||||
#### 2.5 简单登录的实现
|
||||
|
||||
```java
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="${pageContext.request.contextPath}/login" method="post">
|
||||
<input name="username" type="text">
|
||||
<button id="btn">输入临时用户名后登录!</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```java
|
||||
@Controller
|
||||
public class LoginController {
|
||||
|
||||
@PostMapping("login")
|
||||
public String login(String username, HttpSession session){
|
||||
session.setAttribute(Constant.USER_NAME,username);
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
### 1.1 项目搭建
|
||||
|
||||
1.新建maven web工程,并引入相应的依赖
|
||||
1.新建 maven web 工程,并引入相应的依赖
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
@ -62,9 +62,9 @@
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
2.得益于servlet3.0和spring的支持,我们可以在没有web.xml的情况下完成关于servlet配置。
|
||||
2.得益于 servlet3.0 和 spring 的支持,我们可以在没有 web.xml 的情况下完成关于 servlet 配置。
|
||||
|
||||
新建DispatcherServletInitializer.java文件,这个类的作用相当于我们在xml方式下web.xml中配置的DispatcherServlet
|
||||
新建 DispatcherServletInitializer.java 文件,这个类的作用相当于我们在 xml 方式下 web.xml 中配置的 DispatcherServlet
|
||||
|
||||
```java
|
||||
package com.heibaiying.config;
|
||||
@ -92,7 +92,7 @@ public class DispatcherServletInitializer extends AbstractAnnotationConfigDispat
|
||||
|
||||
```
|
||||
|
||||
3.新建ServletConfig.java,文件内容如下(这个类相当于我们在xml配置方式中的springApplication.xml)
|
||||
3.新建 ServletConfig.java,文件内容如下 (这个类相当于我们在 xml 配置方式中的 springApplication.xml)
|
||||
|
||||
```java
|
||||
package com.heibaiying.config;
|
||||
@ -144,7 +144,7 @@ public class ServletConfig implements WebMvcConfigurer {
|
||||
|
||||
```
|
||||
|
||||
4.在src 下新建controller用于测试
|
||||
4.在 src 下新建 controller 用于测试
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@ -171,7 +171,7 @@ public class HelloController {
|
||||
|
||||
```
|
||||
|
||||
5.在WEB-INF 下新建jsp文件夹,新建hello.jsp 文件
|
||||
5.在 WEB-INF 下新建 jsp 文件夹,新建 hello.jsp 文件
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@ -185,23 +185,23 @@ public class HelloController {
|
||||
</html>
|
||||
```
|
||||
|
||||
6.启动tomcat服务,访问localhost:8080/mvc/hello
|
||||
6.启动 tomcat 服务,访问 localhost:8080/mvc/hello
|
||||
|
||||
### 1.2 相关注解说明
|
||||
|
||||
**1.@Configuration**
|
||||
|
||||
@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
|
||||
@Configuration 用于定义配置类,可替换 xml 配置文件,被注解的类内部包含有一个或多个被@Bean 注解的方法,这些方法将会被 AnnotationConfigApplicationContext 或 AnnotationConfigWebApplicationContext 类进行扫描,并用于构建 bean 定义,初始化 Spring 容器。
|
||||
|
||||
**2.@EnableWebMvc**
|
||||
|
||||
简单的说就是提供了部分springmvc的功能,例如格式转换和参数绑定。
|
||||
简单的说就是提供了部分 springmvc 的功能,例如格式转换和参数绑定。
|
||||
|
||||
|
||||
|
||||
## 二、配置自定义拦截器
|
||||
|
||||
1.创建自定义拦截器,实现接口HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
|
||||
1.创建自定义拦截器,实现接口 HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
|
||||
|
||||
```java
|
||||
package com.heibaiying.interceptors;
|
||||
@ -219,17 +219,17 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class MyFirstInterceptor implements HandlerInterceptor {
|
||||
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
System.out.println("进入第一个拦截器preHandle");
|
||||
System.out.println("进入第一个拦截器 preHandle");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入afterCompletion这个方法
|
||||
// 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入 afterCompletion 这个方法
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
System.out.println("进入第一个拦截器postHandle");
|
||||
System.out.println("进入第一个拦截器 postHandle");
|
||||
}
|
||||
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
System.out.println("进入第一个拦截器afterCompletion");
|
||||
System.out.println("进入第一个拦截器 afterCompletion");
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -250,22 +250,22 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class MySecondInterceptor implements HandlerInterceptor {
|
||||
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
System.out.println("进入第二个拦截器preHandle");
|
||||
System.out.println("进入第二个拦截器 preHandle");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
System.out.println("进入第二个拦截器postHandle");
|
||||
System.out.println("进入第二个拦截器 postHandle");
|
||||
}
|
||||
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
System.out.println("进入第二个拦截器afterCompletion");
|
||||
System.out.println("进入第二个拦截器 afterCompletion");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
2.在ServletConfig.java中注册自定义拦截器
|
||||
2.在 ServletConfig.java 中注册自定义拦截器
|
||||
|
||||
```java
|
||||
/**
|
||||
@ -279,7 +279,7 @@ public class MySecondInterceptor implements HandlerInterceptor {
|
||||
|
||||
3.关于多个拦截器方法执行顺序的说明
|
||||
|
||||
拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
|
||||
拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的 preHandle 方法会先执行,然而它的 postHandle 方法和 afterCompletion 方法却会后执行。
|
||||
|
||||
|
||||
|
||||
@ -340,7 +340,7 @@ public class NoAuthExceptionResolver implements HandlerExceptionResolver {
|
||||
return new ModelAndView();
|
||||
}
|
||||
|
||||
// 判断是否是Ajax请求
|
||||
// 判断是否是 Ajax 请求
|
||||
private boolean isAjax(HttpServletRequest request) {
|
||||
return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
|
||||
}
|
||||
@ -348,7 +348,7 @@ public class NoAuthExceptionResolver implements HandlerExceptionResolver {
|
||||
|
||||
```
|
||||
|
||||
3.在ServletConfig.java注册自定义异常处理器
|
||||
3.在 ServletConfig.java 注册自定义异常处理器
|
||||
|
||||
```java
|
||||
/**
|
||||
@ -359,7 +359,7 @@ public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> re
|
||||
}
|
||||
```
|
||||
|
||||
4.定义测试controller,抛出自定义异常
|
||||
4.定义测试 controller,抛出自定义异常
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@ -379,7 +379,7 @@ public class HelloController {
|
||||
}
|
||||
```
|
||||
|
||||
注:调用这个controller时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入postHandle这个方法 但一定会进入afterCompletion这个方法
|
||||
注:调用这个 controller 时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入 postHandle 这个方法 但一定会进入 afterCompletion 这个方法
|
||||
|
||||
|
||||
|
||||
@ -387,7 +387,7 @@ public class HelloController {
|
||||
|
||||
### 4.1 参数绑定
|
||||
|
||||
1.新建Programmer.java
|
||||
1.新建 Programmer.java
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@ -412,9 +412,9 @@ public class Programmer {
|
||||
|
||||
```
|
||||
|
||||
注:@Data 是lombok包下的注解,用来生成相应的set、get方法,使得类的书写更为简洁。
|
||||
注:@Data 是 lombok 包下的注解,用来生成相应的 set、get 方法,使得类的书写更为简洁。
|
||||
|
||||
2.新建ParamBindController.java 文件
|
||||
2.新建 ParamBindController.java 文件
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@ -475,7 +475,7 @@ public class ParamBindController {
|
||||
|
||||
```
|
||||
|
||||
3.新建param.jsp 文件
|
||||
3.新建 param.jsp 文件
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@ -495,11 +495,11 @@ public class ParamBindController {
|
||||
|
||||
```
|
||||
|
||||
4.启动tomcat,用[postman](https://www.getpostman.com/)软件发送请求进行测试
|
||||
4.启动 tomcat,用[postman](https://www.getpostman.com/) 软件发送请求进行测试
|
||||
|
||||
### 4.2 关于日期格式转换的三种方法
|
||||
|
||||
1.如上实例代码所示,在对应的controller中初始化绑定
|
||||
1.如上实例代码所示,在对应的 controller 中初始化绑定
|
||||
|
||||
```java
|
||||
@InitBinder
|
||||
@ -508,13 +508,13 @@ public class ParamBindController {
|
||||
}
|
||||
```
|
||||
|
||||
2.利用@DateTimeFormat注解,如果是用实体类去接收参数,则在对应的属性上用@DateTimeFormat和@JsonFormat声明
|
||||
2.利用@DateTimeFormat 注解,如果是用实体类去接收参数,则在对应的属性上用@DateTimeFormat 和@JsonFormat 声明
|
||||
|
||||
```java
|
||||
public String param(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday)
|
||||
```
|
||||
|
||||
3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在ServletConfig.java中进行注册
|
||||
3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在 ServletConfig.java 中进行注册
|
||||
|
||||
```java
|
||||
package com.heibaiying.convert;
|
||||
@ -558,7 +558,7 @@ public void addFormatters(FormatterRegistry registry) {
|
||||
|
||||
## 五、数据校验
|
||||
|
||||
1.spring支持的数据校验是JSR303的标准,需要引入依赖的jar包
|
||||
1.spring 支持的数据校验是 JSR303 的标准,需要引入依赖的 jar 包
|
||||
|
||||
```java
|
||||
<!-- 数据校验依赖包 -->
|
||||
@ -574,7 +574,7 @@ public void addFormatters(FormatterRegistry registry) {
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2.新建测试ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上bindingResult参数,这个参数中包含了校验的结果
|
||||
2.新建测试 ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上 bindingResult 参数,这个参数中包含了校验的结果
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@ -620,7 +620,7 @@ public class ParamValidController {
|
||||
|
||||
```
|
||||
|
||||
3.在Programmer.java的对应属性上加上注解约束(支持的注解可以在javax.validation.constraints包中查看)
|
||||
3.在 Programmer.java 的对应属性上加上注解约束 (支持的注解可以在 javax.validation.constraints 包中查看)
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@ -655,7 +655,7 @@ public class Programmer {
|
||||
|
||||
#### 6.1 文件上传
|
||||
|
||||
1.在ServletConfig.java中进行配置,使之支持文件上传
|
||||
1.在 ServletConfig.java 中进行配置,使之支持文件上传
|
||||
|
||||
```java
|
||||
/**
|
||||
@ -671,7 +671,7 @@ public CommonsMultipartResolver multipartResolver(){
|
||||
}
|
||||
```
|
||||
|
||||
2.新建测试上传的FileController.java
|
||||
2.新建测试上传的 FileController.java
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@ -714,7 +714,7 @@ public class FileController {
|
||||
*/
|
||||
@PostMapping("upFile")
|
||||
public String upFile(MultipartFile file, HttpSession session) {
|
||||
//保存在项目根目录下image文件夹下,如果文件夹不存在则创建
|
||||
//保存在项目根目录下 image 文件夹下,如果文件夹不存在则创建
|
||||
FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
|
||||
// success.jsp 就是一个简单的成功页面
|
||||
return "success";
|
||||
@ -732,7 +732,7 @@ public class FileController {
|
||||
}
|
||||
|
||||
/***
|
||||
* 多文件上传方式2 分别为不同文件指定不同名字
|
||||
* 多文件上传方式 2 分别为不同文件指定不同名字
|
||||
*/
|
||||
@PostMapping("upFiles2")
|
||||
public String upFile(String extendParam,
|
||||
@ -748,7 +748,7 @@ public class FileController {
|
||||
|
||||
```
|
||||
|
||||
3.其中工具类FileUtil.java代码如下
|
||||
3.其中工具类 FileUtil.java 代码如下
|
||||
|
||||
```java
|
||||
package com.heibaiying.utils;
|
||||
@ -803,7 +803,7 @@ public class FileUtil {
|
||||
}
|
||||
```
|
||||
|
||||
4.新建用于上传的jsp页面,上传文件时表单必须声明 enctype="multipart/form-data"
|
||||
4.新建用于上传的 jsp 页面,上传文件时表单必须声明 enctype="multipart/form-data"
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@ -820,13 +820,13 @@ public class FileUtil {
|
||||
</form>
|
||||
|
||||
<form action="${pageContext.request.contextPath }/upFiles" method="post" enctype="multipart/form-data">
|
||||
请选择上传文件(多选):<input name="file" type="file" multiple><br>
|
||||
请选择上传文件 (多选):<input name="file" type="file" multiple><br>
|
||||
<input type="submit" value="点击上传文件">
|
||||
</form>
|
||||
|
||||
<form action="${pageContext.request.contextPath }/upFiles2" method="post" enctype="multipart/form-data">
|
||||
请选择上传文件1:<input name="file1" type="file"><br>
|
||||
请选择上传文件2:<input name="file2" type="file"><br>
|
||||
请选择上传文件 1:<input name="file1" type="file"><br>
|
||||
请选择上传文件 2:<input name="file2" type="file"><br>
|
||||
文件内容额外备注: <input name="extendParam" type="text"><br>
|
||||
<input type="submit" value="点击上传文件">
|
||||
</form>
|
||||
@ -838,7 +838,7 @@ public class FileUtil {
|
||||
|
||||
#### 6.2 文件下载
|
||||
|
||||
1.在fileController.java中加上方法:
|
||||
1.在 fileController.java 中加上方法:
|
||||
|
||||
```java
|
||||
/***
|
||||
@ -869,7 +869,7 @@ public class FileUtil {
|
||||
}
|
||||
```
|
||||
|
||||
2.其中fileDownload.jsp 如下:
|
||||
2.其中 fileDownload.jsp 如下:
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@ -887,7 +887,7 @@ public class FileUtil {
|
||||
|
||||
## 七、Restful风格的请求
|
||||
|
||||
1.新建Pet.java实体类
|
||||
1.新建 Pet.java 实体类
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@ -896,7 +896,7 @@ import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :测试restful风格的实体类
|
||||
* @description :测试 restful 风格的实体类
|
||||
*/
|
||||
|
||||
@Data
|
||||
@ -909,13 +909,13 @@ public class Pet {
|
||||
|
||||
```
|
||||
|
||||
2.新建RestfulController.java,用@PathVariable和@ModelAttribute注解进行参数绑定。
|
||||
2.新建 RestfulController.java,用@PathVariable 和@ModelAttribute 注解进行参数绑定。
|
||||
|
||||
注: 在REST中,资源通过URL进行识别和定位。REST中的行为是通过HTTP方法定义的。在进行不同行为时对应HTTP方法和Spring注解分别如下:
|
||||
注: 在 REST 中,资源通过 URL 进行识别和定位。REST 中的行为是通过 HTTP 方法定义的。在进行不同行为时对应 HTTP 方法和 Spring 注解分别如下:
|
||||
|
||||
- 创建资源时:POST(PostMapping)
|
||||
- 读取资源时:GET( @GetMapping)
|
||||
- 更新资源时:PUT或PATCH(PutMapping、PatchMapping)
|
||||
- 更新资源时:PUT 或 PATCH(PutMapping、PatchMapping)
|
||||
- 删除资源时:DELETE(DeleteMapping)
|
||||
|
||||
```java
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
### 1.1 项目搭建
|
||||
|
||||
1.新建maven web工程,并引入相应的依赖
|
||||
1.新建 maven web 工程,并引入相应的依赖
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
@ -63,7 +63,7 @@
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
2.配置web.xml
|
||||
2.配置 web.xml
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@ -73,7 +73,7 @@
|
||||
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||
version="3.1">
|
||||
|
||||
<!--配置spring前端控制器-->
|
||||
<!--配置 spring 前端控制器-->
|
||||
<servlet>
|
||||
<servlet-name>springMvc</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
@ -92,7 +92,7 @@
|
||||
</web-app>
|
||||
```
|
||||
|
||||
3.在resources下新建springApplication.xml文件,文件内容如下:
|
||||
3.在 resources 下新建 springApplication.xml 文件,文件内容如下:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@ -107,7 +107,7 @@
|
||||
<!-- 开启注解包扫描-->
|
||||
<context:component-scan base-package="com.heibaiying.*"/>
|
||||
|
||||
<!--使用默认的Servlet来响应静态文件 详见 1.2 -->
|
||||
<!--使用默认的 Servlet 来响应静态文件 详见 1.2 -->
|
||||
<mvc:default-servlet-handler/>
|
||||
|
||||
<!-- 开启注解驱动 详见 1.2 -->
|
||||
@ -125,7 +125,7 @@
|
||||
</beans>
|
||||
```
|
||||
|
||||
4.在src 下新建controller用于测试
|
||||
4.在 src 下新建 controller 用于测试
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@ -152,7 +152,7 @@ public class HelloController {
|
||||
|
||||
```
|
||||
|
||||
5.在WEB-INF 下新建jsp文件夹,新建hello.jsp 文件
|
||||
5.在 WEB-INF 下新建 jsp 文件夹,新建 hello.jsp 文件
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@ -166,15 +166,15 @@ public class HelloController {
|
||||
</html>
|
||||
```
|
||||
|
||||
6.启动tomcat服务,访问localhost:8080/mvc/hello
|
||||
6.启动 tomcat 服务,访问 localhost:8080/mvc/hello
|
||||
|
||||
### 1.2 相关配置讲解
|
||||
|
||||
**1.\<mvc:default-servlet-handler/>**
|
||||
|
||||
在web.xml配置中,我们将DispatcherServlet的拦截路径设置为“\”,则spring会捕获所有web请求,包括对静态资源的请求,为了正确处理对静态资源的请求,spring提供了两种解决方案:
|
||||
在 web.xml 配置中,我们将 DispatcherServlet 的拦截路径设置为“\”,则 spring 会捕获所有 web 请求,包括对静态资源的请求,为了正确处理对静态资源的请求,spring 提供了两种解决方案:
|
||||
|
||||
- 配置\<mvc:default-servlet-handler/> : 配置<mvc:default-servlet-handler />后,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。
|
||||
- 配置\<mvc:default-servlet-handler/> : 配置<mvc:default-servlet-handler />后,会在 Spring MVC 上下文中定义一个 org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的 URL 进行筛查,如果发现是静态资源的请求,就将该请求转由 Web 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。
|
||||
|
||||
- 配置\<mvc:resources /> :指定静态资源的位置和路径映射:
|
||||
|
||||
@ -187,13 +187,13 @@ public class HelloController {
|
||||
**2.\<mvc:annotation-driven/>**
|
||||
|
||||
<mvc:annotation-driven /> 会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter
|
||||
两个bean,用以支持@Controllers分发请求。并提供了数据绑定、参数转换、json转换等功能,所以必须加上这个配置。
|
||||
两个 bean,用以支持@Controllers 分发请求。并提供了数据绑定、参数转换、json 转换等功能,所以必须加上这个配置。
|
||||
|
||||
|
||||
|
||||
## 二、配置自定义拦截器
|
||||
|
||||
1.创建自定义拦截器,实现接口HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
|
||||
1.创建自定义拦截器,实现接口 HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
|
||||
|
||||
```java
|
||||
package com.heibaiying.interceptors;
|
||||
@ -211,17 +211,17 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class MyFirstInterceptor implements HandlerInterceptor {
|
||||
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
System.out.println("进入第一个拦截器preHandle");
|
||||
System.out.println("进入第一个拦截器 preHandle");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入afterCompletion这个方法
|
||||
// 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入 afterCompletion 这个方法
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
System.out.println("进入第一个拦截器postHandle");
|
||||
System.out.println("进入第一个拦截器 postHandle");
|
||||
}
|
||||
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
System.out.println("进入第一个拦截器afterCompletion");
|
||||
System.out.println("进入第一个拦截器 afterCompletion");
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -242,22 +242,22 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class MySecondInterceptor implements HandlerInterceptor {
|
||||
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
System.out.println("进入第二个拦截器preHandle");
|
||||
System.out.println("进入第二个拦截器 preHandle");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
|
||||
System.out.println("进入第二个拦截器postHandle");
|
||||
System.out.println("进入第二个拦截器 postHandle");
|
||||
}
|
||||
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
System.out.println("进入第二个拦截器afterCompletion");
|
||||
System.out.println("进入第二个拦截器 afterCompletion");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
2.在springApplication.xml中注册自定义拦截器
|
||||
2.在 springApplication.xml 中注册自定义拦截器
|
||||
|
||||
```xml
|
||||
<!--配置拦截器-->
|
||||
@ -276,7 +276,7 @@ public class MySecondInterceptor implements HandlerInterceptor {
|
||||
|
||||
3.关于多个拦截器方法执行顺序的说明
|
||||
|
||||
拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
|
||||
拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的 preHandle 方法会先执行,然而它的 postHandle 方法和 afterCompletion 方法却会后执行。
|
||||
|
||||
|
||||
|
||||
@ -337,7 +337,7 @@ public class NoAuthExceptionResolver implements HandlerExceptionResolver {
|
||||
return new ModelAndView();
|
||||
}
|
||||
|
||||
// 判断是否是Ajax请求
|
||||
// 判断是否是 Ajax 请求
|
||||
private boolean isAjax(HttpServletRequest request) {
|
||||
return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
|
||||
}
|
||||
@ -345,14 +345,14 @@ public class NoAuthExceptionResolver implements HandlerExceptionResolver {
|
||||
|
||||
```
|
||||
|
||||
3.在springApplication.xml注册自定义异常处理器
|
||||
3.在 springApplication.xml 注册自定义异常处理器
|
||||
|
||||
```xml
|
||||
<!--配置全局异常处理器-->
|
||||
<bean class="com.heibaiying.exception.NoAuthExceptionResolver"/>
|
||||
```
|
||||
|
||||
4.定义测试controller,抛出自定义异常
|
||||
4.定义测试 controller,抛出自定义异常
|
||||
|
||||
```java
|
||||
@Controller
|
||||
@ -372,7 +372,7 @@ public class HelloController {
|
||||
}
|
||||
```
|
||||
|
||||
注:调用这个controller时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入postHandle这个方法 但一定会进入afterCompletion这个方法
|
||||
注:调用这个 controller 时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入 postHandle 这个方法 但一定会进入 afterCompletion 这个方法
|
||||
|
||||
|
||||
|
||||
@ -380,7 +380,7 @@ public class HelloController {
|
||||
|
||||
### 4.1 参数绑定
|
||||
|
||||
1.新建Programmer.java
|
||||
1.新建 Programmer.java
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@ -405,9 +405,9 @@ public class Programmer {
|
||||
|
||||
```
|
||||
|
||||
注:@Data 是lombok包下的注解,用来生成相应的set、get方法,使得类的书写更为简洁。
|
||||
注:@Data 是 lombok 包下的注解,用来生成相应的 set、get 方法,使得类的书写更为简洁。
|
||||
|
||||
2.新建ParamBindController.java 文件
|
||||
2.新建 ParamBindController.java 文件
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@ -468,7 +468,7 @@ public class ParamBindController {
|
||||
|
||||
```
|
||||
|
||||
3.新建param.jsp 文件
|
||||
3.新建 param.jsp 文件
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@ -488,11 +488,11 @@ public class ParamBindController {
|
||||
|
||||
```
|
||||
|
||||
4.启动tomcat,用[postman](https://www.getpostman.com/)软件发送请求进行测试
|
||||
4.启动 tomcat,用[postman](https://www.getpostman.com/) 软件发送请求进行测试
|
||||
|
||||
### 4.2 关于日期格式转换的三种方法
|
||||
|
||||
1.如上实例代码所示,在对应的controller中初始化绑定
|
||||
1.如上实例代码所示,在对应的 controller 中初始化绑定
|
||||
|
||||
```java
|
||||
@InitBinder
|
||||
@ -501,13 +501,13 @@ public class ParamBindController {
|
||||
}
|
||||
```
|
||||
|
||||
2.利用@DateTimeFormat注解,如果是用实体类去接收参数,则在对应的属性上用@DateTimeFormat和@JsonFormat声明
|
||||
2.利用@DateTimeFormat 注解,如果是用实体类去接收参数,则在对应的属性上用@DateTimeFormat 和@JsonFormat 声明
|
||||
|
||||
```java
|
||||
public String param(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday)
|
||||
```
|
||||
|
||||
3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在springApplication.xml中进行注册
|
||||
3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在 springApplication.xml 中进行注册
|
||||
|
||||
```java
|
||||
package com.heibaiying.convert;
|
||||
@ -553,7 +553,7 @@ springApplication.xml
|
||||
|
||||
## 五、数据校验
|
||||
|
||||
1.spring支持的数据校验是JSR303的标准,需要引入依赖的jar包
|
||||
1.spring 支持的数据校验是 JSR303 的标准,需要引入依赖的 jar 包
|
||||
|
||||
```java
|
||||
<!-- 数据校验依赖包 -->
|
||||
@ -569,7 +569,7 @@ springApplication.xml
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2.新建测试ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上bindingResult参数,这个参数中包含了校验的结果
|
||||
2.新建测试 ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上 bindingResult 参数,这个参数中包含了校验的结果
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@ -615,7 +615,7 @@ public class ParamValidController {
|
||||
|
||||
```
|
||||
|
||||
3.在Programmer.java的对应属性上加上注解约束(支持的注解可以在javax.validation.constraints包中查看)
|
||||
3.在 Programmer.java 的对应属性上加上注解约束 (支持的注解可以在 javax.validation.constraints 包中查看)
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@ -650,7 +650,7 @@ public class Programmer {
|
||||
|
||||
#### 6.1 文件上传
|
||||
|
||||
1.在springApplication.xml中进行配置,使之支持文件上传
|
||||
1.在 springApplication.xml 中进行配置,使之支持文件上传
|
||||
|
||||
```xml
|
||||
<!--配置文件上传-->
|
||||
@ -663,7 +663,7 @@ public class Programmer {
|
||||
</bean>
|
||||
```
|
||||
|
||||
2.新建测试上传的FileController.java
|
||||
2.新建测试上传的 FileController.java
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
@ -706,7 +706,7 @@ public class FileController {
|
||||
*/
|
||||
@PostMapping("upFile")
|
||||
public String upFile(MultipartFile file, HttpSession session) {
|
||||
//保存在项目根目录下image文件夹下,如果文件夹不存在则创建
|
||||
//保存在项目根目录下 image 文件夹下,如果文件夹不存在则创建
|
||||
FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
|
||||
// success.jsp 就是一个简单的成功页面
|
||||
return "success";
|
||||
@ -724,7 +724,7 @@ public class FileController {
|
||||
}
|
||||
|
||||
/***
|
||||
* 多文件上传方式2 分别为不同文件指定不同名字
|
||||
* 多文件上传方式 2 分别为不同文件指定不同名字
|
||||
*/
|
||||
@PostMapping("upFiles2")
|
||||
public String upFile(String extendParam,
|
||||
@ -740,7 +740,7 @@ public class FileController {
|
||||
|
||||
```
|
||||
|
||||
3.其中工具类FileUtil.java代码如下
|
||||
3.其中工具类 FileUtil.java 代码如下
|
||||
|
||||
```java
|
||||
package com.heibaiying.utils;
|
||||
@ -795,7 +795,7 @@ public class FileUtil {
|
||||
}
|
||||
```
|
||||
|
||||
4.新建用于上传的jsp页面,上传文件时表单必须声明 enctype="multipart/form-data"
|
||||
4.新建用于上传的 jsp 页面,上传文件时表单必须声明 enctype="multipart/form-data"
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@ -812,13 +812,13 @@ public class FileUtil {
|
||||
</form>
|
||||
|
||||
<form action="${pageContext.request.contextPath }/upFiles" method="post" enctype="multipart/form-data">
|
||||
请选择上传文件(多选):<input name="file" type="file" multiple><br>
|
||||
请选择上传文件 (多选):<input name="file" type="file" multiple><br>
|
||||
<input type="submit" value="点击上传文件">
|
||||
</form>
|
||||
|
||||
<form action="${pageContext.request.contextPath }/upFiles2" method="post" enctype="multipart/form-data">
|
||||
请选择上传文件1:<input name="file1" type="file"><br>
|
||||
请选择上传文件2:<input name="file2" type="file"><br>
|
||||
请选择上传文件 1:<input name="file1" type="file"><br>
|
||||
请选择上传文件 2:<input name="file2" type="file"><br>
|
||||
文件内容额外备注: <input name="extendParam" type="text"><br>
|
||||
<input type="submit" value="点击上传文件">
|
||||
</form>
|
||||
@ -830,7 +830,7 @@ public class FileUtil {
|
||||
|
||||
#### 6.2 文件下载
|
||||
|
||||
1.在fileController.java中加上方法:
|
||||
1.在 fileController.java 中加上方法:
|
||||
|
||||
```java
|
||||
/***
|
||||
@ -861,7 +861,7 @@ public class FileUtil {
|
||||
}
|
||||
```
|
||||
|
||||
2.其中fileDownload.jsp 如下:
|
||||
2.其中 fileDownload.jsp 如下:
|
||||
|
||||
```jsp
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
@ -879,7 +879,7 @@ public class FileUtil {
|
||||
|
||||
## 七、Restful风格的请求
|
||||
|
||||
1.新建Pet.java实体类
|
||||
1.新建 Pet.java 实体类
|
||||
|
||||
```java
|
||||
package com.heibaiying.bean;
|
||||
@ -888,7 +888,7 @@ import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description :测试restful风格的实体类
|
||||
* @description :测试 restful 风格的实体类
|
||||
*/
|
||||
|
||||
@Data
|
||||
@ -901,13 +901,13 @@ public class Pet {
|
||||
|
||||
```
|
||||
|
||||
2.新建RestfulController.java,用@PathVariable和@ModelAttribute注解进行参数绑定。
|
||||
2.新建 RestfulController.java,用@PathVariable 和@ModelAttribute 注解进行参数绑定。
|
||||
|
||||
注: 在REST中,资源通过URL进行识别和定位。REST中的行为是通过HTTP方法定义的。在进行不同行为时对应HTTP方法和Spring注解分别如下:
|
||||
注: 在 REST 中,资源通过 URL 进行识别和定位。REST 中的行为是通过 HTTP 方法定义的。在进行不同行为时对应 HTTP 方法和 Spring 注解分别如下:
|
||||
|
||||
- 创建资源时:POST(PostMapping)
|
||||
- 读取资源时:GET( @GetMapping)
|
||||
- 更新资源时:PUT或PATCH(PutMapping、PatchMapping)
|
||||
- 更新资源时:PUT 或 PATCH(PutMapping、PatchMapping)
|
||||
- 删除资源时:DELETE(DeleteMapping)
|
||||
|
||||
```java
|
||||
|
Reference in New Issue
Block a user