增加README.md文章导航
This commit is contained in:
parent
1569c0ad61
commit
4fc9b96f2a
2
spring-boot/spring-boot-swagger2/README.md
Normal file
2
spring-boot/spring-boot-swagger2/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
@Profile({"dev","test"})
|
||||
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
|
61
spring-boot/spring-boot-swagger2/pom.xml
Normal file
61
spring-boot/spring-boot-swagger2/pom.xml
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.2.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>com.heibaiying.springboot</groupId>
|
||||
<artifactId>spring-boot-swagger2</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>spring-boot-swagger2</name>
|
||||
<description>swagger2 project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--swagger2-->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
<!--swagger-ui -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
<version>2.9.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,14 @@
|
||||
package com.heibaiying.springboot;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SpringBootSwagger2Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootSwagger2Application.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package com.heibaiying.springboot.bean;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class Product {
|
||||
|
||||
private long id;
|
||||
private String name;
|
||||
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
|
||||
private Date date;
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package com.heibaiying.springboot.config;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import static com.google.common.base.Predicates.not;
|
||||
import static springfox.documentation.builders.PathSelectors.regex;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : Swagger 配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Value("${swagger.enable}")
|
||||
private boolean swaggerEnable;
|
||||
|
||||
/***
|
||||
* 配置swagger
|
||||
* 开发和测试环境下可以开启swagger辅助进行调试,而生产环境下可以关闭或者进行相应的权限控制,防止接口信息泄露
|
||||
*/
|
||||
@Bean
|
||||
public Docket createRestApi() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.enable(swaggerEnable)
|
||||
.apiInfo(apiInfo())
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.heibaiying.springboot.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.paths(doFilteringRules())
|
||||
.build();
|
||||
}
|
||||
|
||||
/***
|
||||
* 接口文档的描述信息
|
||||
*/
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title("spring boot swagger2 用例")
|
||||
.description("描述")
|
||||
.licenseUrl("https://mit-license.org/")
|
||||
.version("1.0")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 可以使用正则定义url过滤规则
|
||||
*/
|
||||
private Predicate<String> doFilteringRules() {
|
||||
return not(
|
||||
regex("/ignore/*")
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package com.heibaiying.springboot.controller;
|
||||
|
||||
import com.heibaiying.springboot.bean.Product;
|
||||
|
||||
import io.swagger.annotations.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import springfox.documentation.annotations.ApiIgnore;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author : heibaiying
|
||||
* @description : 产品查询接口
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@Api(value = "产品接口", description = "产品信息接口")
|
||||
@RestController
|
||||
public class ProductController {
|
||||
|
||||
/***
|
||||
* 一个标准的swagger注解
|
||||
*/
|
||||
@ApiOperation(notes = "查询所有产品", value = "产品查询接口")
|
||||
@ApiImplicitParams(
|
||||
@ApiImplicitParam(name = "id", value = "产品编号", paramType = "path", defaultValue = "1")
|
||||
)
|
||||
@ApiResponses(value = {
|
||||
@ApiResponse(code = 200, message = "请求成功"),
|
||||
@ApiResponse(code = 400, message = "无效的请求"),
|
||||
@ApiResponse(code = 401, message = "未经过授权认证"),
|
||||
@ApiResponse(code = 403, message = "已经过授权认证,但是没有该资源对应的访问权限"),
|
||||
@ApiResponse(code = 404, message = "服务器找不到给定的资源,商品不存在"),
|
||||
@ApiResponse(code = 500, message = "服务器错误")
|
||||
})
|
||||
@GetMapping(value = "/product/{id}", produces = "application/json")
|
||||
public ResponseEntity<Product> getProduct(@PathVariable long id) {
|
||||
Product product = new Product(id, "product" + id, new Date());
|
||||
return ResponseEntity.ok(product);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 如果用实体类接收参数,则用实体类对应的属性名称指定参数
|
||||
*/
|
||||
@ApiOperation(notes = "保存产品", value = "产品保存接口")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "id", value = "产品编号", paramType = "body", defaultValue = "1"),
|
||||
@ApiImplicitParam(name = "name", value = "产品名称", paramType = "body"),
|
||||
@ApiImplicitParam(name = "date", value = "产品生产日期", paramType = "body")
|
||||
}
|
||||
)
|
||||
@PostMapping(value = "/product")
|
||||
public ResponseEntity<Void> saveProduct(@RequestBody Product product) {
|
||||
System.out.println(product);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
* 在配置类中指明了该接口不被扫描到,可以在配置类中使用正则指定某一类符合规则的接口不被扫描到
|
||||
*/
|
||||
@ApiOperation(notes = "该接口会被忽略", value = "产品保存接口")
|
||||
@PostMapping(value = "/ignore")
|
||||
public ResponseEntity<Product> ignore() {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 不加上任何swagger相关的注解也会被扫描到 如果不希望被扫描到,需要用 @ApiIgnore 修饰
|
||||
*/
|
||||
@PostMapping(value = "/normal")
|
||||
public ResponseEntity<Void> normal() {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@ApiIgnore
|
||||
@PostMapping(value = "/apiIgnore")
|
||||
public ResponseEntity<Void> apiIgnore() {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
swagger.enable = true
|
@ -0,0 +1,17 @@
|
||||
package com.heibaiying.springboot;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class SpringBootSwagger2ApplicationTests {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,236 +1,249 @@
|
||||
# spring AOP(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 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)
|
||||
```
|
||||
|
||||
# spring AOP(注解方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-aop">二、spring aop</a><br/>
|
||||
<a href="#21-创建待切入接口及其实现类">2.1 创建待切入接口及其实现类</a><br/>
|
||||
<a href="#22-创建自定义切面类">2.2 创建自定义切面类</a><br/>
|
||||
<a href="#23-配置切面">2.3 配置切面</a><br/>
|
||||
<a href="#24-测试切面">2.4 测试切面</a><br/>
|
||||
<a href="#25--切面执行顺序">2.5 切面执行顺序</a><br/>
|
||||
<a href="#附-关于切面表达式的说明">附: 关于切面表达式的说明</a><br/>
|
||||
## 正文<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)
|
||||
```
|
||||
|
||||
更多表达式可以参考官方文档:[Declaring a Pointcut](https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html#aop-pointcuts)
|
@ -1,227 +1,239 @@
|
||||
# spring AOP(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 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>
|
||||
# spring AOP(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-aop">二、spring aop</a><br/>
|
||||
<a href="#21-创建待切入接口及其实现类">2.1 创建待切入接口及其实现类</a><br/>
|
||||
<a href="#22-创建自定义切面类">2.2 创建自定义切面类</a><br/>
|
||||
<a href="#23-配置切面">2.3 配置切面</a><br/>
|
||||
<a href="#24-测试切面">2.4 测试切面</a><br/>
|
||||
<a href="#附-关于切面表达式的说明">附: 关于切面表达式的说明</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
### 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)
|
@ -1,5 +1,18 @@
|
||||
# spring +druid+ mybatis(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#1创建maven工程除了Spring基本依赖外还需要导入mybatis和druid的相关依赖">1、创建maven工程,除了Spring基本依赖外,还需要导入mybatis和druid的相关依赖</a><br/>
|
||||
<a href="#2新建-DispatcherServletInitializerjava继承自AbstractAnnotationConfigDispatcherServletInitializer等价于我们在webxml中配置的前端控制器">2、新建 DispatcherServletInitializer.java继承自AbstractAnnotationConfigDispatcherServletInitializer,等价于我们在web.xml中配置的前端控制器</a><br/>
|
||||
<a href="#3基于servlet-30的支持可以采用注解的方式注册druid的servlet和filter">3、基于servlet 3.0的支持,可以采用注解的方式注册druid的servlet和filter </a><br/>
|
||||
<a href="#4在resources文件夹下新建数据库配置文件mysqlpropertiesoracleproperties">4、在resources文件夹下新建数据库配置文件mysql.properties、oracle.properties</a><br/>
|
||||
<a href="#5在新建数据库配置映射类DataSourceConfigjava">5、在新建数据库配置映射类DataSourceConfig.java</a><br/>
|
||||
<a href="#6新建ServletConfigjava进行数据库相关配置">6、新建ServletConfig.java,进行数据库相关配置</a><br/>
|
||||
<a href="#7新建mybtais-配置文件按需要进行额外参数配置-更多settings配置项可以参考[官方文档]http//wwwmybatisorg/mybatis-3/zh/configurationhtml">7、新建mybtais 配置文件,按需要进行额外参数配置, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)</a><br/>
|
||||
<a href="#8新建查询接口及其对应的mapper文件">8、新建查询接口及其对应的mapper文件</a><br/>
|
||||
<a href="#9新建测试controller进行测试">9、新建测试controller进行测试</a><br/>
|
||||
<a href="#10druid-监控页面访问地址http//localhost8080/druid/indexhtml">10、druid 监控页面访问地址http://localhost:8080/druid/index.html</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
### 项目目录结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-druid-mybatis-annotation.png"/> </div>
|
||||
@ -61,7 +74,7 @@ public class DispatcherServletInitializer extends AbstractAnnotationConfigDispat
|
||||
}
|
||||
```
|
||||
|
||||
#### 3、基础servlet 3.0的支持,可以采用注解的方式注册druid的servlet和filter
|
||||
#### 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小节 注解和可插拔性**
|
||||
|
||||
@ -311,7 +324,7 @@ public interface OracleDao {
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 9.新建测试controller进行测试
|
||||
#### 9、新建测试controller进行测试
|
||||
|
||||
```java
|
||||
@RestController
|
||||
|
@ -1,5 +1,16 @@
|
||||
# spring +druid+ mybatis(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#1创建maven工程除了Spring基本依赖外还需要导入mybatis和druid的相关依赖">1、创建maven工程,除了Spring基本依赖外,还需要导入mybatis和druid的相关依赖</a><br/>
|
||||
<a href="#2在webxml-中配置spring前端控制器druid监控台servlet和filter">2、在web.xml 中配置spring前端控制器、druid监控台servlet和filter</a><br/>
|
||||
<a href="#3在resources文件夹下新建数据库配置文件jdbcproperties">3、在resources文件夹下新建数据库配置文件jdbc.properties</a><br/>
|
||||
<a href="#4在resources文件夹下创建springApplicationxml-配置文件和druidxml配置文件">4、在resources文件夹下创建springApplication.xml 配置文件和druid.xml配置文件 </a><br/>
|
||||
<a href="#5新建mybtais-配置文件按需要进行额外配置更多settings配置项可以参考[官方文档]http//wwwmybatisorg/mybatis-3/zh/configurationhtml">5、新建mybtais 配置文件,按需要进行额外配置,更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)</a><br/>
|
||||
<a href="#6新建查询接口及其对应的mapper文件">6、新建查询接口及其对应的mapper文件</a><br/>
|
||||
<a href="#7新建测试controller进行测试">7、新建测试controller进行测试</a><br/>
|
||||
<a href="#8druid-监控页面访问地址http//localhost8080/druid/indexhtml">8、druid 监控页面访问地址http://localhost:8080/druid/index.html</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
### 项目目录结构
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-druid-mybatis.png"/> </div>
|
||||
@ -308,7 +319,7 @@ public interface OracleDao {
|
||||
</mapper>
|
||||
```
|
||||
|
||||
#### 7.新建测试controller进行测试
|
||||
#### 7、新建测试controller进行测试
|
||||
|
||||
```java
|
||||
package com.heibaiying.controller;
|
||||
|
@ -1,327 +1,341 @@
|
||||
# spring 整合 dubbo(注解方式)
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo-annotation下构建三个子模块:
|
||||
|
||||
1. dubbo-ano-common 是公共模块,用于存放公共的接口和bean,被dubbo-ano-provider和dubbo-ano-provider在pom.xml中引用;
|
||||
2. dubbo-ano-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-ano-provider 是服务的消费者,调用provider提供的查询服务。
|
||||
|
||||
1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<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
|
||||
# spring 整合 dubbo(注解方式)
|
||||
## 目录<br/>
|
||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
|
||||
<a href="#二项目依赖">二、项目依赖</a><br/>
|
||||
<a href="#三公共模块dubbo-ano-common">三、公共模块(dubbo-ano-common)</a><br/>
|
||||
<a href="#四-服务提供者dubbo-ano-provider">四、 服务提供者(dubbo-ano-provider)</a><br/>
|
||||
<a href="#41-提供方配置">4.1 提供方配置</a><br/>
|
||||
<a href="#42--使用注解@Service暴露服务">4.2 使用注解@Service暴露服务</a><br/>
|
||||
<a href="#五服务消费者dubbo-ano-consumer">五、服务消费者(dubbo-ano-consumer)</a><br/>
|
||||
<a href="#1消费方的配置">1.消费方的配置</a><br/>
|
||||
<a href="#2使用注解@Reference引用远程服务">2.使用注解@Reference引用远程服务</a><br/>
|
||||
<a href="#六项目构建的说明">六、项目构建的说明</a><br/>
|
||||
<a href="#七关于dubbo新版本管理控制台的安装说明">七、关于dubbo新版本管理控制台的安装说明</a><br/>
|
||||
## 正文<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,279 +1,293 @@
|
||||
# spring 整合 dubbo(xml配置方式)
|
||||
|
||||
## 一、 项目结构说明
|
||||
|
||||
1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo下构建三个子模块:
|
||||
|
||||
1. dubbo-common 是公共模块,用于存放公共的接口和bean,被dubbo-provider和dubbo-provider在pom.xml中引用;
|
||||
2. dubbo-provider 是服务的提供者,提供商品的查询服务;
|
||||
3. dubbo-provider 是服务的消费者,调用provider提供的查询服务。
|
||||
|
||||
1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
|
||||
|
||||
<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
|
||||
# spring 整合 dubbo(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一-项目结构说明">一、 项目结构说明</a><br/>
|
||||
<a href="#二项目依赖">二、项目依赖</a><br/>
|
||||
<a href="#三公共模块dubbo-common">三、公共模块(dubbo-common)</a><br/>
|
||||
<a href="#四-服务提供者dubbo-provider">四、 服务提供者(dubbo-provider)</a><br/>
|
||||
<a href="#41--productService是服务的提供者-商品数据用模拟数据展示">4.1 productService是服务的提供者( 商品数据用模拟数据展示)</a><br/>
|
||||
<a href="#42-在dubboxml暴露服务">4.2 在dubbo.xml暴露服务</a><br/>
|
||||
<a href="#五服务消费者dubbo-consumer">五、服务消费者(dubbo-consumer)</a><br/>
|
||||
<a href="#1在dubboxml调用远程的服务">1.在dubbo.xml调用远程的服务</a><br/>
|
||||
<a href="#2消费服务">2.消费服务</a><br/>
|
||||
<a href="#六项目构建的说明">六、项目构建的说明</a><br/>
|
||||
<a href="#七关于dubbo新版本管理控制台的安装说明">七、关于dubbo新版本管理控制台的安装说明</a><br/>
|
||||
## 正文<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,287 +1,297 @@
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置类为com.heibaiying.config下EmailConfig.java;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为SendEmail。
|
||||
# spring 邮件发送(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-email">二、spring email</a><br/>
|
||||
<a href="#21-邮件发送配置">2.1 邮件发送配置</a><br/>
|
||||
<a href="#22-新建邮件发送基本类">2.2 新建邮件发送基本类</a><br/>
|
||||
<a href="#23-邮件发送的测试">2.3 邮件发送的测试</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
<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,298 +1,308 @@
|
||||
# spring 邮件发送(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 邮件发送配置文件为springApplication.xml;
|
||||
2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
|
||||
3. 项目以单元测试的方法进行测试,测试类为SendEmail。
|
||||
# spring 邮件发送(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-email">二、spring email</a><br/>
|
||||
<a href="#21-邮件发送配置">2.1 邮件发送配置</a><br/>
|
||||
<a href="#22-新建邮件发送基本类">2.2 新建邮件发送基本类</a><br/>
|
||||
<a href="#23-邮件发送的测试">2.3 邮件发送的测试</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
<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!");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
@ -1,5 +1,16 @@
|
||||
# spring 整合 jdbc template(注解方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#1说明">1.说明</a><br/>
|
||||
<a href="#12--项目依赖">1.2 项目依赖</a><br/>
|
||||
<a href="#二spring-整合-jdbc-template">二、spring 整合 jdbc template</a><br/>
|
||||
<a href="#21-在resources文件夹下新建数据库配置文件mysqlpropertiesoracleproperties及其映射类">2.1 在resources文件夹下新建数据库配置文件mysql.properties、oracle.properties及其映射类</a><br/>
|
||||
<a href="#22-新建数据库配置类DatabaseConfigjava">2.2 新建数据库配置类DatabaseConfig.java</a><br/>
|
||||
<a href="#23-新建查询接口及其实现类这里我查询的表是mysql和oracle中的字典表">2.3 新建查询接口及其实现类</a><br/>
|
||||
<a href="#24-新建测试类进行测试">2.4 新建测试类进行测试</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
## 1.说明
|
||||
|
||||
#### 1.1 项目目录结构
|
||||
|
@ -1,5 +1,17 @@
|
||||
# spring 整合 jdbc template(xml配置方式)
|
||||
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11--项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12--项目依赖">1.2 项目依赖</a><br/>
|
||||
<a href="#二-spring-整合-jdbc-template">二、 spring 整合 jdbc template</a><br/>
|
||||
<a href="#1在resources文件夹下新建数据库配置文件jdbcproperties">1、在resources文件夹下新建数据库配置文件jdbc.properties</a><br/>
|
||||
<a href="#2配置Jdbc数据源并定义事务管理器">2、配置Jdbc数据源并定义事务管理器</a><br/>
|
||||
<a href="#3新建查询接口及其实现类这里我查询的表是mysql和oracle中的字典表">3、新建查询接口及其实现类</a><br/>
|
||||
<a href="#4新建测试类进行测试">4.新建测试类进行测试</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 1.1 项目结构
|
||||
|
@ -1,150 +1,163 @@
|
||||
# spring 整合 mecached(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 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 整合 mecached(注解方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11--XMemcached客户端说明">1.1 XMemcached客户端说明</a><br/>
|
||||
<a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
|
||||
<a href="#13-依赖说明">1.3 依赖说明</a><br/>
|
||||
<a href="#二spring-整合-memcached">二、spring 整合 memcached</a><br/>
|
||||
<a href="#21-单机配置">2.1 单机配置</a><br/>
|
||||
<a href="#22-集群配置">2.2 集群配置</a><br/>
|
||||
<a href="#23-存储基本类型测试用例">2.3 存储基本类型测试用例</a><br/>
|
||||
<a href="#25-存储实体对象测试用例">2.5 存储实体对象测试用例</a><br/>
|
||||
<a href="#附memcached-基本命令">附:memcached 基本命令</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 二、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,191 +1,204 @@
|
||||
# spring 整合 mecached(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 XMemcached客户端说明
|
||||
|
||||
XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
|
||||
|
||||
### 1.2 项目结构说明
|
||||
|
||||
1. memcached的整合配置位于resources下的memcached文件夹下,其中集群配置用cluster开头。所有配置按照需要在springApplication.xml用import导入。
|
||||
2. 实体类Programmer.java用于测试memcached序列化与反序列化
|
||||
|
||||
<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 整合 mecached(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11--XMemcached客户端说明">1.1 XMemcached客户端说明</a><br/>
|
||||
<a href="#12-项目结构说明">1.2 项目结构说明</a><br/>
|
||||
<a href="#13-依赖说明">1.3 依赖说明</a><br/>
|
||||
<a href="#二spring-整合-memcached">二、spring 整合 memcached</a><br/>
|
||||
<a href="#21-单机配置">2.1 单机配置</a><br/>
|
||||
<a href="#22-集群配置">2.2 集群配置</a><br/>
|
||||
<a href="#23-存储基本类型测试用例">2.3 存储基本类型测试用例</a><br/>
|
||||
<a href="#25-存储实体对象测试用例">2.5 存储实体对象测试用例</a><br/>
|
||||
<a href="#附memcached-基本命令">附:memcached 基本命令</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
## 二、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,161 +1,171 @@
|
||||
# spring 整合 mongodb(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 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>
|
||||
# spring 整合 mongodb(注解方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-mongodb">二、spring mongodb</a><br/>
|
||||
<a href="#21-新建配置文件及其映射类">2.1 新建配置文件及其映射类</a><br/>
|
||||
<a href="#22-整合配置">2.2 整合配置</a><br/>
|
||||
<a href="#23-测试整合">2.3 测试整合</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
### 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,144 +1,154 @@
|
||||
# spring 整合 mongodb(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
配置文件位于resources下,项目以单元测试的方式进行测试。
|
||||
|
||||
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/spring-mongodb.png"/> </div>
|
||||
# spring 整合 mongodb(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-mongodb">二、spring mongodb</a><br/>
|
||||
<a href="#21-新建配置文件">2.1 新建配置文件</a><br/>
|
||||
<a href="#22-整合配置">2.2 整合配置</a><br/>
|
||||
<a href="#23-测试整合">2.3 测试整合</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
### 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,262 +1,274 @@
|
||||
# spring 整合 mybatis(注解方式)
|
||||
# spring 整合 mybatis(注解方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目依赖">1.2 项目依赖</a><br/>
|
||||
<a href="#二spring-整合-mybatis">二、spring 整合 mybatis</a><br/>
|
||||
<a href="#21--在resources文件夹下新建数据库配置文件jdbcproperties及其映射类">2.1 在resources文件夹下新建数据库配置文件jdbc.properties及其映射类</a><br/>
|
||||
<a href="#22--配置数据源和mybatis会话工厂定义事务管理器">2.2 配置数据源和mybatis会话工厂、定义事务管理器</a><br/>
|
||||
<a href="#23-新建mybtais配置文件按照需求配置额外参数-更多settings配置项可以参考[官方文档]http//wwwmybatisorg/mybatis-3/zh/configurationhtml">2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)</a><br/>
|
||||
<a href="#24-新建查询接口及其对应的mapper文件">2.4 新建查询接口及其对应的mapper文件</a><br/>
|
||||
<a href="#25-新建测试类进行测试">2.5 新建测试类进行测试</a><br/>
|
||||
## 正文<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,236 +1,248 @@
|
||||
# spring 整合 mybatis(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
#### 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>
|
||||
# spring 整合 mybatis(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构">1.1 项目结构</a><br/>
|
||||
<a href="#12-项目依赖">1.2 项目依赖</a><br/>
|
||||
<a href="#二spring-整合-mybatis">二、spring 整合 mybatis</a><br/>
|
||||
<a href="#21--在resources文件夹下新建数据库配置文件jdbcproperties">2.1 在resources文件夹下新建数据库配置文件jdbc.properties</a><br/>
|
||||
<a href="#22--配置数据源和mybatis会话工厂定义事务管理器">2.2 配置数据源和mybatis会话工厂、定义事务管理器</a><br/>
|
||||
<a href="#23-新建mybtais配置文件按照需求配置额外参数-更多settings配置项可以参考[官方文档]http//wwwmybatisorg/mybatis-3/zh/configurationhtml">2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)</a><br/>
|
||||
<a href="#24-新建查询接口及其对应的mapper文件">2.4 新建查询接口及其对应的mapper文件</a><br/>
|
||||
<a href="#25-新建测试类进行测试">2.5 新建测试类进行测试</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
<!--定义事务管理器-->
|
||||
<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,403 +1,419 @@
|
||||
# spring 整合 rabbitmq(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<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);
|
||||
}
|
||||
}
|
||||
# spring 整合 rabbitmq(注解方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-rabbit-基本配置">二、spring rabbit 基本配置</a><br/>
|
||||
<a href="#21-基本配置属性及其映射类">2.1 基本配置属性及其映射类</a><br/>
|
||||
<a href="#22-创建连接工厂管理器">2.2 创建连接工厂、管理器</a><br/>
|
||||
<a href="#三简单消费的发送">三、简单消费的发送</a><br/>
|
||||
<a href="#31-声明交换机队列绑定关系和消费者监听器">3.1 声明交换机、队列、绑定关系和消费者监听器</a><br/>
|
||||
<a href="#32-测试简单消息的发送">3.2 测试简单消息的发送</a><br/>
|
||||
<a href="#四传输对象">四、传输对象</a><br/>
|
||||
<a href="#41-创建消息的委托处理器">4.1 创建消息的委托处理器</a><br/>
|
||||
<a href="#42-声明交换机队列绑定关系和消费者监听器">4.2 声明交换机、队列、绑定关系和消费者监听器</a><br/>
|
||||
<a href="#43-测试对象消息的发送">4.3 测试对象消息的发送</a><br/>
|
||||
## 正文<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,381 +1,395 @@
|
||||
# spring 整合 rabbitmq(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 1.1 项目结构说明
|
||||
|
||||
1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
|
||||
|
||||
2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
|
||||
|
||||
3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
|
||||
|
||||
注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
|
||||
|
||||
|
||||
|
||||
<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);
|
||||
}
|
||||
}
|
||||
# spring 整合 rabbitmq(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-rabbit-基本配置">二、spring rabbit 基本配置</a><br/>
|
||||
<a href="#三简单消费的发送">三、简单消费的发送</a><br/>
|
||||
<a href="#31-声明交换机队列绑定关系和消费者监听器">3.1 声明交换机、队列、绑定关系和消费者监听器</a><br/>
|
||||
<a href="#32-测试简单消息的发送">3.2 测试简单消息的发送</a><br/>
|
||||
<a href="#四传输对象">四、传输对象</a><br/>
|
||||
<a href="#41-创建消息的委托处理器">4.1 创建消息的委托处理器</a><br/>
|
||||
<a href="#42-声明交换机队列绑定关系和消费者监听器">4.2 声明交换机、队列、绑定关系和消费者监听器</a><br/>
|
||||
<a href="#43-测试对象消息的发送">4.3 测试对象消息的发送</a><br/>
|
||||
## 正文<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);
|
||||
}
|
||||
}
|
||||
```
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,192 +1,201 @@
|
||||
# spring 定时任务(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 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>
|
||||
# spring 定时任务(注解方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-scheduling">二、spring scheduling</a><br/>
|
||||
<a href="#21-创建定时任务">2.1 创建定时任务</a><br/>
|
||||
<a href="#22-配置定时任务">2.2 配置定时任务</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
### 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,183 +1,192 @@
|
||||
# spring 定时任务(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 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>
|
||||
# spring 定时任务(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-scheduling">二、spring scheduling</a><br/>
|
||||
<a href="#21-创建定时任务">2.1 创建定时任务</a><br/>
|
||||
<a href="#22-配置定时任务">2.2 配置定时任务</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
### 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,203 +1,215 @@
|
||||
# spring websocket(注解方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 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>
|
||||
# spring websocket(注解方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-websocket">二、spring websocket</a><br/>
|
||||
<a href="#21-创建消息处理类继承自TextWebSocketHandler">2.1 创建消息处理类,继承自TextWebSocketHandler</a><br/>
|
||||
<a href="#22-创建websocket-握手拦截器如果没有权限拦截等需求这一步不是必须的">2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)</a><br/>
|
||||
<a href="#23-创建websocket的配置类">2.3 创建websocket的配置类</a><br/>
|
||||
<a href="#24-前端-websocket-的实现">2.4 前端 websocket 的实现</a><br/>
|
||||
<a href="#25-简单登录的实现">2.5 简单登录的实现</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
### 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,233 +1,245 @@
|
||||
# spring websocket(xml配置方式)
|
||||
|
||||
## 一、说明
|
||||
|
||||
### 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>
|
||||
# spring websocket(xml配置方式)
|
||||
## 目录<br/>
|
||||
<a href="#一说明">一、说明</a><br/>
|
||||
<a href="#11-项目结构说明">1.1 项目结构说明</a><br/>
|
||||
<a href="#12-依赖说明">1.2 依赖说明</a><br/>
|
||||
<a href="#二spring-websocket">二、spring websocket</a><br/>
|
||||
<a href="#21-创建消息处理类继承自TextWebSocketHandler">2.1 创建消息处理类,继承自TextWebSocketHandler</a><br/>
|
||||
<a href="#22-创建websocket-握手拦截器如果没有权限拦截等需求这一步不是必须的">2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)</a><br/>
|
||||
<a href="#23-配置websocket">2.3 配置websocket</a><br/>
|
||||
<a href="#24-前端-websocket-的实现">2.4 前端 websocket 的实现</a><br/>
|
||||
<a href="#25-简单登录的实现">2.5 简单登录的实现</a><br/>
|
||||
## 正文<br/>
|
||||
|
||||
|
||||
|
||||
### 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";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user