From 4fc9b96f2abe68c024c8d573590b51e05cc18a99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=BD=97=E7=A5=A5?= <1366971433@qq.com>
Date: Fri, 1 Feb 2019 09:50:53 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0README.md=E6=96=87=E7=AB=A0?=
=?UTF-8?q?=E5=AF=BC=E8=88=AA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
spring-boot/spring-boot-swagger2/README.md | 2 +
spring-boot/spring-boot-swagger2/pom.xml | 61 +
.../SpringBootSwagger2Application.java | 14 +
.../heibaiying/springboot/bean/Product.java | 25 +
.../springboot/config/SwaggerConfig.java | 67 +
.../controller/ProductController.java | 84 +
.../src/main/resources/application.properties | 1 +
.../SpringBootSwagger2ApplicationTests.java | 17 +
spring/spring-aop-annotation/README.md | 483 +++--
spring/spring-aop/README.md | 460 ++--
.../spring-druid-mybatis-annotation/README.md | 17 +-
spring/spring-druid-mybatis/README.md | 13 +-
spring/spring-dubbo-annotation/README.md | 666 +++---
spring/spring-dubbo/README.md | 570 ++---
spring/spring-email-annotation/README.md | 580 ++---
spring/spring-email/README.md | 602 +++---
spring/spring-jdbc-annotation/README.md | 11 +
spring/spring-jdbc/README.md | 12 +
spring/spring-memcached-annotation/README.md | 307 +--
spring/spring-memcached/README.md | 389 ++--
spring/spring-mongodb-annotation/README.md | 328 +--
spring/spring-mongodb/README.md | 294 +--
spring/spring-mybatis-annotation/README.md | 532 ++---
spring/spring-mybatis/README.md | 480 +++--
spring/spring-rabbitmq-annotation/README.md | 820 +++----
spring/spring-rabbitmq/README.md | 774 +++----
spring/spring-redis-annotation/README.md | 1098 +++++-----
spring/spring-redis/README.md | 1144 +++++-----
spring/spring-scheduling-annotation/README.md | 389 ++--
spring/spring-scheduling/README.md | 371 ++--
spring/spring-websocket-annotation/README.md | 414 ++--
spring/spring-websocket/README.md | 474 +++--
spring/springmvc-base-annotation/README.md | 1890 +++++++++--------
spring/springmvc-base/README.md | 1872 ++++++++--------
34 files changed, 7953 insertions(+), 7308 deletions(-)
create mode 100644 spring-boot/spring-boot-swagger2/README.md
create mode 100644 spring-boot/spring-boot-swagger2/pom.xml
create mode 100644 spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/SpringBootSwagger2Application.java
create mode 100644 spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/bean/Product.java
create mode 100644 spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/config/SwaggerConfig.java
create mode 100644 spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/controller/ProductController.java
create mode 100644 spring-boot/spring-boot-swagger2/src/main/resources/application.properties
create mode 100644 spring-boot/spring-boot-swagger2/src/test/java/com/heibaiying/springboot/SpringBootSwagger2ApplicationTests.java
diff --git a/spring-boot/spring-boot-swagger2/README.md b/spring-boot/spring-boot-swagger2/README.md
new file mode 100644
index 0000000..0740998
--- /dev/null
+++ b/spring-boot/spring-boot-swagger2/README.md
@@ -0,0 +1,2 @@
+@Profile({"dev","test"})
+@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
\ No newline at end of file
diff --git a/spring-boot/spring-boot-swagger2/pom.xml b/spring-boot/spring-boot-swagger2/pom.xml
new file mode 100644
index 0000000..fb1e14f
--- /dev/null
+++ b/spring-boot/spring-boot-swagger2/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ com.heibaiying.springboot
+ spring-boot-swagger2
+ 0.0.1-SNAPSHOT
+ spring-boot-swagger2
+ swagger2 project for Spring Boot
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ io.springfox
+ springfox-swagger2
+ 2.9.2
+
+
+
+ io.springfox
+ springfox-swagger-ui
+ 2.9.2
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/SpringBootSwagger2Application.java b/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/SpringBootSwagger2Application.java
new file mode 100644
index 0000000..c111945
--- /dev/null
+++ b/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/SpringBootSwagger2Application.java
@@ -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);
+ }
+
+}
+
diff --git a/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/bean/Product.java b/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/bean/Product.java
new file mode 100644
index 0000000..954b647
--- /dev/null
+++ b/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/bean/Product.java
@@ -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;
+
+}
diff --git a/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/config/SwaggerConfig.java b/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/config/SwaggerConfig.java
new file mode 100644
index 0000000..0c2675d
--- /dev/null
+++ b/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/config/SwaggerConfig.java
@@ -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 doFilteringRules() {
+ return not(
+ regex("/ignore/*")
+ );
+ }
+}
diff --git a/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/controller/ProductController.java b/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/controller/ProductController.java
new file mode 100644
index 0000000..eb35560
--- /dev/null
+++ b/spring-boot/spring-boot-swagger2/src/main/java/com/heibaiying/springboot/controller/ProductController.java
@@ -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 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 saveProduct(@RequestBody Product product) {
+ System.out.println(product);
+ return ResponseEntity.ok().build();
+ }
+
+
+ /***
+ * 在配置类中指明了该接口不被扫描到,可以在配置类中使用正则指定某一类符合规则的接口不被扫描到
+ */
+ @ApiOperation(notes = "该接口会被忽略", value = "产品保存接口")
+ @PostMapping(value = "/ignore")
+ public ResponseEntity ignore() {
+ return ResponseEntity.ok().build();
+ }
+
+ /**
+ * 不加上任何swagger相关的注解也会被扫描到 如果不希望被扫描到,需要用 @ApiIgnore 修饰
+ */
+ @PostMapping(value = "/normal")
+ public ResponseEntity normal() {
+ return ResponseEntity.ok().build();
+ }
+
+ @ApiIgnore
+ @PostMapping(value = "/apiIgnore")
+ public ResponseEntity apiIgnore() {
+ return ResponseEntity.ok().build();
+ }
+}
\ No newline at end of file
diff --git a/spring-boot/spring-boot-swagger2/src/main/resources/application.properties b/spring-boot/spring-boot-swagger2/src/main/resources/application.properties
new file mode 100644
index 0000000..2c3050a
--- /dev/null
+++ b/spring-boot/spring-boot-swagger2/src/main/resources/application.properties
@@ -0,0 +1 @@
+swagger.enable = true
\ No newline at end of file
diff --git a/spring-boot/spring-boot-swagger2/src/test/java/com/heibaiying/springboot/SpringBootSwagger2ApplicationTests.java b/spring-boot/spring-boot-swagger2/src/test/java/com/heibaiying/springboot/SpringBootSwagger2ApplicationTests.java
new file mode 100644
index 0000000..3b3e771
--- /dev/null
+++ b/spring-boot/spring-boot-swagger2/src/test/java/com/heibaiying/springboot/SpringBootSwagger2ApplicationTests.java
@@ -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() {
+ }
+
+}
+
diff --git a/spring/spring-aop-annotation/README.md b/spring/spring-aop-annotation/README.md
index 26df9b0..391c0b0 100644
--- a/spring/spring-aop-annotation/README.md
+++ b/spring/spring-aop-annotation/README.md
@@ -1,236 +1,249 @@
-# spring AOP(注解方式)
-
-## 一、说明
-
-### 1.1 项目结构说明
-
-1. 切面配置位于com.heibaiying.config下AopConfig.java文件;
-2. 自定义切面位于advice下,其中CustomAdvice是标准的自定义切面,FirstAdvice和SecondAdvice用于测试多切面共同作用于同一个被切入点时的执行顺序;
-3. OrderService是待切入方法。
-
-
-
-
-
-### 1.2 依赖说明
-
-除了spring的基本依赖外,需要导入aop依赖包
-
-```xml
-
-
- org.springframework
- spring-aop
- ${spring-base-version}
-
-```
-
-
-
-## 二、spring aop
-
-#### 2.1 创建待切入接口及其实现类
-
-```java
-public interface OrderService {
-
- Order queryOrder(Long id);
-
- Order createOrder(Long id, String productName);
-}
-```
-
-```java
-public class OrderServiceImpl implements OrderService {
-
- public Order queryOrder(Long id) {
- return new Order(id, "product", new Date());
- }
-
- public Order createOrder(Long id, String productName) {
- // 模拟抛出异常
- // int j = 1 / 0;
- return new Order(id, "new Product", new Date());
- }
-}
-
-```
-
-#### 2.2 创建自定义切面类
-
-注:@Pointcut的值可以是多个切面表达式的组合。
-
-```java
-/**
- * @author : heibaiying
- * @description : 自定义切面
- */
-@Aspect
-@Component //除了加上@Aspect外 还需要声明为spring的组件 @Aspect只是一个切面声明
-public class CustomAdvice {
-
-
- /**
- * 使用 || , or 表示或
- * 使用 && , and 表示与
- * ! 表示非
- */
- @Pointcut("execution(* com.heibaiying.service.OrderService.*(..)) && !execution(* com.heibaiying.service.OrderService.deleteOrder(..))")
- private void pointCut() {
-
- }
-
- @Before("pointCut()")
- public void before(JoinPoint joinPoint) {
- //获取节点名称
- String name = joinPoint.getSignature().getName();
- Object[] args = joinPoint.getArgs();
- System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
- }
-
- // returning 参数用于指定返回结果与哪一个参数绑定
- @AfterReturning(pointcut = "pointCut()", returning = "result")
- public void afterReturning(JoinPoint joinPoint, Object result) {
- System.out.println("后置返回通知结果" + result);
- }
-
- @Around("pointCut()")
- public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
- System.out.println("环绕通知-前");
- //调用目标方法
- Object proceed = joinPoint.proceed();
- System.out.println("环绕通知-后");
- return proceed;
- }
-
- // throwing 参数用于指定抛出的异常与哪一个参数绑定
- @AfterThrowing(pointcut = "pointCut()", throwing = "exception")
- public void afterThrowing(JoinPoint joinPoint, Exception exception) {
- System.err.println("后置异常通知:" + exception);
- }
-
-
- @After("pointCut()")
- public void after(JoinPoint joinPoint) {
- System.out.println("后置通知");
- }
-}
-
-```
-
-#### 2.3 配置切面
-
-```java
-/**
- * @author : heibaiying
- * @description : 开启切面配置
- */
-@Configuration
-@ComponentScan("com.heibaiying.*")
-@EnableAspectJAutoProxy // 开启@Aspect注解支持 等价于
-public class AopConfig {
-}
-```
-
-#### 2.4 测试切面
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = AopConfig.class)
-public class AopTest {
-
-
- @Autowired
- private OrderService orderService;
-
- @Test
- public void saveAndQuery() {
- orderService.createOrder(1283929319L, "手机");
- orderService.queryOrder(4891894129L);
- }
-
- /**
- * 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
- * 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行
- */
- @Test
- public void delete() {
- orderService.deleteOrder(12793179L);
- }
-}
-```
-
-#### 2.5 切面执行顺序
-
-- 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
-
-- 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行,类似于同心圆原理。
-
-
-
-
-
-## 附: 关于切面表达式的说明
-
-切面表达式遵循以下格式:
-
-```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(注解方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring aop
+ 2.1 创建待切入接口及其实现类
+ 2.2 创建自定义切面类
+ 2.3 配置切面
+ 2.4 测试切面
+ 2.5 切面执行顺序
+附: 关于切面表达式的说明
+## 正文
+
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 切面配置位于com.heibaiying.config下AopConfig.java文件;
+2. 自定义切面位于advice下,其中CustomAdvice是标准的自定义切面,FirstAdvice和SecondAdvice用于测试多切面共同作用于同一个被切入点时的执行顺序;
+3. OrderService是待切入方法。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入aop依赖包
+
+```xml
+
+
+ org.springframework
+ spring-aop
+ ${spring-base-version}
+
+```
+
+
+
+## 二、spring aop
+
+#### 2.1 创建待切入接口及其实现类
+
+```java
+public interface OrderService {
+
+ Order queryOrder(Long id);
+
+ Order createOrder(Long id, String productName);
+}
+```
+
+```java
+public class OrderServiceImpl implements OrderService {
+
+ public Order queryOrder(Long id) {
+ return new Order(id, "product", new Date());
+ }
+
+ public Order createOrder(Long id, String productName) {
+ // 模拟抛出异常
+ // int j = 1 / 0;
+ return new Order(id, "new Product", new Date());
+ }
+}
+
+```
+
+#### 2.2 创建自定义切面类
+
+注:@Pointcut的值可以是多个切面表达式的组合。
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 自定义切面
+ */
+@Aspect
+@Component //除了加上@Aspect外 还需要声明为spring的组件 @Aspect只是一个切面声明
+public class CustomAdvice {
+
+
+ /**
+ * 使用 || , or 表示或
+ * 使用 && , and 表示与
+ * ! 表示非
+ */
+ @Pointcut("execution(* com.heibaiying.service.OrderService.*(..)) && !execution(* com.heibaiying.service.OrderService.deleteOrder(..))")
+ private void pointCut() {
+
+ }
+
+ @Before("pointCut()")
+ public void before(JoinPoint joinPoint) {
+ //获取节点名称
+ String name = joinPoint.getSignature().getName();
+ Object[] args = joinPoint.getArgs();
+ System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
+ }
+
+ // returning 参数用于指定返回结果与哪一个参数绑定
+ @AfterReturning(pointcut = "pointCut()", returning = "result")
+ public void afterReturning(JoinPoint joinPoint, Object result) {
+ System.out.println("后置返回通知结果" + result);
+ }
+
+ @Around("pointCut()")
+ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+ System.out.println("环绕通知-前");
+ //调用目标方法
+ Object proceed = joinPoint.proceed();
+ System.out.println("环绕通知-后");
+ return proceed;
+ }
+
+ // throwing 参数用于指定抛出的异常与哪一个参数绑定
+ @AfterThrowing(pointcut = "pointCut()", throwing = "exception")
+ public void afterThrowing(JoinPoint joinPoint, Exception exception) {
+ System.err.println("后置异常通知:" + exception);
+ }
+
+
+ @After("pointCut()")
+ public void after(JoinPoint joinPoint) {
+ System.out.println("后置通知");
+ }
+}
+
+```
+
+#### 2.3 配置切面
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 开启切面配置
+ */
+@Configuration
+@ComponentScan("com.heibaiying.*")
+@EnableAspectJAutoProxy // 开启@Aspect注解支持 等价于
+public class AopConfig {
+}
+```
+
+#### 2.4 测试切面
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = AopConfig.class)
+public class AopTest {
+
+
+ @Autowired
+ private OrderService orderService;
+
+ @Test
+ public void saveAndQuery() {
+ orderService.createOrder(1283929319L, "手机");
+ orderService.queryOrder(4891894129L);
+ }
+
+ /**
+ * 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
+ * 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行
+ */
+ @Test
+ public void delete() {
+ orderService.deleteOrder(12793179L);
+ }
+}
+```
+
+#### 2.5 切面执行顺序
+
+- 多个切面作用于同一个切入点时,可以用@Order指定切面的执行顺序
+
+- 优先级高的切面在切入方法前执行的通知(before)会优先执行,但是位于方法后执行的通知(after,afterReturning)反而会延后执行,类似于同心圆原理。
+
+
+
+
+
+## 附: 关于切面表达式的说明
+
+切面表达式遵循以下格式:
+
+```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)
\ No newline at end of file
diff --git a/spring/spring-aop/README.md b/spring/spring-aop/README.md
index e5acbdc..d5738ec 100644
--- a/spring/spring-aop/README.md
+++ b/spring/spring-aop/README.md
@@ -1,227 +1,239 @@
-# spring AOP(xml配置方式)
-
-## 一、说明
-
-### 1.1 项目结构说明
-
-切面配置位于resources下的aop.xml文件,其中CustomAdvice是自定义切面类,OrderService是待切入的方法。
-
-
+# spring AOP(xml配置方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring aop
+ 2.1 创建待切入接口及其实现类
+ 2.2 创建自定义切面类
+ 2.3 配置切面
+ 2.4 测试切面
+附: 关于切面表达式的说明
+## 正文
-
-### 1.2 依赖说明
-
-除了spring的基本依赖外,需要导入aop依赖包
-
-```xml
-
-
- org.springframework
- spring-aop
- ${spring-base-version}
-
-```
-
-
-
-## 二、spring aop
-
-#### 2.1 创建待切入接口及其实现类
-
-```java
-public interface OrderService {
-
- Order queryOrder(Long id);
-
- Order createOrder(Long id, String productName);
-}
-```
-
-```java
-public class OrderServiceImpl implements OrderService {
-
- public Order queryOrder(Long id) {
- return new Order(id, "product", new Date());
- }
-
- public Order createOrder(Long id, String productName) {
- // 模拟抛出异常
- // int j = 1 / 0;
- return new Order(id, "new Product", new Date());
- }
-}
-
-```
-
-#### 2.2 创建自定义切面类
-
-```java
-/**
- * @author : heibaiying
- * @description : 自定义切面
- */
-public class CustomAdvice {
-
-
- //前置通知
- public void before(JoinPoint joinPoint) {
- //获取节点名称
- String name = joinPoint.getSignature().getName();
- Object[] args = joinPoint.getArgs();
- System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
- }
-
- //后置通知(抛出异常后不会被执行)
- public void afterReturning(JoinPoint joinPoint, Object result) {
- System.out.println("后置返回通知结果" + result);
- }
-
- //环绕通知
- public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
- System.out.println("环绕通知-前");
- //调用目标方法
- Object proceed = joinPoint.proceed();
- System.out.println("环绕通知-后");
- return proceed;
- }
-
- //异常通知
- public void afterException(JoinPoint joinPoint, Exception exception) {
- System.err.println("后置异常通知:" + exception);
- }
-
- ;
-
- // 后置通知 总会执行 但是不能访问到返回值
- public void after(JoinPoint joinPoint) {
- System.out.println("后置通知");
- }
-}
-
-```
-
-#### 2.3 配置切面
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.4 测试切面
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration("classpath:aop.xml")
-public class AopTest {
-
- @Autowired
- private OrderService orderService;
-
- @Test
- public void save() {
- orderService.createOrder(1283929319L, "手机");
- orderService.queryOrder(4891894129L);
- }
-}
-```
-
-
-
-## 附: 关于切面表达式的说明
-
-切面表达式遵循以下格式:
-
-```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是待切入的方法。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入aop依赖包
+
+```xml
+
+
+ org.springframework
+ spring-aop
+ ${spring-base-version}
+
+```
+
+
+
+## 二、spring aop
+
+#### 2.1 创建待切入接口及其实现类
+
+```java
+public interface OrderService {
+
+ Order queryOrder(Long id);
+
+ Order createOrder(Long id, String productName);
+}
+```
+
+```java
+public class OrderServiceImpl implements OrderService {
+
+ public Order queryOrder(Long id) {
+ return new Order(id, "product", new Date());
+ }
+
+ public Order createOrder(Long id, String productName) {
+ // 模拟抛出异常
+ // int j = 1 / 0;
+ return new Order(id, "new Product", new Date());
+ }
+}
+
+```
+
+#### 2.2 创建自定义切面类
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 自定义切面
+ */
+public class CustomAdvice {
+
+
+ //前置通知
+ public void before(JoinPoint joinPoint) {
+ //获取节点名称
+ String name = joinPoint.getSignature().getName();
+ Object[] args = joinPoint.getArgs();
+ System.out.println(name + "方法调用前:获取调用参数" + Arrays.toString(args));
+ }
+
+ //后置通知(抛出异常后不会被执行)
+ public void afterReturning(JoinPoint joinPoint, Object result) {
+ System.out.println("后置返回通知结果" + result);
+ }
+
+ //环绕通知
+ public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+ System.out.println("环绕通知-前");
+ //调用目标方法
+ Object proceed = joinPoint.proceed();
+ System.out.println("环绕通知-后");
+ return proceed;
+ }
+
+ //异常通知
+ public void afterException(JoinPoint joinPoint, Exception exception) {
+ System.err.println("后置异常通知:" + exception);
+ }
+
+ ;
+
+ // 后置通知 总会执行 但是不能访问到返回值
+ public void after(JoinPoint joinPoint) {
+ System.out.println("后置通知");
+ }
+}
+
+```
+
+#### 2.3 配置切面
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.4 测试切面
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration("classpath:aop.xml")
+public class AopTest {
+
+ @Autowired
+ private OrderService orderService;
+
+ @Test
+ public void save() {
+ orderService.createOrder(1283929319L, "手机");
+ orderService.queryOrder(4891894129L);
+ }
+}
+```
+
+
+
+## 附: 关于切面表达式的说明
+
+切面表达式遵循以下格式:
+
+```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)
\ No newline at end of file
diff --git a/spring/spring-druid-mybatis-annotation/README.md b/spring/spring-druid-mybatis-annotation/README.md
index bbaf7a8..eeb52b4 100644
--- a/spring/spring-druid-mybatis-annotation/README.md
+++ b/spring/spring-druid-mybatis-annotation/README.md
@@ -1,5 +1,18 @@
# spring +druid+ mybatis(注解方式)
+## 目录
+ 1、创建maven工程,除了Spring基本依赖外,还需要导入mybatis和druid的相关依赖
+ 2、新建 DispatcherServletInitializer.java继承自AbstractAnnotationConfigDispatcherServletInitializer,等价于我们在web.xml中配置的前端控制器
+ 3、基于servlet 3.0的支持,可以采用注解的方式注册druid的servlet和filter
+ 4、在resources文件夹下新建数据库配置文件mysql.properties、oracle.properties
+ 5、在新建数据库配置映射类DataSourceConfig.java
+ 6、新建ServletConfig.java,进行数据库相关配置
+ 7、新建mybtais 配置文件,按需要进行额外参数配置, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
+ 8、新建查询接口及其对应的mapper文件
+ 9、新建测试controller进行测试
+ 10、druid 监控页面访问地址http://localhost:8080/druid/index.html
+## 正文
+
### 项目目录结构
@@ -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 {
```
-#### 9.新建测试controller进行测试
+#### 9、新建测试controller进行测试
```java
@RestController
diff --git a/spring/spring-druid-mybatis/README.md b/spring/spring-druid-mybatis/README.md
index 0d60525..da81a63 100644
--- a/spring/spring-druid-mybatis/README.md
+++ b/spring/spring-druid-mybatis/README.md
@@ -1,5 +1,16 @@
# spring +druid+ mybatis(xml配置方式)
+## 目录
+ 1、创建maven工程,除了Spring基本依赖外,还需要导入mybatis和druid的相关依赖
+ 2、在web.xml 中配置spring前端控制器、druid监控台servlet和filter
+ 3、在resources文件夹下新建数据库配置文件jdbc.properties
+ 4、在resources文件夹下创建springApplication.xml 配置文件和druid.xml配置文件
+ 5、新建mybtais 配置文件,按需要进行额外配置,更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
+ 6、新建查询接口及其对应的mapper文件
+ 7、新建测试controller进行测试
+ 8、druid 监控页面访问地址http://localhost:8080/druid/index.html
+## 正文
+
### 项目目录结构
@@ -308,7 +319,7 @@ public interface OracleDao {
```
-#### 7.新建测试controller进行测试
+#### 7、新建测试controller进行测试
```java
package com.heibaiying.controller;
diff --git a/spring/spring-dubbo-annotation/README.md b/spring/spring-dubbo-annotation/README.md
index e86055a..f1b0309 100644
--- a/spring/spring-dubbo-annotation/README.md
+++ b/spring/spring-dubbo-annotation/README.md
@@ -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)
-
-
-
-
-
-## 二、项目依赖
-
-**在父工程的项目中统一导入依赖dubbo依赖的的jar包**
-
-这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
-
-```xml
-
-com.alibaba
-dubbo
-2.6.2
-
-
- org.apache.curator
- curator-framework
- 4.0.0
-
-
- org.apache.zookeeper
- zookeeper
-
-
-
-
- org.apache.zookeeper
- zookeeper
- 3.4.13
-
-```
-
-
-
-## 三、公共模块(dubbo-ano-common)
-
-- api 下为公共的调用接口;
-- bean 下为公共的实体类。
-
-
-
-## 四、 服务提供者(dubbo-ano-provider)
-
-
-
-#### 4.1 提供方配置
-
-```java
-@Configuration
-public class DubboConfiguration {
-
- /**
- * 提供方应用信息,用于计算依赖关系
- */
- @Bean
- public ApplicationConfig applicationConfig() {
- ApplicationConfig applicationConfig = new ApplicationConfig();
- applicationConfig.setName("dubbo-ano-provider");
- return applicationConfig;
- }
-
- /**
- * 使用zookeeper注册中心暴露服务地址
- */
- @Bean
- public RegistryConfig registryConfig() {
- RegistryConfig registryConfig = new RegistryConfig();
- registryConfig.setAddress("zookeeper://127.0.0.1:2181");
- registryConfig.setClient("curator");
- return registryConfig;
- }
-
- /**
- * 用dubbo协议在20880端口暴露服务
- */
- @Bean
- public ProtocolConfig protocolConfig() {
- ProtocolConfig protocolConfig = new ProtocolConfig();
- protocolConfig.setName("dubbo");
- protocolConfig.setPort(20880);
- return protocolConfig;
- }
-}
-```
-
-#### 4.2 使用注解@Service暴露服务
-
-需要注意的是这里的@Service注解不是spring的注解,而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
-
-```java
-package com.heibaiying.service;
-
-import com.alibaba.dubbo.config.annotation.Service;
-import com.heibaiying.api.IProductService;
-import com.heibaiying.bean.Product;
-
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * @author : heibaiying
- * @description : 产品提供接口实现类
- */
-@Service(timeout = 5000)
-public class ProductService implements IProductService {
-
- private static List productList = new ArrayList<>();
-
- static {
- for (int i = 0; i < 20; i++) {
- productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
- }
- }
-
- public Product queryProductById(int id) {
- for (Product product : productList) {
- if (product.getId() == id) {
- return product;
- }
- }
- return null;
- }
-
- public List queryAllProducts() {
- return productList;
- }
-}
-```
-
-
-
-## 五、服务消费者(dubbo-ano-consumer)
-
-
-
-#### 1.消费方的配置
-
-```java
-@Configuration
-public class DubboConfiguration {
-
- /**
- * 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
- */
- @Bean
- public ApplicationConfig applicationConfig() {
- ApplicationConfig applicationConfig = new ApplicationConfig();
- applicationConfig.setName("dubbo-ano-consumer");
- return applicationConfig;
- }
-
- /**
- * 设置调用服务超时时间
- * 关闭所有服务的启动时检查
- */
- @Bean
- public ConsumerConfig consumerConfig() {
- ConsumerConfig consumerConfig = new ConsumerConfig();
- consumerConfig.setTimeout(3000);
- consumerConfig.setCheck(false);
- return consumerConfig;
- }
-
- /**
- * 使用zookeeper注册中心暴露发现服务地址
- */
- @Bean
- public RegistryConfig registryConfig() {
- RegistryConfig registryConfig = new RegistryConfig();
- registryConfig.setAddress("zookeeper://127.0.0.1:2181");
- registryConfig.setClient("curator");
- return registryConfig;
- }
-
-}
-```
-
-#### 2.使用注解@Reference引用远程服务
-
-```java
-package com.heibaiying.controller;
-
-import com.alibaba.dubbo.config.annotation.Reference;
-import com.heibaiying.api.IProductService;
-import com.heibaiying.bean.Product;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-
-import java.util.List;
-
-@Controller
-@RequestMapping("sell")
-public class SellController {
-
- // dubbo远程引用注解
- @Reference
- private IProductService productService;
-
- @RequestMapping
- public String productList(Model model) {
- List products = productService.queryAllProducts();
- model.addAttribute("products", products);
- return "products";
- }
-
- @RequestMapping("product/{id}")
- public String productDetail(@PathVariable int id, Model model) {
- Product product = productService.queryProductById(id);
- model.addAttribute("product", product);
- return "product";
- }
-}
-
-```
-
-## 六、项目构建的说明
-
-因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
-
-```shell
-mvn install -Dmaven.test.skip = true
-```
-
-consumer中 pom.xml如下
-
-```xml
-
-
-
- spring-dubbo-annotation
- com.heibaiying
- 1.0-SNAPSHOT
-
- 4.0.0
-
- dubbo-ano-consumer
-
-
-
- com.heibaiying
- dubbo-ano-common
- 1.0-SNAPSHOT
- compile
-
-
-
-
-```
-
-provider中 pom.xml如下
-
-```xml
-
-
-
- spring-dubbo-annotation
- com.heibaiying
- 1.0-SNAPSHOT
-
- 4.0.0
-
- dubbo-ano-provider
-
-
-
- com.heibaiying
- dubbo-ano-common
- 1.0-SNAPSHOT
- compile
-
-
-
-
-```
-
-## 七、关于dubbo新版本管理控制台的安装说明
-
-安装:
-
-```sh
-git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
-cd /var/tmp/dubbo-ops
-mvn clean package
-```
-
-配置:
-
-```sh
-配置文件为:
-dubbo-admin-backend/src/main/resources/application.properties
-主要的配置有 默认的配置就是127.0.0.1:2181:
-dubbo.registry.address=zookeeper://127.0.0.1:2181
-```
-
-启动:
-
-```sh
-mvn --projects dubbo-admin-backend spring-boot:run
-```
-
-访问:
-
-```
-http://127.0.0.1:8080
+# spring 整合 dubbo(注解方式)
+
## 目录
+一、 项目结构说明
+二、项目依赖
+三、公共模块(dubbo-ano-common)
+四、 服务提供者(dubbo-ano-provider)
+ 4.1 提供方配置
+ 4.2 使用注解@Service暴露服务
+五、服务消费者(dubbo-ano-consumer)
+ 1.消费方的配置
+ 2.使用注解@Reference引用远程服务
+六、项目构建的说明
+七、关于dubbo新版本管理控制台的安装说明
+## 正文
+
+
+## 一、 项目结构说明
+
+1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo-annotation下构建三个子模块:
+
+1. dubbo-ano-common 是公共模块,用于存放公共的接口和bean,被dubbo-ano-provider和dubbo-ano-provider在pom.xml中引用;
+2. dubbo-ano-provider 是服务的提供者,提供商品的查询服务;
+3. dubbo-ano-provider 是服务的消费者,调用provider提供的查询服务。
+
+1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
+
+
+
+
+
+## 二、项目依赖
+
+**在父工程的项目中统一导入依赖dubbo依赖的的jar包**
+
+这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
+
+```xml
+
+com.alibaba
+dubbo
+2.6.2
+
+
+ org.apache.curator
+ curator-framework
+ 4.0.0
+
+
+ org.apache.zookeeper
+ zookeeper
+
+
+
+
+ org.apache.zookeeper
+ zookeeper
+ 3.4.13
+
+```
+
+
+
+## 三、公共模块(dubbo-ano-common)
+
+- api 下为公共的调用接口;
+- bean 下为公共的实体类。
+
+
+
+## 四、 服务提供者(dubbo-ano-provider)
+
+
+
+#### 4.1 提供方配置
+
+```java
+@Configuration
+public class DubboConfiguration {
+
+ /**
+ * 提供方应用信息,用于计算依赖关系
+ */
+ @Bean
+ public ApplicationConfig applicationConfig() {
+ ApplicationConfig applicationConfig = new ApplicationConfig();
+ applicationConfig.setName("dubbo-ano-provider");
+ return applicationConfig;
+ }
+
+ /**
+ * 使用zookeeper注册中心暴露服务地址
+ */
+ @Bean
+ public RegistryConfig registryConfig() {
+ RegistryConfig registryConfig = new RegistryConfig();
+ registryConfig.setAddress("zookeeper://127.0.0.1:2181");
+ registryConfig.setClient("curator");
+ return registryConfig;
+ }
+
+ /**
+ * 用dubbo协议在20880端口暴露服务
+ */
+ @Bean
+ public ProtocolConfig protocolConfig() {
+ ProtocolConfig protocolConfig = new ProtocolConfig();
+ protocolConfig.setName("dubbo");
+ protocolConfig.setPort(20880);
+ return protocolConfig;
+ }
+}
+```
+
+#### 4.2 使用注解@Service暴露服务
+
+需要注意的是这里的@Service注解不是spring的注解,而是dubbo的注解 com.alibaba.dubbo.config.annotation.Service
+
+```java
+package com.heibaiying.service;
+
+import com.alibaba.dubbo.config.annotation.Service;
+import com.heibaiying.api.IProductService;
+import com.heibaiying.bean.Product;
+
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author : heibaiying
+ * @description : 产品提供接口实现类
+ */
+@Service(timeout = 5000)
+public class ProductService implements IProductService {
+
+ private static List productList = new ArrayList<>();
+
+ static {
+ for (int i = 0; i < 20; i++) {
+ productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
+ }
+ }
+
+ public Product queryProductById(int id) {
+ for (Product product : productList) {
+ if (product.getId() == id) {
+ return product;
+ }
+ }
+ return null;
+ }
+
+ public List queryAllProducts() {
+ return productList;
+ }
+}
+```
+
+
+
+## 五、服务消费者(dubbo-ano-consumer)
+
+
+
+#### 1.消费方的配置
+
+```java
+@Configuration
+public class DubboConfiguration {
+
+ /**
+ * 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
+ */
+ @Bean
+ public ApplicationConfig applicationConfig() {
+ ApplicationConfig applicationConfig = new ApplicationConfig();
+ applicationConfig.setName("dubbo-ano-consumer");
+ return applicationConfig;
+ }
+
+ /**
+ * 设置调用服务超时时间
+ * 关闭所有服务的启动时检查
+ */
+ @Bean
+ public ConsumerConfig consumerConfig() {
+ ConsumerConfig consumerConfig = new ConsumerConfig();
+ consumerConfig.setTimeout(3000);
+ consumerConfig.setCheck(false);
+ return consumerConfig;
+ }
+
+ /**
+ * 使用zookeeper注册中心暴露发现服务地址
+ */
+ @Bean
+ public RegistryConfig registryConfig() {
+ RegistryConfig registryConfig = new RegistryConfig();
+ registryConfig.setAddress("zookeeper://127.0.0.1:2181");
+ registryConfig.setClient("curator");
+ return registryConfig;
+ }
+
+}
+```
+
+#### 2.使用注解@Reference引用远程服务
+
+```java
+package com.heibaiying.controller;
+
+import com.alibaba.dubbo.config.annotation.Reference;
+import com.heibaiying.api.IProductService;
+import com.heibaiying.bean.Product;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.util.List;
+
+@Controller
+@RequestMapping("sell")
+public class SellController {
+
+ // dubbo远程引用注解
+ @Reference
+ private IProductService productService;
+
+ @RequestMapping
+ public String productList(Model model) {
+ List products = productService.queryAllProducts();
+ model.addAttribute("products", products);
+ return "products";
+ }
+
+ @RequestMapping("product/{id}")
+ public String productDetail(@PathVariable int id, Model model) {
+ Product product = productService.queryProductById(id);
+ model.addAttribute("product", product);
+ return "product";
+ }
+}
+
+```
+
+## 六、项目构建的说明
+
+因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
+
+```shell
+mvn install -Dmaven.test.skip = true
+```
+
+consumer中 pom.xml如下
+
+```xml
+
+
+
+ spring-dubbo-annotation
+ com.heibaiying
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ dubbo-ano-consumer
+
+
+
+ com.heibaiying
+ dubbo-ano-common
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+```
+
+provider中 pom.xml如下
+
+```xml
+
+
+
+ spring-dubbo-annotation
+ com.heibaiying
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ dubbo-ano-provider
+
+
+
+ com.heibaiying
+ dubbo-ano-common
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+```
+
+## 七、关于dubbo新版本管理控制台的安装说明
+
+安装:
+
+```sh
+git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
+cd /var/tmp/dubbo-ops
+mvn clean package
+```
+
+配置:
+
+```sh
+配置文件为:
+dubbo-admin-backend/src/main/resources/application.properties
+主要的配置有 默认的配置就是127.0.0.1:2181:
+dubbo.registry.address=zookeeper://127.0.0.1:2181
+```
+
+启动:
+
+```sh
+mvn --projects dubbo-admin-backend spring-boot:run
+```
+
+访问:
+
+```
+http://127.0.0.1:8080
```
\ No newline at end of file
diff --git a/spring/spring-dubbo/README.md b/spring/spring-dubbo/README.md
index 732f063..5d836f4 100644
--- a/spring/spring-dubbo/README.md
+++ b/spring/spring-dubbo/README.md
@@ -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)
-
-
-
-
-
-## 二、项目依赖
-
-**在父工程的项目中统一导入依赖dubbo依赖的的jar包**
-
-这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
-
-```xml
-
-com.alibaba
-dubbo
-2.6.2
-
-
- org.apache.curator
- curator-framework
- 4.0.0
-
-
- org.apache.zookeeper
- zookeeper
-
-
-
-
- org.apache.zookeeper
- zookeeper
- 3.4.13
-
-```
-
-
-
-## 三、公共模块(dubbo-common)
-
-- api 下为公共的调用接口;
-- bean 下为公共的实体类。
-
-
-
-## 四、 服务提供者(dubbo-provider)
-
-
-
-#### 4.1 productService是服务的提供者( 商品数据用模拟数据展示)
-
-注:这里实现的接口IProductService来源于公共模块
-
-```java
-/**
- * @author : heibaiying
- * @description : 产品提供接口实现类
- */
-@Service
-public class ProductService implements IProductService {
-
- private static List productList = new ArrayList<>();
-
- static {
- for (int i = 0; i < 20; i++) {
- productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
- }
- }
-
- public Product queryProductById(int id) {
- for (Product product : productList) {
- if (product.getId() == id) {
- return product;
- }
- }
- return null;
- }
-
-
- public List queryAllProducts() {
- return productList;
- }
-}
-```
-
-#### 4.2 在dubbo.xml暴露服务
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-## 五、服务消费者(dubbo-consumer)
-
-
-
-#### 1.在dubbo.xml调用远程的服务
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.消费服务
-
-```java
-@Controller
-@RequestMapping("sell")
-public class SellController {
-
- @Autowired
- private IProductService productService;
-
- @RequestMapping
- public String productList(Model model) {
- List products = productService.queryAllProducts();
- model.addAttribute("products", products);
- return "products";
- }
-
- @RequestMapping("product/{id}")
- public String productDetail(@PathVariable int id, Model model) {
- Product product = productService.queryProductById(id);
- model.addAttribute("product", product);
- return "product";
- }
-}
-```
-
-## 六、项目构建的说明
-
-因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
-
-```shell
-mvn install -Dmaven.test.skip = true
-```
-
-consumer中 pom.xml如下
-
-```xml
-
-
-
- spring-dubbo
- com.heibaiying
- 1.0-SNAPSHOT
-
- 4.0.0
-
- dubbo-consumer
-
-
-
- com.heibaiying
- dubbo-common
- 1.0-SNAPSHOT
- compile
-
-
-
-
-```
-
-provider中 pom.xml如下
-
-```xml
-
-
-
- spring-dubbo
- com.heibaiying
- 1.0-SNAPSHOT
-
- 4.0.0
-
- dubbo-provider
-
-
-
- com.heibaiying
- dubbo-common
- 1.0-SNAPSHOT
- compile
-
-
-
-
-```
-
-## 七、关于dubbo新版本管理控制台的安装说明
-
-安装:
-
-```sh
-git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
-cd /var/tmp/dubbo-ops
-mvn clean package
-```
-
-配置:
-
-```sh
-配置文件为:
-dubbo-admin-backend/src/main/resources/application.properties
-主要的配置有 默认的配置就是127.0.0.1:2181:
-dubbo.registry.address=zookeeper://127.0.0.1:2181
-```
-
-启动:
-
-```sh
-mvn --projects dubbo-admin-backend spring-boot:run
-```
-
-访问:
-
-```
-http://127.0.0.1:8080
+# spring 整合 dubbo(xml配置方式)
+
## 目录
+一、 项目结构说明
+二、项目依赖
+三、公共模块(dubbo-common)
+四、 服务提供者(dubbo-provider)
+ 4.1 productService是服务的提供者( 商品数据用模拟数据展示)
+ 4.2 在dubbo.xml暴露服务
+五、服务消费者(dubbo-consumer)
+ 1.在dubbo.xml调用远程的服务
+ 2.消费服务
+六、项目构建的说明
+七、关于dubbo新版本管理控制台的安装说明
+## 正文
+
+
+## 一、 项目结构说明
+
+1.1 按照dubbo 文档推荐的服务最佳实践,建议将服务接口、服务模型、服务异常等均放在 API 包中,所以项目采用maven多模块的构建方式,在spring-dubbo下构建三个子模块:
+
+1. dubbo-common 是公共模块,用于存放公共的接口和bean,被dubbo-provider和dubbo-provider在pom.xml中引用;
+2. dubbo-provider 是服务的提供者,提供商品的查询服务;
+3. dubbo-provider 是服务的消费者,调用provider提供的查询服务。
+
+1.2 本项目dubbo的搭建采用zookeeper作为注册中心, 关于zookeeper的安装和基本操作可以参见我的手记[Zookeeper 基础命令与Java客户端](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/ZooKeeper/ZooKeeper%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA%E4%B8%8EJava%E5%AE%A2%E6%88%B7%E7%AB%AF.md)
+
+
+
+
+
+## 二、项目依赖
+
+**在父工程的项目中统一导入依赖dubbo依赖的的jar包**
+
+这里需要注意的是ZooKeeper 3.5.x 和 ZooKeeper 3.4.x 是存在不兼容的情况 详见官网解释[ZooKeeper Version Compatibility](https://curator.apache.org/zk-compatibility.html), zookeeper 3.5 目前是beta版本,所以zookeeper 我选择的版本是 zookeeper-3.4.9 作为服务端。但默认情况下 curator-framework自动引用的最新的3.5的版本客户端,会出现 KeeperException$UnimplementedException 异常
+
+```xml
+
+com.alibaba
+dubbo
+2.6.2
+
+
+ org.apache.curator
+ curator-framework
+ 4.0.0
+
+
+ org.apache.zookeeper
+ zookeeper
+
+
+
+
+ org.apache.zookeeper
+ zookeeper
+ 3.4.13
+
+```
+
+
+
+## 三、公共模块(dubbo-common)
+
+- api 下为公共的调用接口;
+- bean 下为公共的实体类。
+
+
+
+## 四、 服务提供者(dubbo-provider)
+
+
+
+#### 4.1 productService是服务的提供者( 商品数据用模拟数据展示)
+
+注:这里实现的接口IProductService来源于公共模块
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 产品提供接口实现类
+ */
+@Service
+public class ProductService implements IProductService {
+
+ private static List productList = new ArrayList<>();
+
+ static {
+ for (int i = 0; i < 20; i++) {
+ productList.add(new Product(i, "产品" + i, i / 2 == 0, new Date(), 66.66f * i));
+ }
+ }
+
+ public Product queryProductById(int id) {
+ for (Product product : productList) {
+ if (product.getId() == id) {
+ return product;
+ }
+ }
+ return null;
+ }
+
+
+ public List queryAllProducts() {
+ return productList;
+ }
+}
+```
+
+#### 4.2 在dubbo.xml暴露服务
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## 五、服务消费者(dubbo-consumer)
+
+
+
+#### 1.在dubbo.xml调用远程的服务
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.消费服务
+
+```java
+@Controller
+@RequestMapping("sell")
+public class SellController {
+
+ @Autowired
+ private IProductService productService;
+
+ @RequestMapping
+ public String productList(Model model) {
+ List products = productService.queryAllProducts();
+ model.addAttribute("products", products);
+ return "products";
+ }
+
+ @RequestMapping("product/{id}")
+ public String productDetail(@PathVariable int id, Model model) {
+ Product product = productService.queryProductById(id);
+ model.addAttribute("product", product);
+ return "product";
+ }
+}
+```
+
+## 六、项目构建的说明
+
+因为在项目中,consumer和provider模块均依赖公共模块,所以在构建consumer和provider项目前需要将common 模块安装到本地仓库,**依次**对**父工程**和**common模块**执行:
+
+```shell
+mvn install -Dmaven.test.skip = true
+```
+
+consumer中 pom.xml如下
+
+```xml
+
+
+
+ spring-dubbo
+ com.heibaiying
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ dubbo-consumer
+
+
+
+ com.heibaiying
+ dubbo-common
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+```
+
+provider中 pom.xml如下
+
+```xml
+
+
+
+ spring-dubbo
+ com.heibaiying
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ dubbo-provider
+
+
+
+ com.heibaiying
+ dubbo-common
+ 1.0-SNAPSHOT
+ compile
+
+
+
+
+```
+
+## 七、关于dubbo新版本管理控制台的安装说明
+
+安装:
+
+```sh
+git clone https://github.com/apache/incubator-dubbo-ops.git /var/tmp/dubbo-ops
+cd /var/tmp/dubbo-ops
+mvn clean package
+```
+
+配置:
+
+```sh
+配置文件为:
+dubbo-admin-backend/src/main/resources/application.properties
+主要的配置有 默认的配置就是127.0.0.1:2181:
+dubbo.registry.address=zookeeper://127.0.0.1:2181
+```
+
+启动:
+
+```sh
+mvn --projects dubbo-admin-backend spring-boot:run
+```
+
+访问:
+
+```
+http://127.0.0.1:8080
```
\ No newline at end of file
diff --git a/spring/spring-email-annotation/README.md b/spring/spring-email-annotation/README.md
index cf0550e..4ef4b95 100644
--- a/spring/spring-email-annotation/README.md
+++ b/spring/spring-email-annotation/README.md
@@ -1,287 +1,297 @@
-# spring 邮件发送(xml配置方式)
-
-## 一、说明
-
-### 1.1 项目结构说明
-
-1. 邮件发送配置类为com.heibaiying.config下EmailConfig.java;
-2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
-3. 项目以单元测试的方法进行测试,测试类为SendEmail。
+# spring 邮件发送(xml配置方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring email
+ 2.1 邮件发送配置
+ 2.2 新建邮件发送基本类
+ 2.3 邮件发送的测试
+## 正文
-
-
-
-
-
-### 1.2 依赖说明
-
-除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
-
-```xml
-
-
- org.springframework
- spring-context-support
- ${spring-base-version}
-
-
-
-
- com.ibeetl
- beetl
- 2.9.7
-
-```
-
-
-
-## 二、spring email
-
-#### 2.1 邮件发送配置
-
-```java
-/**
- * @author : heibaiying
- * @description : 邮件发送配置类
- */
-
-@Configuration
-@ComponentScan(value = "com.heibaiying.email")
-public class EmailConfig {
-
- /***
- * 在这里可以声明不同的邮件服务器主机,通常是SMTP主机,而具体的用户名和时授权码则建议在业务中从数据库查询
- */
- @Bean(name = "qqMailSender")
- JavaMailSenderImpl javaMailSender() {
- JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
- javaMailSender.setHost("smtp.qq.com");
- javaMailSender.setPassword("587");
- return javaMailSender;
- }
-
- /***
- * 配置模板引擎
- */
- @Bean
- GroupTemplate groupTemplate() throws IOException {
- //指定加载模板资源的位置 指定在classpath:beetl下-
- ClasspathResourceLoader loader = new ClasspathResourceLoader("beetl");
- //beetl配置 这里采用默认的配置-
- org.beetl.core.Configuration configuration = org.beetl.core.Configuration.defaultConfiguration();
- return new GroupTemplate(loader, configuration);
- }
-}
-
-```
-
-#### 2.2 新建邮件发送基本类
-
-```java
-/**
- * @author : heibaiying
- * @description : 邮件发送基本类
- */
-@Component
-public class SpringMail {
-
- @Autowired
- private JavaMailSenderImpl qqMailSender;
- @Autowired
- private GroupTemplate groupTemplate;
-
- /**
- * 发送简单邮件
- * 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
- * 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
- * qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
- */
- public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
- // 设置发送人邮箱和授权码
- qqMailSender.setUsername(from);
- qqMailSender.setPassword(authWord);
- // 实例化消息对象
- SimpleMailMessage msg = new SimpleMailMessage();
- msg.setFrom(from);
- msg.setTo(to);
- msg.setSubject(subject);
- msg.setText(content);
- try {
- // 发送消息
- this.qqMailSender.send(msg);
- System.out.println("发送邮件成功");
- } catch (MailException ex) {
- // 消息发送失败可以做对应的处理
- System.err.println("发送邮件失败" + ex.getMessage());
- }
- }
-
- /**
- * 发送带附件的邮件
- */
- public void sendEmailWithAttachments(String from, String authWord, String to,
- String subject, String content, Map files) {
- try {
- // 设置发送人邮箱和授权码
- qqMailSender.setUsername(from);
- qqMailSender.setPassword(authWord);
- // 实例化消息对象
- MimeMessage message = qqMailSender.createMimeMessage();
- // 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
- MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
- helper.setFrom(from);
- helper.setTo(to);
- helper.setSubject(subject);
- helper.setText(content);
- // 传入附件
- for (Map.Entry entry : files.entrySet()) {
- helper.addAttachment(entry.getKey(), entry.getValue());
- }
- // 发送消息
- this.qqMailSender.send(message);
- System.out.println("发送邮件成功");
- } catch (MessagingException ex) {
- // 消息发送失败可以做对应的处理
- System.err.println("发送邮件失败" + ex.getMessage());
- }
- }
-
-
- /**
- * 发送带内嵌资源的邮件
- */
- public void sendEmailWithInline(String from, String authWord, String to,
- String subject, String content, File file) {
- try {
- // 设置发送人邮箱和授权码
- qqMailSender.setUsername(from);
- qqMailSender.setPassword(authWord);
- // 实例化消息对象
- MimeMessage message = qqMailSender.createMimeMessage();
- // 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
- MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
- helper.setFrom(from);
- helper.setTo(to);
- helper.setSubject(subject);
- // 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
- helper.setText("
", true);
- // 需要先指定文本 再指定资源文件
- FileSystemResource res = new FileSystemResource(file);
- helper.addInline("image", res);
- // 发送消息
- this.qqMailSender.send(message);
- System.out.println("发送邮件成功");
- } catch (MessagingException ex) {
- // 消息发送失败可以做对应的处理
- System.err.println("发送邮件失败" + ex.getMessage());
- }
- }
-
- /**
- * 使用模板邮件
- */
- public void sendEmailByTemplate(String from, String authWord, String to,
- String subject, String content) {
- try {
- Template t = groupTemplate.getTemplate("template.html");
- t.binding("subject", subject);
- t.binding("content", content);
- String text = t.render();
- // 设置发送人邮箱和授权码
- qqMailSender.setUsername(from);
- qqMailSender.setPassword(authWord);
- // 实例化消息对象
- MimeMessage message = qqMailSender.createMimeMessage();
- // 指定 utf-8 防止乱码
- MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
- helper.setFrom(from);
- helper.setTo(to);
- helper.setSubject(subject);
- // 为true 时候 表示文本内容以 html 渲染
- helper.setText(text, true);
- this.qqMailSender.send(message);
- System.out.println("发送邮件成功");
- } catch (MessagingException ex) {
- // 消息发送失败可以做对应的处理
- System.err.println("发送邮件失败" + ex.getMessage());
- }
- }
-
-}
-
-```
-
-**关于模板邮件的说明:**
-
-- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
-
-- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
-
- 一个简单的模板template.html如下:
-
-```html
-
-
-
-
-
-
- 邮件主题: ${subject}
- ${content}
-
-
-```
-
-
-
-#### 2.3 邮件发送的测试
-
-```java
-/**
- * @author : heibaiying
- * @description : 发送邮件测试类
- */
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = EmailConfig.class)
-public class SendEmail {
-
- @Autowired
- private SpringMail springMail;
-
- // 发送方邮箱地址
- private static final String from = "发送方邮箱地址@qq.com";
- // 发送方邮箱地址对应的授权码
- private static final String authWord = "授权码";
- // 接收方邮箱地址
- private static final String to = "接收方邮箱地址@qq.com";
-
- @Test
- public void sendMessage() {
-
- springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
- }
-
-
- @Test
- public void sendComplexMessage() {
- Map fileMap = new HashMap<>();
- fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
- fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
- springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
- , "Hello Spring Email!", fileMap);
- }
-
- @Test
- public void sendEmailWithInline() {
- springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
- , "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
- }
-
- @Test
- public void sendEmailByTemplate() {
- springMail.sendEmailByTemplate(from, authWord, to,
- "spring模板邮件", "Hello Spring Email!");
- }
-}
-```
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 邮件发送配置类为com.heibaiying.config下EmailConfig.java;
+2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
+3. 项目以单元测试的方法进行测试,测试类为SendEmail。
+
+
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
+
+```xml
+
+
+ org.springframework
+ spring-context-support
+ ${spring-base-version}
+
+
+
+
+ com.ibeetl
+ beetl
+ 2.9.7
+
+```
+
+
+
+## 二、spring email
+
+#### 2.1 邮件发送配置
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 邮件发送配置类
+ */
+
+@Configuration
+@ComponentScan(value = "com.heibaiying.email")
+public class EmailConfig {
+
+ /***
+ * 在这里可以声明不同的邮件服务器主机,通常是SMTP主机,而具体的用户名和时授权码则建议在业务中从数据库查询
+ */
+ @Bean(name = "qqMailSender")
+ JavaMailSenderImpl javaMailSender() {
+ JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
+ javaMailSender.setHost("smtp.qq.com");
+ javaMailSender.setPassword("587");
+ return javaMailSender;
+ }
+
+ /***
+ * 配置模板引擎
+ */
+ @Bean
+ GroupTemplate groupTemplate() throws IOException {
+ //指定加载模板资源的位置 指定在classpath:beetl下-
+ ClasspathResourceLoader loader = new ClasspathResourceLoader("beetl");
+ //beetl配置 这里采用默认的配置-
+ org.beetl.core.Configuration configuration = org.beetl.core.Configuration.defaultConfiguration();
+ return new GroupTemplate(loader, configuration);
+ }
+}
+
+```
+
+#### 2.2 新建邮件发送基本类
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 邮件发送基本类
+ */
+@Component
+public class SpringMail {
+
+ @Autowired
+ private JavaMailSenderImpl qqMailSender;
+ @Autowired
+ private GroupTemplate groupTemplate;
+
+ /**
+ * 发送简单邮件
+ * 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
+ * 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
+ * qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
+ */
+ public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
+ // 设置发送人邮箱和授权码
+ qqMailSender.setUsername(from);
+ qqMailSender.setPassword(authWord);
+ // 实例化消息对象
+ SimpleMailMessage msg = new SimpleMailMessage();
+ msg.setFrom(from);
+ msg.setTo(to);
+ msg.setSubject(subject);
+ msg.setText(content);
+ try {
+ // 发送消息
+ this.qqMailSender.send(msg);
+ System.out.println("发送邮件成功");
+ } catch (MailException ex) {
+ // 消息发送失败可以做对应的处理
+ System.err.println("发送邮件失败" + ex.getMessage());
+ }
+ }
+
+ /**
+ * 发送带附件的邮件
+ */
+ public void sendEmailWithAttachments(String from, String authWord, String to,
+ String subject, String content, Map files) {
+ try {
+ // 设置发送人邮箱和授权码
+ qqMailSender.setUsername(from);
+ qqMailSender.setPassword(authWord);
+ // 实例化消息对象
+ MimeMessage message = qqMailSender.createMimeMessage();
+ // 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
+ helper.setFrom(from);
+ helper.setTo(to);
+ helper.setSubject(subject);
+ helper.setText(content);
+ // 传入附件
+ for (Map.Entry entry : files.entrySet()) {
+ helper.addAttachment(entry.getKey(), entry.getValue());
+ }
+ // 发送消息
+ this.qqMailSender.send(message);
+ System.out.println("发送邮件成功");
+ } catch (MessagingException ex) {
+ // 消息发送失败可以做对应的处理
+ System.err.println("发送邮件失败" + ex.getMessage());
+ }
+ }
+
+
+ /**
+ * 发送带内嵌资源的邮件
+ */
+ public void sendEmailWithInline(String from, String authWord, String to,
+ String subject, String content, File file) {
+ try {
+ // 设置发送人邮箱和授权码
+ qqMailSender.setUsername(from);
+ qqMailSender.setPassword(authWord);
+ // 实例化消息对象
+ MimeMessage message = qqMailSender.createMimeMessage();
+ // 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
+ helper.setFrom(from);
+ helper.setTo(to);
+ helper.setSubject(subject);
+ // 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
+ helper.setText("
", true);
+ // 需要先指定文本 再指定资源文件
+ FileSystemResource res = new FileSystemResource(file);
+ helper.addInline("image", res);
+ // 发送消息
+ this.qqMailSender.send(message);
+ System.out.println("发送邮件成功");
+ } catch (MessagingException ex) {
+ // 消息发送失败可以做对应的处理
+ System.err.println("发送邮件失败" + ex.getMessage());
+ }
+ }
+
+ /**
+ * 使用模板邮件
+ */
+ public void sendEmailByTemplate(String from, String authWord, String to,
+ String subject, String content) {
+ try {
+ Template t = groupTemplate.getTemplate("template.html");
+ t.binding("subject", subject);
+ t.binding("content", content);
+ String text = t.render();
+ // 设置发送人邮箱和授权码
+ qqMailSender.setUsername(from);
+ qqMailSender.setPassword(authWord);
+ // 实例化消息对象
+ MimeMessage message = qqMailSender.createMimeMessage();
+ // 指定 utf-8 防止乱码
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
+ helper.setFrom(from);
+ helper.setTo(to);
+ helper.setSubject(subject);
+ // 为true 时候 表示文本内容以 html 渲染
+ helper.setText(text, true);
+ this.qqMailSender.send(message);
+ System.out.println("发送邮件成功");
+ } catch (MessagingException ex) {
+ // 消息发送失败可以做对应的处理
+ System.err.println("发送邮件失败" + ex.getMessage());
+ }
+ }
+
+}
+
+```
+
+**关于模板邮件的说明:**
+
+- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
+
+- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
+
+ 一个简单的模板template.html如下:
+
+```html
+
+
+
+
+
+
+ 邮件主题: ${subject}
+ ${content}
+
+
+```
+
+
+
+#### 2.3 邮件发送的测试
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 发送邮件测试类
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = EmailConfig.class)
+public class SendEmail {
+
+ @Autowired
+ private SpringMail springMail;
+
+ // 发送方邮箱地址
+ private static final String from = "发送方邮箱地址@qq.com";
+ // 发送方邮箱地址对应的授权码
+ private static final String authWord = "授权码";
+ // 接收方邮箱地址
+ private static final String to = "接收方邮箱地址@qq.com";
+
+ @Test
+ public void sendMessage() {
+
+ springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
+ }
+
+
+ @Test
+ public void sendComplexMessage() {
+ Map fileMap = new HashMap<>();
+ fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
+ fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
+ springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
+ , "Hello Spring Email!", fileMap);
+ }
+
+ @Test
+ public void sendEmailWithInline() {
+ springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
+ , "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
+ }
+
+ @Test
+ public void sendEmailByTemplate() {
+ springMail.sendEmailByTemplate(from, authWord, to,
+ "spring模板邮件", "Hello Spring Email!");
+ }
+}
+```
diff --git a/spring/spring-email/README.md b/spring/spring-email/README.md
index fc51875..2d8a0ad 100644
--- a/spring/spring-email/README.md
+++ b/spring/spring-email/README.md
@@ -1,298 +1,308 @@
-# spring 邮件发送(xml配置方式)
-
-## 一、说明
-
-### 1.1 项目结构说明
-
-1. 邮件发送配置文件为springApplication.xml;
-2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
-3. 项目以单元测试的方法进行测试,测试类为SendEmail。
+# spring 邮件发送(xml配置方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring email
+ 2.1 邮件发送配置
+ 2.2 新建邮件发送基本类
+ 2.3 邮件发送的测试
+## 正文
-
-
-
-
-
-### 1.2 依赖说明
-
-除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
-
-```xml
-
-
- org.springframework
- spring-context-support
- ${spring-base-version}
-
-
-
-
- com.ibeetl
- beetl
- 2.9.7
-
-```
-
-
-
-## 二、spring email
-
-#### 2.1 邮件发送配置
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.2 新建邮件发送基本类
-
-```java
-/**
- * @author : heibaiying
- * @description : 邮件发送基本类
- */
-@Component
-public class SpringMail {
-
- @Autowired
- private JavaMailSenderImpl qqMailSender;
- @Autowired
- private GroupTemplate groupTemplate;
-
- /**
- * 发送简单邮件
- * 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
- * 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
- * qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
- */
- public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
- // 设置发送人邮箱和授权码
- qqMailSender.setUsername(from);
- qqMailSender.setPassword(authWord);
- // 实例化消息对象
- SimpleMailMessage msg = new SimpleMailMessage();
- msg.setFrom(from);
- msg.setTo(to);
- msg.setSubject(subject);
- msg.setText(content);
- try {
- // 发送消息
- this.qqMailSender.send(msg);
- System.out.println("发送邮件成功");
- } catch (MailException ex) {
- // 消息发送失败可以做对应的处理
- System.err.println("发送邮件失败" + ex.getMessage());
- }
- }
-
- /**
- * 发送带附件的邮件
- */
- public void sendEmailWithAttachments(String from, String authWord, String to,
- String subject, String content, Map files) {
- try {
- // 设置发送人邮箱和授权码
- qqMailSender.setUsername(from);
- qqMailSender.setPassword(authWord);
- // 实例化消息对象
- MimeMessage message = qqMailSender.createMimeMessage();
- // 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
- MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
- helper.setFrom(from);
- helper.setTo(to);
- helper.setSubject(subject);
- helper.setText(content);
- // 传入附件
- for (Map.Entry entry : files.entrySet()) {
- helper.addAttachment(entry.getKey(), entry.getValue());
- }
- // 发送消息
- this.qqMailSender.send(message);
- System.out.println("发送邮件成功");
- } catch (MessagingException ex) {
- // 消息发送失败可以做对应的处理
- System.err.println("发送邮件失败" + ex.getMessage());
- }
- }
-
-
- /**
- * 发送带内嵌资源的邮件
- */
- public void sendEmailWithInline(String from, String authWord, String to,
- String subject, String content, File file) {
- try {
- // 设置发送人邮箱和授权码
- qqMailSender.setUsername(from);
- qqMailSender.setPassword(authWord);
- // 实例化消息对象
- MimeMessage message = qqMailSender.createMimeMessage();
- // 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
- MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
- helper.setFrom(from);
- helper.setTo(to);
- helper.setSubject(subject);
- // 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
- helper.setText("
", true);
- // 需要先指定文本 再指定资源文件
- FileSystemResource res = new FileSystemResource(file);
- helper.addInline("image", res);
- // 发送消息
- this.qqMailSender.send(message);
- System.out.println("发送邮件成功");
- } catch (MessagingException ex) {
- // 消息发送失败可以做对应的处理
- System.err.println("发送邮件失败" + ex.getMessage());
- }
- }
-
- /**
- * 使用模板邮件
- */
- public void sendEmailByTemplate(String from, String authWord, String to,
- String subject, String content) {
- try {
- Template t = groupTemplate.getTemplate("template.html");
- t.binding("subject", subject);
- t.binding("content", content);
- String text = t.render();
- // 设置发送人邮箱和授权码
- qqMailSender.setUsername(from);
- qqMailSender.setPassword(authWord);
- // 实例化消息对象
- MimeMessage message = qqMailSender.createMimeMessage();
- // 指定 utf-8 防止乱码
- MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
- helper.setFrom(from);
- helper.setTo(to);
- helper.setSubject(subject);
- // 为true 时候 表示文本内容以 html 渲染
- helper.setText(text, true);
- this.qqMailSender.send(message);
- System.out.println("发送邮件成功");
- } catch (MessagingException ex) {
- // 消息发送失败可以做对应的处理
- System.err.println("发送邮件失败" + ex.getMessage());
- }
- }
-
-}
-
-```
-
-**关于模板邮件的说明:**
-
-- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
-
-- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
-
- 一个简单的模板template.html如下:
-
-```html
-
-
-
-
-
-
- 邮件主题: ${subject}
- ${content}
-
-
-```
-
-
-
-#### 2.3 邮件发送的测试
-
-```java
-/**
- * @author : heibaiying
- * @description : 发送邮件测试类
- */
-@RunWith(SpringRunner.class)
-@ContextConfiguration({"classpath:springApplication.xml"})
-public class SendEmail {
-
- @Autowired
- private SpringMail springMail;
-
- // 发送方邮箱地址
- private static final String from = "发送方邮箱地址@qq.com";
- // 发送方邮箱地址对应的授权码
- private static final String authWord = "授权码";
- // 接收方邮箱地址
- private static final String to = "接收方邮箱地址@qq.com";
-
-
- /**
- * 简单邮件测试
- */
- @Test
- public void sendMessage() {
- springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
- }
-
- /**
- * 发送带附件的邮件
- */
- @Test
- public void sendComplexMessage() {
- Map fileMap = new HashMap<>();
- fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
- fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
- springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
- , "Hello Spring Email!", fileMap);
- }
-
- /**
- * 发送内嵌资源的邮件
- */
- @Test
- public void sendEmailWithInline() {
- springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
- , "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
- }
-
- /**
- * 发送模板邮件
- */
- @Test
- public void sendEmailByTemplate() {
- springMail.sendEmailByTemplate(from, authWord, to,
- "spring模板邮件", "Hello Spring Email!");
- }
-}
-
-```
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 邮件发送配置文件为springApplication.xml;
+2. 简单邮件发送、附件邮件发送、内嵌资源邮件发送、模板邮件发送的方法封装在SpringMail类中;
+3. 项目以单元测试的方法进行测试,测试类为SendEmail。
+
+
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入邮件发送的支持包spring-context-support
+
+```xml
+
+
+ org.springframework
+ spring-context-support
+ ${spring-base-version}
+
+
+
+
+ com.ibeetl
+ beetl
+ 2.9.7
+
+```
+
+
+
+## 二、spring email
+
+#### 2.1 邮件发送配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.2 新建邮件发送基本类
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 邮件发送基本类
+ */
+@Component
+public class SpringMail {
+
+ @Autowired
+ private JavaMailSenderImpl qqMailSender;
+ @Autowired
+ private GroupTemplate groupTemplate;
+
+ /**
+ * 发送简单邮件
+ * 在qq邮件发送的测试中,测试结果表明不管是简单邮件还是复杂邮件都必须指定发送用户,
+ * 且发送用户已经授权不然都会抛出异常: SMTPSendFailedException 501 mail from address must be same as authorization user
+ * qq 的授权码 可以在 设置/账户/POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 中开启服务后获取
+ */
+ public void sendTextMessage(String from, String authWord, String to, String subject, String content) {
+ // 设置发送人邮箱和授权码
+ qqMailSender.setUsername(from);
+ qqMailSender.setPassword(authWord);
+ // 实例化消息对象
+ SimpleMailMessage msg = new SimpleMailMessage();
+ msg.setFrom(from);
+ msg.setTo(to);
+ msg.setSubject(subject);
+ msg.setText(content);
+ try {
+ // 发送消息
+ this.qqMailSender.send(msg);
+ System.out.println("发送邮件成功");
+ } catch (MailException ex) {
+ // 消息发送失败可以做对应的处理
+ System.err.println("发送邮件失败" + ex.getMessage());
+ }
+ }
+
+ /**
+ * 发送带附件的邮件
+ */
+ public void sendEmailWithAttachments(String from, String authWord, String to,
+ String subject, String content, Map files) {
+ try {
+ // 设置发送人邮箱和授权码
+ qqMailSender.setUsername(from);
+ qqMailSender.setPassword(authWord);
+ // 实例化消息对象
+ MimeMessage message = qqMailSender.createMimeMessage();
+ // 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
+ helper.setFrom(from);
+ helper.setTo(to);
+ helper.setSubject(subject);
+ helper.setText(content);
+ // 传入附件
+ for (Map.Entry entry : files.entrySet()) {
+ helper.addAttachment(entry.getKey(), entry.getValue());
+ }
+ // 发送消息
+ this.qqMailSender.send(message);
+ System.out.println("发送邮件成功");
+ } catch (MessagingException ex) {
+ // 消息发送失败可以做对应的处理
+ System.err.println("发送邮件失败" + ex.getMessage());
+ }
+ }
+
+
+ /**
+ * 发送带内嵌资源的邮件
+ */
+ public void sendEmailWithInline(String from, String authWord, String to,
+ String subject, String content, File file) {
+ try {
+ // 设置发送人邮箱和授权码
+ qqMailSender.setUsername(from);
+ qqMailSender.setPassword(authWord);
+ // 实例化消息对象
+ MimeMessage message = qqMailSender.createMimeMessage();
+ // 需要指定第二个参数为true 代表创建支持可选文本,内联元素和附件的多部分消息
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
+ helper.setFrom(from);
+ helper.setTo(to);
+ helper.setSubject(subject);
+ // 使用true标志来指示包含的文本是HTML 固定格式资源前缀 cid:
+ helper.setText("
", true);
+ // 需要先指定文本 再指定资源文件
+ FileSystemResource res = new FileSystemResource(file);
+ helper.addInline("image", res);
+ // 发送消息
+ this.qqMailSender.send(message);
+ System.out.println("发送邮件成功");
+ } catch (MessagingException ex) {
+ // 消息发送失败可以做对应的处理
+ System.err.println("发送邮件失败" + ex.getMessage());
+ }
+ }
+
+ /**
+ * 使用模板邮件
+ */
+ public void sendEmailByTemplate(String from, String authWord, String to,
+ String subject, String content) {
+ try {
+ Template t = groupTemplate.getTemplate("template.html");
+ t.binding("subject", subject);
+ t.binding("content", content);
+ String text = t.render();
+ // 设置发送人邮箱和授权码
+ qqMailSender.setUsername(from);
+ qqMailSender.setPassword(authWord);
+ // 实例化消息对象
+ MimeMessage message = qqMailSender.createMimeMessage();
+ // 指定 utf-8 防止乱码
+ MimeMessageHelper helper = new MimeMessageHelper(message, true, "utf-8");
+ helper.setFrom(from);
+ helper.setTo(to);
+ helper.setSubject(subject);
+ // 为true 时候 表示文本内容以 html 渲染
+ helper.setText(text, true);
+ this.qqMailSender.send(message);
+ System.out.println("发送邮件成功");
+ } catch (MessagingException ex) {
+ // 消息发送失败可以做对应的处理
+ System.err.println("发送邮件失败" + ex.getMessage());
+ }
+ }
+
+}
+
+```
+
+**关于模板邮件的说明:**
+
+- 模板引擎最主要的作用是,在对邮件格式有要求的时候,采用拼接字符串不够直观,所以采用模板引擎;
+
+- 这里我们使用的beetl模板引擎,原因是其性能优异,官网是介绍其性能6倍与freemaker,并有完善的文档支持。当然大家也可以换成任何其他的模板引擎(freemarker,thymeleaf)
+
+ 一个简单的模板template.html如下:
+
+```html
+
+
+
+
+
+
+ 邮件主题: ${subject}
+ ${content}
+
+
+```
+
+
+
+#### 2.3 邮件发送的测试
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 发送邮件测试类
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration({"classpath:springApplication.xml"})
+public class SendEmail {
+
+ @Autowired
+ private SpringMail springMail;
+
+ // 发送方邮箱地址
+ private static final String from = "发送方邮箱地址@qq.com";
+ // 发送方邮箱地址对应的授权码
+ private static final String authWord = "授权码";
+ // 接收方邮箱地址
+ private static final String to = "接收方邮箱地址@qq.com";
+
+
+ /**
+ * 简单邮件测试
+ */
+ @Test
+ public void sendMessage() {
+ springMail.sendTextMessage(from, authWord, to, "spring简单邮件", "Hello Spring Email!");
+ }
+
+ /**
+ * 发送带附件的邮件
+ */
+ @Test
+ public void sendComplexMessage() {
+ Map fileMap = new HashMap<>();
+ fileMap.put("image1.jpg", new File("D:\\LearningNotes\\picture\\msm相关依赖.png"));
+ fileMap.put("image2.jpg", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
+ springMail.sendEmailWithAttachments(from, authWord, to, "spring多附件邮件"
+ , "Hello Spring Email!", fileMap);
+ }
+
+ /**
+ * 发送内嵌资源的邮件
+ */
+ @Test
+ public void sendEmailWithInline() {
+ springMail.sendEmailWithInline(from, authWord, to, "spring内嵌资源邮件"
+ , "Hello Spring Email!", new File("D:\\LearningNotes\\picture\\RabbitMQ模型架构.png"));
+ }
+
+ /**
+ * 发送模板邮件
+ */
+ @Test
+ public void sendEmailByTemplate() {
+ springMail.sendEmailByTemplate(from, authWord, to,
+ "spring模板邮件", "Hello Spring Email!");
+ }
+}
+
+```
diff --git a/spring/spring-jdbc-annotation/README.md b/spring/spring-jdbc-annotation/README.md
index e53f9e5..60ea7ab 100644
--- a/spring/spring-jdbc-annotation/README.md
+++ b/spring/spring-jdbc-annotation/README.md
@@ -1,5 +1,16 @@
# spring 整合 jdbc template(注解方式)
+## 目录
+1.说明
+ 1.2 项目依赖
+二、spring 整合 jdbc template
+ 2.1 在resources文件夹下新建数据库配置文件mysql.properties、oracle.properties及其映射类
+ 2.2 新建数据库配置类DatabaseConfig.java
+ 2.3 新建查询接口及其实现类
+ 2.4 新建测试类进行测试
+## 正文
+
+
## 1.说明
#### 1.1 项目目录结构
diff --git a/spring/spring-jdbc/README.md b/spring/spring-jdbc/README.md
index 7017f78..a520db7 100644
--- a/spring/spring-jdbc/README.md
+++ b/spring/spring-jdbc/README.md
@@ -1,5 +1,17 @@
# spring 整合 jdbc template(xml配置方式)
+## 目录
+一、说明
+ 1.1 项目结构
+ 1.2 项目依赖
+二、 spring 整合 jdbc template
+ 1、在resources文件夹下新建数据库配置文件jdbc.properties
+ 2、配置Jdbc数据源并定义事务管理器
+ 3、新建查询接口及其实现类
+ 4.新建测试类进行测试
+## 正文
+
+
## 一、说明
#### 1.1 项目结构
diff --git a/spring/spring-memcached-annotation/README.md b/spring/spring-memcached-annotation/README.md
index 487f8a1..682843a 100644
--- a/spring/spring-memcached-annotation/README.md
+++ b/spring/spring-memcached-annotation/README.md
@@ -1,150 +1,163 @@
-# spring 整合 mecached(注解方式)
-
-## 一、说明
-
-### 1.1 XMemcached客户端说明
-
-XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
-
-### 1.2 项目结构说明
-
-1. memcached的整合配置位于com.heibaiying.config文件夹下。
-
-
-
-### 1.3 依赖说明
-
-除了spring的基本依赖外,需要导入xmemcached依赖包
-
-```xml
-
-
- com.googlecode.xmemcached
- xmemcached
- 2.4.5
-
-```
+# spring 整合 mecached(注解方式)
+
## 目录
+一、说明
+ 1.1 XMemcached客户端说明
+ 1.2 项目结构说明
+ 1.3 依赖说明
+二、spring 整合 memcached
+ 2.1 单机配置
+ 2.2 集群配置
+ 2.3 存储基本类型测试用例
+ 2.5 存储实体对象测试用例
+附:memcached 基本命令
+## 正文
-
-## 二、spring 整合 memcached
-
-#### 2.1 单机配置
-
-```java
-@Bean
-public MemcachedClient memcachedClient() {
- XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
- MemcachedClient memcachedClient = null;
-try {
- memcachedClient = builder.build();
- } catch (IOException e) {
- e.printStackTrace();
-}
- return memcachedClient;
-}
-```
-
-#### 2.2 集群配置
-
-```java
-@Bean
-public MemcachedClient memcachedClientForCluster() {
-
- List addressList = new ArrayList();
- addressList.add(new InetSocketAddress("192.168.200.201", 11211));
- addressList.add(new InetSocketAddress("192.168.200.201", 11212));
- // 赋予权重
- int[] weights = {1, 2};
- XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
- // 设置连接池大小
- builder.setConnectionPoolSize(10);
- // 协议工厂
- builder.setCommandFactory(new TextCommandFactory());
- // 分布策略,一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
- builder.setSessionLocator(new KetamaMemcachedSessionLocator());
- // 设置序列化器
- builder.setTranscoder(new SerializingTranscoder());
- MemcachedClient memcachedClient = null;
- try {
- memcachedClient = builder.build();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return memcachedClient;
-}
-```
-
-#### 2.3 存储基本类型测试用例
-
-xmemcached单机版本和集群版本注入的实例是相同的;
-
-```java
-/**
- * @author : heibaiying
- * @description : Memcached 操作基本对象
- */
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = {MemcacheConfig.class})
-public class MemSamples {
-
- @Autowired
- private MemcachedClient memcachedClient;
-
- @Test
- public void operate() throws InterruptedException, MemcachedException, TimeoutException {
- memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
- String value = memcachedClient.get("hello");
- System.out.println("hello=" + value);
- memcachedClient.delete("hello");
- value = memcachedClient.get("hello");
- System.out.println("hello=" + value);
- }
-}
-```
-
-#### 2.5 存储实体对象测试用例
-
-```java
-/**
- * @author : heibaiying
- * @description :Memcached 序列化与反序列化
- */
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = {MemcacheConfig.class})
-public class MemObjectSamples {
-
- @Autowired
- private MemcachedClient memcachedClient;
-
- @Test
- public void operate() throws InterruptedException, MemcachedException, TimeoutException {
- memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
- Programmer programmer = memcachedClient.get("programmer");
- System.out.println("hello ," + programmer.getName());
- memcachedClient.delete("programmer");
- programmer = memcachedClient.get("programmer");
- Assert.assertNull(programmer);
- }
-}
-
-```
-
-
-
-## 附:memcached 基本命令
-
-| 命令 | 格式 | 说明 |
-| --------------- | -------------------------------------------------- | ------------------------------------- |
-| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
-| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
-| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
-| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
-| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
-| 查询操作 get | get key | |
-| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
-| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
-| 删除 delete | delete key | 将数据打一个删除标记 |
-| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
-| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
+## 一、说明
+
+### 1.1 XMemcached客户端说明
+
+XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
+
+### 1.2 项目结构说明
+
+1. memcached的整合配置位于com.heibaiying.config文件夹下。
+
+
+
+### 1.3 依赖说明
+
+除了spring的基本依赖外,需要导入xmemcached依赖包
+
+```xml
+
+
+ com.googlecode.xmemcached
+ xmemcached
+ 2.4.5
+
+```
+
+
+
+## 二、spring 整合 memcached
+
+#### 2.1 单机配置
+
+```java
+@Bean
+public MemcachedClient memcachedClient() {
+ XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
+ MemcachedClient memcachedClient = null;
+try {
+ memcachedClient = builder.build();
+ } catch (IOException e) {
+ e.printStackTrace();
+}
+ return memcachedClient;
+}
+```
+
+#### 2.2 集群配置
+
+```java
+@Bean
+public MemcachedClient memcachedClientForCluster() {
+
+ List addressList = new ArrayList();
+ addressList.add(new InetSocketAddress("192.168.200.201", 11211));
+ addressList.add(new InetSocketAddress("192.168.200.201", 11212));
+ // 赋予权重
+ int[] weights = {1, 2};
+ XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
+ // 设置连接池大小
+ builder.setConnectionPoolSize(10);
+ // 协议工厂
+ builder.setCommandFactory(new TextCommandFactory());
+ // 分布策略,一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
+ builder.setSessionLocator(new KetamaMemcachedSessionLocator());
+ // 设置序列化器
+ builder.setTranscoder(new SerializingTranscoder());
+ MemcachedClient memcachedClient = null;
+ try {
+ memcachedClient = builder.build();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return memcachedClient;
+}
+```
+
+#### 2.3 存储基本类型测试用例
+
+xmemcached单机版本和集群版本注入的实例是相同的;
+
+```java
+/**
+ * @author : heibaiying
+ * @description : Memcached 操作基本对象
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = {MemcacheConfig.class})
+public class MemSamples {
+
+ @Autowired
+ private MemcachedClient memcachedClient;
+
+ @Test
+ public void operate() throws InterruptedException, MemcachedException, TimeoutException {
+ memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
+ String value = memcachedClient.get("hello");
+ System.out.println("hello=" + value);
+ memcachedClient.delete("hello");
+ value = memcachedClient.get("hello");
+ System.out.println("hello=" + value);
+ }
+}
+```
+
+#### 2.5 存储实体对象测试用例
+
+```java
+/**
+ * @author : heibaiying
+ * @description :Memcached 序列化与反序列化
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = {MemcacheConfig.class})
+public class MemObjectSamples {
+
+ @Autowired
+ private MemcachedClient memcachedClient;
+
+ @Test
+ public void operate() throws InterruptedException, MemcachedException, TimeoutException {
+ memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
+ Programmer programmer = memcachedClient.get("programmer");
+ System.out.println("hello ," + programmer.getName());
+ memcachedClient.delete("programmer");
+ programmer = memcachedClient.get("programmer");
+ Assert.assertNull(programmer);
+ }
+}
+
+```
+
+
+
+## 附:memcached 基本命令
+
+| 命令 | 格式 | 说明 |
+| --------------- | -------------------------------------------------- | ------------------------------------- |
+| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
+| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
+| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
+| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
+| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
+| 查询操作 get | get key | |
+| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
+| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
+| 删除 delete | delete key | 将数据打一个删除标记 |
+| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
+| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
| 清库 | flush_all | |
\ No newline at end of file
diff --git a/spring/spring-memcached/README.md b/spring/spring-memcached/README.md
index b4ca3af..83094aa 100644
--- a/spring/spring-memcached/README.md
+++ b/spring/spring-memcached/README.md
@@ -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序列化与反序列化
-
-
-
-**springapplication.xml文件:**
-
-```xml
-
-
-
-
-
-
-
-
-
-
-```
-
-### 1.3 依赖说明
-
-除了spring的基本依赖外,需要导入xmemcached依赖包
-
-```xml
-
-
- com.googlecode.xmemcached
- xmemcached
- 2.4.5
-
-```
+# spring 整合 mecached(xml配置方式)
+
## 目录
+一、说明
+ 1.1 XMemcached客户端说明
+ 1.2 项目结构说明
+ 1.3 依赖说明
+二、spring 整合 memcached
+ 2.1 单机配置
+ 2.2 集群配置
+ 2.3 存储基本类型测试用例
+ 2.5 存储实体对象测试用例
+附:memcached 基本命令
+## 正文
-
-## 二、spring 整合 memcached
-
-#### 2.1 单机配置
-
-```xml
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.2 集群配置
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
- 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.3 存储基本类型测试用例
-
-xmemcached单机版本和集群版本注入的实例是相同的;
-
-```java
-/**
- * @author : heibaiying
- * @description : Memcached 操作基本对象
- */
-@RunWith(SpringRunner.class)
-@ContextConfiguration({"classpath:springApplication.xml"})
-public class MemSamples {
-
- @Autowired
- private MemcachedClient memcachedClient;
-
- @Test
- public void operate() throws InterruptedException, MemcachedException, TimeoutException {
- memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
- String value = memcachedClient.get("hello");
- System.out.println("hello=" + value);
- memcachedClient.delete("hello");
- value = memcachedClient.get("hello");
- System.out.println("hello=" + value);
- }
-}
-
-```
-
-#### 2.5 存储实体对象测试用例
-
-```java
-/**
- * @author : heibaiying
- * @description :Memcached 序列化与反序列化
- */
-@RunWith(SpringRunner.class)
-@ContextConfiguration({"classpath:springApplication.xml"})
-public class MemObjectSamples {
-
- @Autowired
- private MemcachedClient memcachedClient;
-
- @Test
- public void operate() throws InterruptedException, MemcachedException, TimeoutException {
- memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
- Programmer programmer = memcachedClient.get("programmer");
- System.out.println("hello ," + programmer.getName());
- memcachedClient.delete("programmer");
- programmer = memcachedClient.get("programmer");
- Assert.assertNull(programmer);
- }
-}
-
-```
-
-
-
-## 附:memcached 基本命令
-
-| 命令 | 格式 | 说明 |
-| --------------- | -------------------------------------------------- | ------------------------------------- |
-| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
-| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
-| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
-| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
-| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
-| 查询操作 get | get key | |
-| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
-| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
-| 删除 delete | delete key | 将数据打一个删除标记 |
-| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
-| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
+## 一、说明
+
+### 1.1 XMemcached客户端说明
+
+XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
+
+### 1.2 项目结构说明
+
+1. memcached的整合配置位于resources下的memcached文件夹下,其中集群配置用cluster开头。所有配置按照需要在springApplication.xml用import导入。
+2. 实体类Programmer.java用于测试memcached序列化与反序列化
+
+
+
+**springapplication.xml文件:**
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+### 1.3 依赖说明
+
+除了spring的基本依赖外,需要导入xmemcached依赖包
+
+```xml
+
+
+ com.googlecode.xmemcached
+ xmemcached
+ 2.4.5
+
+```
+
+
+
+## 二、spring 整合 memcached
+
+#### 2.1 单机配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.2 集群配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.3 存储基本类型测试用例
+
+xmemcached单机版本和集群版本注入的实例是相同的;
+
+```java
+/**
+ * @author : heibaiying
+ * @description : Memcached 操作基本对象
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration({"classpath:springApplication.xml"})
+public class MemSamples {
+
+ @Autowired
+ private MemcachedClient memcachedClient;
+
+ @Test
+ public void operate() throws InterruptedException, MemcachedException, TimeoutException {
+ memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
+ String value = memcachedClient.get("hello");
+ System.out.println("hello=" + value);
+ memcachedClient.delete("hello");
+ value = memcachedClient.get("hello");
+ System.out.println("hello=" + value);
+ }
+}
+
+```
+
+#### 2.5 存储实体对象测试用例
+
+```java
+/**
+ * @author : heibaiying
+ * @description :Memcached 序列化与反序列化
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration({"classpath:springApplication.xml"})
+public class MemObjectSamples {
+
+ @Autowired
+ private MemcachedClient memcachedClient;
+
+ @Test
+ public void operate() throws InterruptedException, MemcachedException, TimeoutException {
+ memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
+ Programmer programmer = memcachedClient.get("programmer");
+ System.out.println("hello ," + programmer.getName());
+ memcachedClient.delete("programmer");
+ programmer = memcachedClient.get("programmer");
+ Assert.assertNull(programmer);
+ }
+}
+
+```
+
+
+
+## 附:memcached 基本命令
+
+| 命令 | 格式 | 说明 |
+| --------------- | -------------------------------------------------- | ------------------------------------- |
+| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
+| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
+| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
+| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
+| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
+| 查询操作 get | get key | |
+| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
+| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
+| 删除 delete | delete key | 将数据打一个删除标记 |
+| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
+| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
| 清库 | flush_all | |
\ No newline at end of file
diff --git a/spring/spring-mongodb-annotation/README.md b/spring/spring-mongodb-annotation/README.md
index 6297afd..5b2e8d3 100644
--- a/spring/spring-mongodb-annotation/README.md
+++ b/spring/spring-mongodb-annotation/README.md
@@ -1,161 +1,171 @@
-# spring 整合 mongodb(注解方式)
-
-## 一、说明
-
-### 1.1 项目结构说明
-
-配置文件位于com.heibaiying.config下,项目以单元测试的方式进行测试。
-
-
+# spring 整合 mongodb(注解方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring mongodb
+ 2.1 新建配置文件及其映射类
+ 2.2 整合配置
+ 2.3 测试整合
+## 正文
-
-### 1.2 依赖说明
-
-除了spring的基本依赖外,需要导入mongodb整合依赖包
-
-```xml
-
-
- org.springframework.data
- spring-data-mongodb
- 2.1.3.RELEASE
-
-```
-
-
-
-## 二、spring mongodb
-
-#### 2.1 新建配置文件及其映射类
-
-```properties
-mongo.host=192.168.200.228
-mongo.port=27017
-# 数据库名称. 默认是'db'.
-mongo.dbname=database
-# 每个主机允许的连接数
-mongo.connectionsPerHost=10
-# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
-mongo.threadsAllowedToBlockForConnectionMultiplier=5
-# 连接超时的毫秒 0是默认值且无限大。
-mongo.connectTimeout=1000
-# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
-mongo.maxWaitTime=1500
-# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
-mongo.socketKeepAlive=true
-# 用于群集心跳的连接的套接字超时。
-mongo.socketTimeout=1500
-```
-
-```java
-/**
- * @author : heibaiying
- * @description : Mongo 配置属性
- */
-@Data
-@Configuration
-@PropertySource(value = "classpath:mongodb.properties")
-public class MongoProperty {
-
- @Value("${mongo.host}")
- private String host;
- @Value("${mongo.port}")
- private int port;
- @Value("${mongo.dbname}")
- private String dbname;
- @Value("${mongo.connectionsPerHost}")
- private int connectionsPerHost;
- @Value("${mongo.threadsAllowedToBlockForConnectionMultiplier}")
- private int multiplier;
- @Value("${mongo.connectTimeout}")
- private int connectTimeout;
- @Value("${mongo.maxWaitTime}")
- private int maxWaitTime;
- @Value("${mongo.socketKeepAlive}")
- private boolean socketKeepAlive;
- @Value("${mongo.socketTimeout}")
- private int socketTimeout;
-}
-```
-
-#### 2.2 整合配置
-
-```java
-/**
- * @author : heibaiying
- * @description : Mongo 配置类
- */
-
-@Configuration
-@ComponentScan(value = "com.heibaiying.*")
-public class MongoConfig {
-
- @Bean
- public MongoDbFactory mongoDbFactory(MongoProperty mongo) {
- MongoClientOptions options = MongoClientOptions.builder()
- .threadsAllowedToBlockForConnectionMultiplier(mongo.getMultiplier())
- .connectionsPerHost(mongo.getConnectionsPerHost())
- .connectTimeout(mongo.getConnectTimeout())
- .maxWaitTime(mongo.getMaxWaitTime())
- .socketTimeout(mongo.getSocketTimeout())
- .build();
- MongoClient client = new MongoClient(new ServerAddress(mongo.getHost(), mongo.getPort()), options);
- return new SimpleMongoDbFactory(client, mongo.getDbname());
- }
-
- @Bean
- public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) {
- return new MongoTemplate(mongoDbFactory);
- }
-}
-```
-
-#### 2.3 测试整合
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = MongoConfig.class)
-public class MongoDBTest {
-
- @Autowired
- private MongoTemplate mongoTemplate;
-
- @Test
- public void insert() {
- // 单条插入
- mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
- List programmers = new ArrayList();
- // 批量插入
- programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
- programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
- mongoTemplate.insert(programmers, Programmer.class);
- }
-
- // 条件查询
- @Test
- public void select() {
- Criteria criteria = new Criteria();
- criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
- Query query = new Query(criteria);
- Programmer one = mongoTemplate.findOne(query, Programmer.class);
- System.out.println(one);
- }
-
-
- // 更新数据
- @Test
- public void MUpdate() {
- UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
- System.out.println("更新记录数:" + updateResult.getModifiedCount());
- }
-
- // 删除指定数据
- @Test
- public void delete() {
- DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
- System.out.println("影响记录数:" + result.getDeletedCount());
- System.out.println("是否成功:" + result.wasAcknowledged());
- }
-}
-```
+## 一、说明
+
+### 1.1 项目结构说明
+
+配置文件位于com.heibaiying.config下,项目以单元测试的方式进行测试。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入mongodb整合依赖包
+
+```xml
+
+
+ org.springframework.data
+ spring-data-mongodb
+ 2.1.3.RELEASE
+
+```
+
+
+
+## 二、spring mongodb
+
+#### 2.1 新建配置文件及其映射类
+
+```properties
+mongo.host=192.168.200.228
+mongo.port=27017
+# 数据库名称. 默认是'db'.
+mongo.dbname=database
+# 每个主机允许的连接数
+mongo.connectionsPerHost=10
+# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
+mongo.threadsAllowedToBlockForConnectionMultiplier=5
+# 连接超时的毫秒 0是默认值且无限大。
+mongo.connectTimeout=1000
+# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
+mongo.maxWaitTime=1500
+# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
+mongo.socketKeepAlive=true
+# 用于群集心跳的连接的套接字超时。
+mongo.socketTimeout=1500
+```
+
+```java
+/**
+ * @author : heibaiying
+ * @description : Mongo 配置属性
+ */
+@Data
+@Configuration
+@PropertySource(value = "classpath:mongodb.properties")
+public class MongoProperty {
+
+ @Value("${mongo.host}")
+ private String host;
+ @Value("${mongo.port}")
+ private int port;
+ @Value("${mongo.dbname}")
+ private String dbname;
+ @Value("${mongo.connectionsPerHost}")
+ private int connectionsPerHost;
+ @Value("${mongo.threadsAllowedToBlockForConnectionMultiplier}")
+ private int multiplier;
+ @Value("${mongo.connectTimeout}")
+ private int connectTimeout;
+ @Value("${mongo.maxWaitTime}")
+ private int maxWaitTime;
+ @Value("${mongo.socketKeepAlive}")
+ private boolean socketKeepAlive;
+ @Value("${mongo.socketTimeout}")
+ private int socketTimeout;
+}
+```
+
+#### 2.2 整合配置
+
+```java
+/**
+ * @author : heibaiying
+ * @description : Mongo 配置类
+ */
+
+@Configuration
+@ComponentScan(value = "com.heibaiying.*")
+public class MongoConfig {
+
+ @Bean
+ public MongoDbFactory mongoDbFactory(MongoProperty mongo) {
+ MongoClientOptions options = MongoClientOptions.builder()
+ .threadsAllowedToBlockForConnectionMultiplier(mongo.getMultiplier())
+ .connectionsPerHost(mongo.getConnectionsPerHost())
+ .connectTimeout(mongo.getConnectTimeout())
+ .maxWaitTime(mongo.getMaxWaitTime())
+ .socketTimeout(mongo.getSocketTimeout())
+ .build();
+ MongoClient client = new MongoClient(new ServerAddress(mongo.getHost(), mongo.getPort()), options);
+ return new SimpleMongoDbFactory(client, mongo.getDbname());
+ }
+
+ @Bean
+ public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) {
+ return new MongoTemplate(mongoDbFactory);
+ }
+}
+```
+
+#### 2.3 测试整合
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = MongoConfig.class)
+public class MongoDBTest {
+
+ @Autowired
+ private MongoTemplate mongoTemplate;
+
+ @Test
+ public void insert() {
+ // 单条插入
+ mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
+ List programmers = new ArrayList();
+ // 批量插入
+ programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
+ programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
+ mongoTemplate.insert(programmers, Programmer.class);
+ }
+
+ // 条件查询
+ @Test
+ public void select() {
+ Criteria criteria = new Criteria();
+ criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
+ Query query = new Query(criteria);
+ Programmer one = mongoTemplate.findOne(query, Programmer.class);
+ System.out.println(one);
+ }
+
+
+ // 更新数据
+ @Test
+ public void MUpdate() {
+ UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
+ System.out.println("更新记录数:" + updateResult.getModifiedCount());
+ }
+
+ // 删除指定数据
+ @Test
+ public void delete() {
+ DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
+ System.out.println("影响记录数:" + result.getDeletedCount());
+ System.out.println("是否成功:" + result.wasAcknowledged());
+ }
+}
+```
diff --git a/spring/spring-mongodb/README.md b/spring/spring-mongodb/README.md
index 14257fe..d7e9a43 100644
--- a/spring/spring-mongodb/README.md
+++ b/spring/spring-mongodb/README.md
@@ -1,144 +1,154 @@
-# spring 整合 mongodb(xml配置方式)
-
-## 一、说明
-
-### 1.1 项目结构说明
-
-配置文件位于resources下,项目以单元测试的方式进行测试。
-
-
+# spring 整合 mongodb(xml配置方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring mongodb
+ 2.1 新建配置文件
+ 2.2 整合配置
+ 2.3 测试整合
+## 正文
-
-### 1.2 依赖说明
-
-除了spring的基本依赖外,需要导入mongodb整合依赖包
-
-```xml
-
-
- org.springframework.data
- spring-data-mongodb
- 2.1.3.RELEASE
-
-```
-
-
-
-## 二、spring mongodb
-
-#### 2.1 新建配置文件
-
-```properties
-mongo.host=192.168.200.228
-mongo.port=27017
-# 数据库名称. 默认是'db'.
-mongo.dbname=database
-# 每个主机允许的连接数
-mongo.connectionsPerHost=10
-# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
-mongo.threadsAllowedToBlockForConnectionMultiplier=5
-# 连接超时的毫秒 0是默认值且无限大。
-mongo.connectTimeout=1000
-# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
-mongo.maxWaitTime=1500
-# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
-mongo.socketKeepAlive=true
-# 用于群集心跳的连接的套接字超时。
-mongo.socketTimeout=1500
-```
-
-#### 2.2 整合配置
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.3 测试整合
-
-```java
-/**
- * @author : heibaiying
- * @description : MongoDB 查询
- */
-@RunWith(SpringRunner.class)
-@ContextConfiguration(locations = "classpath:mongodb.xml")
-public class MongoDBTest {
-
- @Autowired
- private MongoTemplate mongoTemplate;
-
- @Test
- public void insert() {
- // 单条插入
- mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
- List programmers = new ArrayList();
- // 批量插入
- programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
- programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
- mongoTemplate.insert(programmers, Programmer.class);
- }
-
- // 条件查询
- @Test
- public void select() {
- Criteria criteria = new Criteria();
- criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
- Query query = new Query(criteria);
- Programmer one = mongoTemplate.findOne(query, Programmer.class);
- System.out.println(one);
- }
-
-
- // 更新数据
- @Test
- public void MUpdate() {
- UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
- System.out.println("更新记录数:" + updateResult.getModifiedCount());
- }
-
- // 删除指定数据
- @Test
- public void delete() {
- DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
- System.out.println("影响记录数:" + result.getDeletedCount());
- System.out.println("是否成功:" + result.wasAcknowledged());
- }
-
-}
-```
+## 一、说明
+
+### 1.1 项目结构说明
+
+配置文件位于resources下,项目以单元测试的方式进行测试。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入mongodb整合依赖包
+
+```xml
+
+
+ org.springframework.data
+ spring-data-mongodb
+ 2.1.3.RELEASE
+
+```
+
+
+
+## 二、spring mongodb
+
+#### 2.1 新建配置文件
+
+```properties
+mongo.host=192.168.200.228
+mongo.port=27017
+# 数据库名称. 默认是'db'.
+mongo.dbname=database
+# 每个主机允许的连接数
+mongo.connectionsPerHost=10
+# 线程队列数,它和上面connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出异常
+mongo.threadsAllowedToBlockForConnectionMultiplier=5
+# 连接超时的毫秒 0是默认值且无限大。
+mongo.connectTimeout=1000
+# 最大等待连接的线程阻塞时间 默认是120000 ms (2 minutes).
+mongo.maxWaitTime=1500
+# 保持活动标志,控制是否有套接字保持活动超时 官方默认为true 且不建议禁用
+mongo.socketKeepAlive=true
+# 用于群集心跳的连接的套接字超时。
+mongo.socketTimeout=1500
+```
+
+#### 2.2 整合配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.3 测试整合
+
+```java
+/**
+ * @author : heibaiying
+ * @description : MongoDB 查询
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(locations = "classpath:mongodb.xml")
+public class MongoDBTest {
+
+ @Autowired
+ private MongoTemplate mongoTemplate;
+
+ @Test
+ public void insert() {
+ // 单条插入
+ mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
+ List programmers = new ArrayList();
+ // 批量插入
+ programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
+ programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
+ mongoTemplate.insert(programmers, Programmer.class);
+ }
+
+ // 条件查询
+ @Test
+ public void select() {
+ Criteria criteria = new Criteria();
+ criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
+ Query query = new Query(criteria);
+ Programmer one = mongoTemplate.findOne(query, Programmer.class);
+ System.out.println(one);
+ }
+
+
+ // 更新数据
+ @Test
+ public void MUpdate() {
+ UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
+ System.out.println("更新记录数:" + updateResult.getModifiedCount());
+ }
+
+ // 删除指定数据
+ @Test
+ public void delete() {
+ DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
+ System.out.println("影响记录数:" + result.getDeletedCount());
+ System.out.println("是否成功:" + result.wasAcknowledged());
+ }
+
+}
+```
diff --git a/spring/spring-mybatis-annotation/README.md b/spring/spring-mybatis-annotation/README.md
index 3b8aa26..38b8687 100644
--- a/spring/spring-mybatis-annotation/README.md
+++ b/spring/spring-mybatis-annotation/README.md
@@ -1,262 +1,274 @@
-# spring 整合 mybatis(注解方式)
+# spring 整合 mybatis(注解方式)
+
## 目录
+一、说明
+ 1.1 项目结构
+ 1.2 项目依赖
+二、spring 整合 mybatis
+ 2.1 在resources文件夹下新建数据库配置文件jdbc.properties及其映射类
+ 2.2 配置数据源和mybatis会话工厂、定义事务管理器
+ 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
+ 2.4 新建查询接口及其对应的mapper文件
+ 2.5 新建测试类进行测试
+## 正文
-## 一、说明
-
-#### 1.1 项目结构
-
-
-
-#### 1.2 项目依赖
-
-除了spring相关依赖外,还需要导入数据库驱动和对应的mybatis依赖包
-
-```xml
-
-
- org.springframework
- spring-jdbc
- ${spring-base-version}
-
-
- mysql
- mysql-connector-java
- 8.0.13
-
-
- com.oracle
- ojdbc6
- 11.2.0.3.0
-
-
-
- org.mybatis
- mybatis-spring
- 1.3.2
-
-
- org.mybatis
- mybatis
- 3.4.6
-
-```
-
-## 二、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中
-@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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.4 新建查询接口及其对应的mapper文件
-
-```java
-public interface MysqlDao {
-
- List get();
-}
-```
-
-```xml
-
-
-
-
-
-
-
-```
-
-```mysql
-public interface OracleDao {
-
- List queryById(long id);
-}
-
-```
-
-```xml
-
-
-
-
-
-
-
-```
-
-#### 2.5 新建测试类进行测试
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = {DatabaseConfig.class})
-public class MysqlDaoTest {
-
- @Autowired
- private MysqlDao mysqlDao;
-
- @Test
- public void get() {
- List 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 flows = oracleDao.queryById(217584603977429772L);
- if (flows != null) {
- for (Flow flow : flows) {
- System.out.println(flow.getId() + " " + flow.getPlugId());
- }
- }
- }
-}
-
-```
+## 一、说明
+
+#### 1.1 项目结构
+
+
+
+#### 1.2 项目依赖
+
+除了spring相关依赖外,还需要导入数据库驱动和对应的mybatis依赖包
+
+```xml
+
+
+ org.springframework
+ spring-jdbc
+ ${spring-base-version}
+
+
+ mysql
+ mysql-connector-java
+ 8.0.13
+
+
+ com.oracle
+ ojdbc6
+ 11.2.0.3.0
+
+
+
+ org.mybatis
+ mybatis-spring
+ 1.3.2
+
+
+ org.mybatis
+ mybatis
+ 3.4.6
+
+```
+
+## 二、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中
+@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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.4 新建查询接口及其对应的mapper文件
+
+```java
+public interface MysqlDao {
+
+ List get();
+}
+```
+
+```xml
+
+
+
+
+
+
+
+```
+
+```mysql
+public interface OracleDao {
+
+ List queryById(long id);
+}
+
+```
+
+```xml
+
+
+
+
+
+
+
+```
+
+#### 2.5 新建测试类进行测试
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = {DatabaseConfig.class})
+public class MysqlDaoTest {
+
+ @Autowired
+ private MysqlDao mysqlDao;
+
+ @Test
+ public void get() {
+ List 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 flows = oracleDao.queryById(217584603977429772L);
+ if (flows != null) {
+ for (Flow flow : flows) {
+ System.out.println(flow.getId() + " " + flow.getPlugId());
+ }
+ }
+ }
+}
+
+```
+
diff --git a/spring/spring-mybatis/README.md b/spring/spring-mybatis/README.md
index aa454a0..de07c34 100644
--- a/spring/spring-mybatis/README.md
+++ b/spring/spring-mybatis/README.md
@@ -1,236 +1,248 @@
-# spring 整合 mybatis(xml配置方式)
-
-## 一、说明
-
-#### 1.1 项目结构
-
-
-
-#### 1.2 项目依赖
-
-除了spring相关依赖外,还需要导入数据库驱动和对应的mybatis依赖包
-
-```xml
-
-
- org.springframework
- spring-jdbc
- ${spring-base-version}
-
-
- mysql
- mysql-connector-java
- 8.0.13
-
-
- com.oracle
- ojdbc6
- 11.2.0.3.0
-
-
-
- org.mybatis
- mybatis-spring
- 1.3.2
-
-
- org.mybatis
- mybatis
- 3.4.6
-
-```
-
-## 二、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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+# spring 整合 mybatis(xml配置方式)
+
## 目录
+一、说明
+ 1.1 项目结构
+ 1.2 项目依赖
+二、spring 整合 mybatis
+ 2.1 在resources文件夹下新建数据库配置文件jdbc.properties
+ 2.2 配置数据源和mybatis会话工厂、定义事务管理器
+ 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
+ 2.4 新建查询接口及其对应的mapper文件
+ 2.5 新建测试类进行测试
+## 正文
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.4 新建查询接口及其对应的mapper文件
-
-```java
-public interface MysqlDao {
-
- List get();
-}
-```
-
-```xml
-
-
-
-
-
-
-
-```
-
-```mysql
-public interface OracleDao {
-
- List queryById(long id);
-}
-
-```
-
-```xml
-
-
-
-
-
-
-
-```
-
-#### 2.5 新建测试类进行测试
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration({"classpath:springApplication.xml"})
-public class MysqlDaoTest {
-
- @Autowired
- private MysqlDao mysqlDao;
-
- @Test
- public void get() {
- List 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 flows = oracleDao.get();
- if (flows != null) {
- for (Flow flow : flows) {
- System.out.println(flow.getId() + " " + flow.getPlugId());
- }
- }
- }
-}
-
-```
-
+## 一、说明
+
+#### 1.1 项目结构
+
+
+
+#### 1.2 项目依赖
+
+除了spring相关依赖外,还需要导入数据库驱动和对应的mybatis依赖包
+
+```xml
+
+
+ org.springframework
+ spring-jdbc
+ ${spring-base-version}
+
+
+ mysql
+ mysql-connector-java
+ 8.0.13
+
+
+ com.oracle
+ ojdbc6
+ 11.2.0.3.0
+
+
+
+ org.mybatis
+ mybatis-spring
+ 1.3.2
+
+
+ org.mybatis
+ mybatis
+ 3.4.6
+
+```
+
+## 二、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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.3 新建mybtais配置文件,按照需求配置额外参数, 更多settings配置项可以参考[官方文档](http://www.mybatis.org/mybatis-3/zh/configuration.html)
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.4 新建查询接口及其对应的mapper文件
+
+```java
+public interface MysqlDao {
+
+ List get();
+}
+```
+
+```xml
+
+
+
+
+
+
+
+```
+
+```mysql
+public interface OracleDao {
+
+ List queryById(long id);
+}
+
+```
+
+```xml
+
+
+
+
+
+
+
+```
+
+#### 2.5 新建测试类进行测试
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration({"classpath:springApplication.xml"})
+public class MysqlDaoTest {
+
+ @Autowired
+ private MysqlDao mysqlDao;
+
+ @Test
+ public void get() {
+ List 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 flows = oracleDao.get();
+ if (flows != null) {
+ for (Flow flow : flows) {
+ System.out.println(flow.getId() + " " + flow.getPlugId());
+ }
+ }
+ }
+}
+
+```
+
diff --git a/spring/spring-rabbitmq-annotation/README.md b/spring/spring-rabbitmq-annotation/README.md
index 3752593..3c1e0e3 100644
--- a/spring/spring-rabbitmq-annotation/README.md
+++ b/spring/spring-rabbitmq-annotation/README.md
@@ -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),里面有详细的配图说明。
-
-
-
-
-
-
-
-### 1.2 依赖说明
-
-除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
-
-```xml
-
-
- org.springframework.amqp
- spring-rabbit
- 2.1.2.RELEASE
-
-
-
- com.fasterxml.jackson.core
- jackson-databind
- 2.9.8
-
-```
-
-
-
-## 二、spring rabbit 基本配置
-
-#### 2.1 基本配置属性及其映射类
-
-```properties
-rabbitmq.addresses=localhost:5672
-rabbitmq.username=guest
-rabbitmq.password=guest
-# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
-rabbitmq.virtualhost=/
-```
-
-```java
-/**
- * @author : heibaiying
- * @description : rabbit 属性配置
- */
-@Data
-@PropertySource(value = "classpath:rabbitmq.properties")
-@Configuration
-public class RabbitProperty {
-
-
- @Value("${rabbitmq.addresses}")
- private String addresses;
-
- @Value("${rabbitmq.username}")
- private String username;
-
- @Value("${rabbitmq.password}")
- private String password;
-
- @Value("${rabbitmq.virtualhost}")
- private String virtualhost;
-}
-```
-
-#### 2.2 创建连接工厂、管理器
-
-```java
-
-@Configuration
-@ComponentScan("com.heibaiying.rabbit.config")
-public class RabbitBaseConfig {
-
- /**
- * 声明连接工厂
- */
- @Bean
- public ConnectionFactory connectionFactory(RabbitProperty property) {
- CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
- connectionFactory.setAddresses(property.getAddresses());
- connectionFactory.setUsername(property.getUsername());
- connectionFactory.setPassword(property.getPassword());
- connectionFactory.setVirtualHost(property.getVirtualhost());
- return connectionFactory;
- }
-
- /**
- * 创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
- * auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为true。
- */
- @Bean
- public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
- RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
- rabbitAdmin.setAutoStartup(true);
- return rabbitAdmin;
- }
-
-
- @Bean
- public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
- RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
- return rabbitTemplate;
- }
-}
-```
-
-
-
-## 三、简单消费的发送
-
-#### 3.1 声明交换机、队列、绑定关系和消费者监听器
-
-```java
-import com.rabbitmq.client.Channel;
-import org.springframework.amqp.core.*;
-import org.springframework.amqp.rabbit.connection.ConnectionFactory;
-import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
-import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * @author : heibaiying
- * @description : 声明队列、交换机、绑定关系、和队列消息监听
- */
-
-@Configuration
-public class RabbitBaseAnnotation {
-
- @Bean
- public TopicExchange exchange() {
- // 创建一个持久化的交换机
- return new TopicExchange("topic01", true, false);
- }
-
- @Bean
- public Queue firstQueue() {
- // 创建一个持久化的队列1
- return new Queue("FirstQueue", true);
- }
-
- @Bean
- public Queue secondQueue() {
- // 创建一个持久化的队列2
- return new Queue("SecondQueue", true);
- }
-
- /**
- * BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
- * 这里我们声明三个绑定关系用于测试topic这种类型交换器
- */
- @Bean
- public Binding orange() {
- return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
- }
-
- @Bean
- public Binding rabbit() {
- return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
- }
-
- @Bean
- public Binding lazy() {
- return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
- }
-
-
- /*创建队列1消费者监听*/
- @Bean
- public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
-
- SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
- // 设置监听的队列
- container.setQueues(firstQueue());
- // 指定要创建的并发使用者数。
- container.setConcurrentConsumers(1);
- // 设置消费者数量的上限
- container.setMaxConcurrentConsumers(5);
- // 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
- container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
- container.setMessageListener(new ChannelAwareMessageListener() {
- @Override
- public void onMessage(Message message, Channel channel) throws Exception {
- // 可以在这个地方得到消息额外属性
- MessageProperties properties = message.getMessageProperties();
- //得到消息体内容
- byte[] body = message.getBody();
- System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
- /*
- * DeliveryTag 是一个单调递增的整数
- * 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
- */
- channel.basicAck(properties.getDeliveryTag(), false);
- }
- });
- return container;
- }
-
-
- /*创建队列2消费者监听*/
- @Bean
- public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
-
- SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
- container.setQueues(secondQueue());
- container.setMessageListener(new ChannelAwareMessageListener() {
- @Override
- public void onMessage(Message message, Channel channel) throws Exception {
- byte[] body = message.getBody();
- System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
- }
- });
- return container;
- }
-
-}
-```
-
-#### 3.2 测试简单消息的发送
-
-```java
-/**
- * @author : heibaiying
- * @description : 传输简单字符串
- */
-
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = RabbitBaseConfig.class)
-public class RabbitTest {
-
- @Autowired
- private RabbitTemplate rabbitTemplate;
-
- @Test
- public void sendMessage() {
- MessageProperties properties = new MessageProperties();
-
- String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
- Message message1 = new Message(allReceived.getBytes(), properties);
- rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
-
- String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
- Message message2 = new Message(firstReceived.getBytes(), properties);
- rabbitTemplate.send("topic01", "quick.orange.fox", message2);
-
- String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
- Message message3 = new Message(secondReceived.getBytes(), properties);
- rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
-
- String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
- Message message4 = new Message(notReceived.getBytes(), properties);
- rabbitTemplate.send("topic01", "quick.brown.fox", message4);
- }
-}
-```
-
-```java
-结果:
- SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
- FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
- FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
- SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
-```
-
-
-
-## 四、传输对象
-
-#### 4.1 创建消息的委托处理器
-
-这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
-
-```java
-/**
- * @author : heibaiying
- * @description :消息委派处理类
- */
-public class MessageDelegate {
-
- public void handleMessage(ProductManager manager) {
- System.out.println("收到一个产品经理" + manager);
- }
-
- public void handleMessage(Programmer programmer) {
- System.out.println("收到一个程序员" + programmer);
- }
-
-}
-```
-
-#### 4.2 声明交换机、队列、绑定关系和消费者监听器
-
-```java
-/**
- * @author : heibaiying
- * @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
- */
-
-@Configuration
-public class RabbitObjectAnnotation {
-
- @Bean
- public DirectExchange objectTopic() {
- // 创建一个持久化的交换机
- return new DirectExchange("objectTopic", true, false);
- }
-
- @Bean
- public Queue objectQueue() {
- // 创建一个持久化的队列
- return new Queue("objectQueue", true);
- }
-
- @Bean
- public Binding binding() {
- return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
- }
-
-
- /*创建队列消费者监听*/
- @Bean
- public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
-
- SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
- // 设置监听的队列
- container.setQueues(objectQueue());
- // 将监听到的消息委派给实际的处理类
- MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
- // 指定由哪个方法来处理消息 默认就是handleMessage
- adapter.setDefaultListenerMethod("handleMessage");
-
- // 消息转换
- Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
- DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
-
- Map> idClassMapping = new HashMap<>();
- // 针对不同的消息体调用不同的重载方法
- idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
- idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
-
- javaTypeMapper.setIdClassMapping(idClassMapping);
-
- jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
- adapter.setMessageConverter(jackson2JsonMessageConverter);
- container.setMessageListener(adapter);
- return container;
- }
-
-}
-```
-
-#### 4.3 测试对象消息的发送
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = RabbitBaseConfig.class)
-public class RabbitSendObjectTest {
-
- @Autowired
- private RabbitTemplate rabbitTemplate;
-
- @Test
- public void sendProgrammer() throws JsonProcessingException {
- MessageProperties messageProperties = new MessageProperties();
- //必须设置 contentType为 application/json
- messageProperties.setContentType("application/json");
- // 必须指定类型
- messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
- Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
- // 序列化与反序列化都使用的Jackson
- ObjectMapper mapper = new ObjectMapper();
- String programmerJson = mapper.writeValueAsString(programmer);
- Message message = new Message(programmerJson.getBytes(), messageProperties);
- rabbitTemplate.send("objectTopic", "object", message);
- }
-
-
- @Test
- public void sendProductManager() throws JsonProcessingException {
- MessageProperties messageProperties = new MessageProperties();
- messageProperties.setContentType("application/json");
- messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
- ProductManager manager = new ProductManager("xiaohong", 21, new Date());
- ObjectMapper mapper = new ObjectMapper();
- String managerJson = mapper.writeValueAsString(manager);
- Message message = new Message(managerJson.getBytes(), messageProperties);
- rabbitTemplate.send("objectTopic", "object", message);
- }
-}
+# spring 整合 rabbitmq(注解方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring rabbit 基本配置
+ 2.1 基本配置属性及其映射类
+ 2.2 创建连接工厂、管理器
+三、简单消费的发送
+ 3.1 声明交换机、队列、绑定关系和消费者监听器
+ 3.2 测试简单消息的发送
+四、传输对象
+ 4.1 创建消息的委托处理器
+ 4.2 声明交换机、队列、绑定关系和消费者监听器
+ 4.3 测试对象消息的发送
+## 正文
+
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
+
+2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
+
+3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
+
+ 注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
+
+
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
+
+```xml
+
+
+ org.springframework.amqp
+ spring-rabbit
+ 2.1.2.RELEASE
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.9.8
+
+```
+
+
+
+## 二、spring rabbit 基本配置
+
+#### 2.1 基本配置属性及其映射类
+
+```properties
+rabbitmq.addresses=localhost:5672
+rabbitmq.username=guest
+rabbitmq.password=guest
+# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
+rabbitmq.virtualhost=/
+```
+
+```java
+/**
+ * @author : heibaiying
+ * @description : rabbit 属性配置
+ */
+@Data
+@PropertySource(value = "classpath:rabbitmq.properties")
+@Configuration
+public class RabbitProperty {
+
+
+ @Value("${rabbitmq.addresses}")
+ private String addresses;
+
+ @Value("${rabbitmq.username}")
+ private String username;
+
+ @Value("${rabbitmq.password}")
+ private String password;
+
+ @Value("${rabbitmq.virtualhost}")
+ private String virtualhost;
+}
+```
+
+#### 2.2 创建连接工厂、管理器
+
+```java
+
+@Configuration
+@ComponentScan("com.heibaiying.rabbit.config")
+public class RabbitBaseConfig {
+
+ /**
+ * 声明连接工厂
+ */
+ @Bean
+ public ConnectionFactory connectionFactory(RabbitProperty property) {
+ CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
+ connectionFactory.setAddresses(property.getAddresses());
+ connectionFactory.setUsername(property.getUsername());
+ connectionFactory.setPassword(property.getPassword());
+ connectionFactory.setVirtualHost(property.getVirtualhost());
+ return connectionFactory;
+ }
+
+ /**
+ * 创建一个管理器(org.springframework.amqp.rabbit.core.RabbitAdmin),用于管理交换,队列和绑定。
+ * auto-startup 指定是否自动声明上下文中的队列,交换和绑定, 默认值为true。
+ */
+ @Bean
+ public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
+ RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
+ rabbitAdmin.setAutoStartup(true);
+ return rabbitAdmin;
+ }
+
+
+ @Bean
+ public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
+ RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
+ return rabbitTemplate;
+ }
+}
+```
+
+
+
+## 三、简单消费的发送
+
+#### 3.1 声明交换机、队列、绑定关系和消费者监听器
+
+```java
+import com.rabbitmq.client.Channel;
+import org.springframework.amqp.core.*;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
+import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author : heibaiying
+ * @description : 声明队列、交换机、绑定关系、和队列消息监听
+ */
+
+@Configuration
+public class RabbitBaseAnnotation {
+
+ @Bean
+ public TopicExchange exchange() {
+ // 创建一个持久化的交换机
+ return new TopicExchange("topic01", true, false);
+ }
+
+ @Bean
+ public Queue firstQueue() {
+ // 创建一个持久化的队列1
+ return new Queue("FirstQueue", true);
+ }
+
+ @Bean
+ public Queue secondQueue() {
+ // 创建一个持久化的队列2
+ return new Queue("SecondQueue", true);
+ }
+
+ /**
+ * BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
+ * 这里我们声明三个绑定关系用于测试topic这种类型交换器
+ */
+ @Bean
+ public Binding orange() {
+ return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
+ }
+
+ @Bean
+ public Binding rabbit() {
+ return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
+ }
+
+ @Bean
+ public Binding lazy() {
+ return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
+ }
+
+
+ /*创建队列1消费者监听*/
+ @Bean
+ public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
+
+ SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
+ // 设置监听的队列
+ container.setQueues(firstQueue());
+ // 指定要创建的并发使用者数。
+ container.setConcurrentConsumers(1);
+ // 设置消费者数量的上限
+ container.setMaxConcurrentConsumers(5);
+ // 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
+ container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
+ container.setMessageListener(new ChannelAwareMessageListener() {
+ @Override
+ public void onMessage(Message message, Channel channel) throws Exception {
+ // 可以在这个地方得到消息额外属性
+ MessageProperties properties = message.getMessageProperties();
+ //得到消息体内容
+ byte[] body = message.getBody();
+ System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
+ /*
+ * DeliveryTag 是一个单调递增的整数
+ * 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
+ */
+ channel.basicAck(properties.getDeliveryTag(), false);
+ }
+ });
+ return container;
+ }
+
+
+ /*创建队列2消费者监听*/
+ @Bean
+ public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
+
+ SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
+ container.setQueues(secondQueue());
+ container.setMessageListener(new ChannelAwareMessageListener() {
+ @Override
+ public void onMessage(Message message, Channel channel) throws Exception {
+ byte[] body = message.getBody();
+ System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
+ }
+ });
+ return container;
+ }
+
+}
+```
+
+#### 3.2 测试简单消息的发送
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 传输简单字符串
+ */
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = RabbitBaseConfig.class)
+public class RabbitTest {
+
+ @Autowired
+ private RabbitTemplate rabbitTemplate;
+
+ @Test
+ public void sendMessage() {
+ MessageProperties properties = new MessageProperties();
+
+ String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
+ Message message1 = new Message(allReceived.getBytes(), properties);
+ rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
+
+ String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
+ Message message2 = new Message(firstReceived.getBytes(), properties);
+ rabbitTemplate.send("topic01", "quick.orange.fox", message2);
+
+ String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
+ Message message3 = new Message(secondReceived.getBytes(), properties);
+ rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
+
+ String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
+ Message message4 = new Message(notReceived.getBytes(), properties);
+ rabbitTemplate.send("topic01", "quick.brown.fox", message4);
+ }
+}
+```
+
+```java
+结果:
+ SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
+ FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
+ FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
+ SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
+```
+
+
+
+## 四、传输对象
+
+#### 4.1 创建消息的委托处理器
+
+这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
+
+```java
+/**
+ * @author : heibaiying
+ * @description :消息委派处理类
+ */
+public class MessageDelegate {
+
+ public void handleMessage(ProductManager manager) {
+ System.out.println("收到一个产品经理" + manager);
+ }
+
+ public void handleMessage(Programmer programmer) {
+ System.out.println("收到一个程序员" + programmer);
+ }
+
+}
+```
+
+#### 4.2 声明交换机、队列、绑定关系和消费者监听器
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
+ */
+
+@Configuration
+public class RabbitObjectAnnotation {
+
+ @Bean
+ public DirectExchange objectTopic() {
+ // 创建一个持久化的交换机
+ return new DirectExchange("objectTopic", true, false);
+ }
+
+ @Bean
+ public Queue objectQueue() {
+ // 创建一个持久化的队列
+ return new Queue("objectQueue", true);
+ }
+
+ @Bean
+ public Binding binding() {
+ return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
+ }
+
+
+ /*创建队列消费者监听*/
+ @Bean
+ public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
+
+ SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
+ // 设置监听的队列
+ container.setQueues(objectQueue());
+ // 将监听到的消息委派给实际的处理类
+ MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
+ // 指定由哪个方法来处理消息 默认就是handleMessage
+ adapter.setDefaultListenerMethod("handleMessage");
+
+ // 消息转换
+ Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
+ DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
+
+ Map> idClassMapping = new HashMap<>();
+ // 针对不同的消息体调用不同的重载方法
+ idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
+ idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
+
+ javaTypeMapper.setIdClassMapping(idClassMapping);
+
+ jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
+ adapter.setMessageConverter(jackson2JsonMessageConverter);
+ container.setMessageListener(adapter);
+ return container;
+ }
+
+}
+```
+
+#### 4.3 测试对象消息的发送
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = RabbitBaseConfig.class)
+public class RabbitSendObjectTest {
+
+ @Autowired
+ private RabbitTemplate rabbitTemplate;
+
+ @Test
+ public void sendProgrammer() throws JsonProcessingException {
+ MessageProperties messageProperties = new MessageProperties();
+ //必须设置 contentType为 application/json
+ messageProperties.setContentType("application/json");
+ // 必须指定类型
+ messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
+ Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
+ // 序列化与反序列化都使用的Jackson
+ ObjectMapper mapper = new ObjectMapper();
+ String programmerJson = mapper.writeValueAsString(programmer);
+ Message message = new Message(programmerJson.getBytes(), messageProperties);
+ rabbitTemplate.send("objectTopic", "object", message);
+ }
+
+
+ @Test
+ public void sendProductManager() throws JsonProcessingException {
+ MessageProperties messageProperties = new MessageProperties();
+ messageProperties.setContentType("application/json");
+ messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
+ ProductManager manager = new ProductManager("xiaohong", 21, new Date());
+ ObjectMapper mapper = new ObjectMapper();
+ String managerJson = mapper.writeValueAsString(manager);
+ Message message = new Message(managerJson.getBytes(), messageProperties);
+ rabbitTemplate.send("objectTopic", "object", message);
+ }
+}
```
\ No newline at end of file
diff --git a/spring/spring-rabbitmq/README.md b/spring/spring-rabbitmq/README.md
index 55235e7..a3f3b65 100644
--- a/spring/spring-rabbitmq/README.md
+++ b/spring/spring-rabbitmq/README.md
@@ -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),里面有详细的配图说明。
-
-
-
-
-
-
-
-### 1.2 依赖说明
-
-除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
-
-```xml
-
-
- org.springframework.amqp
- spring-rabbit
- 2.1.2.RELEASE
-
-
-
- com.fasterxml.jackson.core
- jackson-databind
- 2.9.8
-
-```
-
-
-
-## 二、spring rabbit 基本配置
-
-```properties
-rabbitmq.addresses=localhost:5672
-rabbitmq.username=guest
-rabbitmq.password=guest
-# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
-rabbitmq.virtualhost=/
-```
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-
-
-## 三、简单消费的发送
-
-#### 3.1 声明交换机、队列、绑定关系和消费者监听器
-
-```java
-import com.rabbitmq.client.Channel;
-import org.springframework.amqp.core.*;
-import org.springframework.amqp.rabbit.connection.ConnectionFactory;
-import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
-import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * @author : heibaiying
- * @description : 声明队列、交换机、绑定关系、和队列消息监听
- */
-
-@Configuration
-public class RabbitBaseAnnotation {
-
- @Bean
- public TopicExchange exchange() {
- // 创建一个持久化的交换机
- return new TopicExchange("topic01", true, false);
- }
-
- @Bean
- public Queue firstQueue() {
- // 创建一个持久化的队列1
- return new Queue("FirstQueue", true);
- }
-
- @Bean
- public Queue secondQueue() {
- // 创建一个持久化的队列2
- return new Queue("SecondQueue", true);
- }
-
- /**
- * BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
- * 这里我们声明三个绑定关系用于测试topic这种类型交换器
- */
- @Bean
- public Binding orange() {
- return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
- }
-
- @Bean
- public Binding rabbit() {
- return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
- }
-
- @Bean
- public Binding lazy() {
- return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
- }
-
-
- /*创建队列1消费者监听*/
- @Bean
- public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
-
- SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
- // 设置监听的队列
- container.setQueues(firstQueue());
- // 指定要创建的并发使用者数。
- container.setConcurrentConsumers(1);
- // 设置消费者数量的上限
- container.setMaxConcurrentConsumers(5);
- // 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
- container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
- container.setMessageListener(new ChannelAwareMessageListener() {
- @Override
- public void onMessage(Message message, Channel channel) throws Exception {
- // 可以在这个地方得到消息额外属性
- MessageProperties properties = message.getMessageProperties();
- //得到消息体内容
- byte[] body = message.getBody();
- System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
- /*
- * DeliveryTag 是一个单调递增的整数
- * 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
- */
- channel.basicAck(properties.getDeliveryTag(), false);
- }
- });
- return container;
- }
-
-
- /*创建队列2消费者监听*/
- @Bean
- public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
-
- SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
- container.setQueues(secondQueue());
- container.setMessageListener(new ChannelAwareMessageListener() {
- @Override
- public void onMessage(Message message, Channel channel) throws Exception {
- byte[] body = message.getBody();
- System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
- }
- });
- return container;
- }
-
-}
-```
-
-#### 3.2 测试简单消息的发送
-
-```java
-/**
- * @author : heibaiying
- * @description : 传输简单字符串
- */
-
-@RunWith(SpringRunner.class)
-@ContextConfiguration(locations = "classpath:rabbitmq.xml")
-public class RabbitTest {
-
- @Autowired
- private RabbitTemplate rabbitTemplate;
-
- @Test
- public void sendMessage() {
- MessageProperties properties = new MessageProperties();
-
- String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
- Message message1 = new Message(allReceived.getBytes(), properties);
- rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
-
- String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
- Message message2 = new Message(firstReceived.getBytes(), properties);
- rabbitTemplate.send("topic01", "quick.orange.fox", message2);
-
- String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
- Message message3 = new Message(secondReceived.getBytes(), properties);
- rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
-
- String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
- Message message4 = new Message(notReceived.getBytes(), properties);
- rabbitTemplate.send("topic01", "quick.brown.fox", message4);
- }
-}
-```
-
-```java
-结果:
- SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
- FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
- FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
- SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
-```
-
-
-
-## 四、传输对象
-
-#### 4.1 创建消息的委托处理器
-
-这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
-
-```java
-/**
- * @author : heibaiying
- * @description :消息委派处理类
- */
-public class MessageDelegate {
-
- public void handleMessage(ProductManager manager) {
- System.out.println("收到一个产品经理" + manager);
- }
-
- public void handleMessage(Programmer programmer) {
- System.out.println("收到一个程序员" + programmer);
- }
-
-}
-```
-
-#### 4.2 声明交换机、队列、绑定关系和消费者监听器
-
-```java
-/**
- * @author : heibaiying
- * @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
- */
-
-@Configuration
-public class RabbitObjectAnnotation {
-
- @Bean
- public DirectExchange objectTopic() {
- // 创建一个持久化的交换机
- return new DirectExchange("objectTopic", true, false);
- }
-
- @Bean
- public Queue objectQueue() {
- // 创建一个持久化的队列
- return new Queue("objectQueue", true);
- }
-
- @Bean
- public Binding binding() {
- return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
- }
-
-
- /*创建队列消费者监听*/
- @Bean
- public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
-
- SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
- // 设置监听的队列
- container.setQueues(objectQueue());
- // 将监听到的消息委派给实际的处理类
- MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
- // 指定由哪个方法来处理消息 默认就是handleMessage
- adapter.setDefaultListenerMethod("handleMessage");
-
- // 消息转换
- Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
- DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
-
- Map> idClassMapping = new HashMap<>();
- // 针对不同的消息体调用不同的重载方法
- idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
- idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
-
- javaTypeMapper.setIdClassMapping(idClassMapping);
-
- jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
- adapter.setMessageConverter(jackson2JsonMessageConverter);
- container.setMessageListener(adapter);
- return container;
- }
-
-}
-```
-
-#### 4.3 测试对象消息的发送
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration(locations = "classpath:rabbitmq.xml")
-public class RabbitSendObjectTest {
-
- @Autowired
- private RabbitTemplate rabbitTemplate;
-
- @Test
- public void sendProgrammer() throws JsonProcessingException {
- MessageProperties messageProperties = new MessageProperties();
- //必须设置 contentType为 application/json
- messageProperties.setContentType("application/json");
- // 必须指定类型
- messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
- Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
- // 序列化与反序列化都使用的Jackson
- ObjectMapper mapper = new ObjectMapper();
- String programmerJson = mapper.writeValueAsString(programmer);
- Message message = new Message(programmerJson.getBytes(), messageProperties);
- rabbitTemplate.send("objectTopic", "object", message);
- }
-
-
- @Test
- public void sendProductManager() throws JsonProcessingException {
- MessageProperties messageProperties = new MessageProperties();
- messageProperties.setContentType("application/json");
- messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
- ProductManager manager = new ProductManager("xiaohong", 21, new Date());
- ObjectMapper mapper = new ObjectMapper();
- String managerJson = mapper.writeValueAsString(manager);
- Message message = new Message(managerJson.getBytes(), messageProperties);
- rabbitTemplate.send("objectTopic", "object", message);
- }
-}
+# spring 整合 rabbitmq(xml配置方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring rabbit 基本配置
+三、简单消费的发送
+ 3.1 声明交换机、队列、绑定关系和消费者监听器
+ 3.2 测试简单消息的发送
+四、传输对象
+ 4.1 创建消息的委托处理器
+ 4.2 声明交换机、队列、绑定关系和消费者监听器
+ 4.3 测试对象消息的发送
+## 正文
+
+
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 本用例关于rabbitmq的整合提供**简单消息发送**和**对象消费发送**两种情况下的sample。
+
+2. rabbitBaseAnnotation.java中声明了topic类型的交换机、持久化队列、及其绑定关系,用于测试说明topic交换机路由键的绑定规则。
+
+3. rabbitObjectAnnotation.java中声明了direct类型的交换机,持久化队列,及其绑定关系,用于示例对象消息的传输。
+
+ 注:关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
+
+
+
+
+
+
+
+### 1.2 依赖说明
+
+除了spring的基本依赖外,需要导入spring rabbitmq 整合依赖
+
+```xml
+
+
+ org.springframework.amqp
+ spring-rabbit
+ 2.1.2.RELEASE
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.9.8
+
+```
+
+
+
+## 二、spring rabbit 基本配置
+
+```properties
+rabbitmq.addresses=localhost:5672
+rabbitmq.username=guest
+rabbitmq.password=guest
+# 虚拟主机,可以类比为命名空间 默认为/ 必须先用图形界面或者管控台添加 程序不会自动创建且会抛出异常
+rabbitmq.virtualhost=/
+```
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+
+## 三、简单消费的发送
+
+#### 3.1 声明交换机、队列、绑定关系和消费者监听器
+
+```java
+import com.rabbitmq.client.Channel;
+import org.springframework.amqp.core.*;
+import org.springframework.amqp.rabbit.connection.ConnectionFactory;
+import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
+import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author : heibaiying
+ * @description : 声明队列、交换机、绑定关系、和队列消息监听
+ */
+
+@Configuration
+public class RabbitBaseAnnotation {
+
+ @Bean
+ public TopicExchange exchange() {
+ // 创建一个持久化的交换机
+ return new TopicExchange("topic01", true, false);
+ }
+
+ @Bean
+ public Queue firstQueue() {
+ // 创建一个持久化的队列1
+ return new Queue("FirstQueue", true);
+ }
+
+ @Bean
+ public Queue secondQueue() {
+ // 创建一个持久化的队列2
+ return new Queue("SecondQueue", true);
+ }
+
+ /**
+ * BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“*”用于匹配一个单词,“#”用于匹配零个或者多个单词
+ * 这里我们声明三个绑定关系用于测试topic这种类型交换器
+ */
+ @Bean
+ public Binding orange() {
+ return BindingBuilder.bind(firstQueue()).to(exchange()).with("*.orange.*");
+ }
+
+ @Bean
+ public Binding rabbit() {
+ return BindingBuilder.bind(secondQueue()).to(exchange()).with("*.*.rabbit");
+ }
+
+ @Bean
+ public Binding lazy() {
+ return BindingBuilder.bind(secondQueue()).to(exchange()).with("lazy.#");
+ }
+
+
+ /*创建队列1消费者监听*/
+ @Bean
+ public SimpleMessageListenerContainer firstQueueLister(ConnectionFactory connectionFactory) {
+
+ SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
+ // 设置监听的队列
+ container.setQueues(firstQueue());
+ // 指定要创建的并发使用者数。
+ container.setConcurrentConsumers(1);
+ // 设置消费者数量的上限
+ container.setMaxConcurrentConsumers(5);
+ // 设置是否自动签收消费 为保证消费被成功消费,建议手工签收
+ container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
+ container.setMessageListener(new ChannelAwareMessageListener() {
+ @Override
+ public void onMessage(Message message, Channel channel) throws Exception {
+ // 可以在这个地方得到消息额外属性
+ MessageProperties properties = message.getMessageProperties();
+ //得到消息体内容
+ byte[] body = message.getBody();
+ System.out.println(firstQueue().getName() + "收到消息:" + new String(body));
+ /*
+ * DeliveryTag 是一个单调递增的整数
+ * 第二个参数 代表是否一次签收多条,如果设置为true,则所有DeliveryTag小于该DeliveryTag的消息都会被签收
+ */
+ channel.basicAck(properties.getDeliveryTag(), false);
+ }
+ });
+ return container;
+ }
+
+
+ /*创建队列2消费者监听*/
+ @Bean
+ public SimpleMessageListenerContainer secondQueueLister(ConnectionFactory connectionFactory) {
+
+ SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
+ container.setQueues(secondQueue());
+ container.setMessageListener(new ChannelAwareMessageListener() {
+ @Override
+ public void onMessage(Message message, Channel channel) throws Exception {
+ byte[] body = message.getBody();
+ System.out.println(secondQueue().getName() + "收到消息:" + new String(body));
+ }
+ });
+ return container;
+ }
+
+}
+```
+
+#### 3.2 测试简单消息的发送
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 传输简单字符串
+ */
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(locations = "classpath:rabbitmq.xml")
+public class RabbitTest {
+
+ @Autowired
+ private RabbitTemplate rabbitTemplate;
+
+ @Test
+ public void sendMessage() {
+ MessageProperties properties = new MessageProperties();
+
+ String allReceived = "我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到";
+ Message message1 = new Message(allReceived.getBytes(), properties);
+ rabbitTemplate.send("topic01", "quick.orange.rabbit", message1);
+
+ String firstReceived = "我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到";
+ Message message2 = new Message(firstReceived.getBytes(), properties);
+ rabbitTemplate.send("topic01", "quick.orange.fox", message2);
+
+ String secondReceived = "我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到";
+ Message message3 = new Message(secondReceived.getBytes(), properties);
+ rabbitTemplate.send("topic01", "lazy.brown.fox", message3);
+
+ String notReceived = "我的路由键 quick.brown.fox 不符合 topic1 任何绑定队列的要求,你将看不到我";
+ Message message4 = new Message(notReceived.getBytes(), properties);
+ rabbitTemplate.send("topic01", "quick.brown.fox", message4);
+ }
+}
+```
+
+```java
+结果:
+ SecondQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
+ FirstQueue收到消息:我的路由键 quick.orange.rabbit 符合queue1 和 queue2 的要求,我应该被两个监听器接收到
+ FirstQueue收到消息:我的路由键 quick.orange.fox 只符合queue1 的要求,只能被queue 1 接收到
+ SecondQueue收到消息:我的路由键 lazy.brown.fox 只符合queue2 的要求,只能被queue 2 接收到
+```
+
+
+
+## 四、传输对象
+
+#### 4.1 创建消息的委托处理器
+
+这里为了增强用例的实用性,我们创建的处理器的handleMessage方法是一个重载方法,对于同一个队列的监听,不仅可以传输对象消息,同时针对不同的对象类型调用不同的处理方法。
+
+```java
+/**
+ * @author : heibaiying
+ * @description :消息委派处理类
+ */
+public class MessageDelegate {
+
+ public void handleMessage(ProductManager manager) {
+ System.out.println("收到一个产品经理" + manager);
+ }
+
+ public void handleMessage(Programmer programmer) {
+ System.out.println("收到一个程序员" + programmer);
+ }
+
+}
+```
+
+#### 4.2 声明交换机、队列、绑定关系和消费者监听器
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 声明队列、交换机、绑定关系、用于测试对象的消息传递
+ */
+
+@Configuration
+public class RabbitObjectAnnotation {
+
+ @Bean
+ public DirectExchange objectTopic() {
+ // 创建一个持久化的交换机
+ return new DirectExchange("objectTopic", true, false);
+ }
+
+ @Bean
+ public Queue objectQueue() {
+ // 创建一个持久化的队列
+ return new Queue("objectQueue", true);
+ }
+
+ @Bean
+ public Binding binding() {
+ return BindingBuilder.bind(objectQueue()).to(objectTopic()).with("object");
+ }
+
+
+ /*创建队列消费者监听*/
+ @Bean
+ public SimpleMessageListenerContainer objectQueueLister(ConnectionFactory connectionFactory) {
+
+ SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
+ // 设置监听的队列
+ container.setQueues(objectQueue());
+ // 将监听到的消息委派给实际的处理类
+ MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());
+ // 指定由哪个方法来处理消息 默认就是handleMessage
+ adapter.setDefaultListenerMethod("handleMessage");
+
+ // 消息转换
+ Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
+ DefaultJackson2JavaTypeMapper javaTypeMapper = new DefaultJackson2JavaTypeMapper();
+
+ Map> idClassMapping = new HashMap<>();
+ // 针对不同的消息体调用不同的重载方法
+ idClassMapping.put(Type.MANAGER, com.heibaiying.bean.ProductManager.class);
+ idClassMapping.put(Type.PROGRAMMER, com.heibaiying.bean.Programmer.class);
+
+ javaTypeMapper.setIdClassMapping(idClassMapping);
+
+ jackson2JsonMessageConverter.setJavaTypeMapper(javaTypeMapper);
+ adapter.setMessageConverter(jackson2JsonMessageConverter);
+ container.setMessageListener(adapter);
+ return container;
+ }
+
+}
+```
+
+#### 4.3 测试对象消息的发送
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration(locations = "classpath:rabbitmq.xml")
+public class RabbitSendObjectTest {
+
+ @Autowired
+ private RabbitTemplate rabbitTemplate;
+
+ @Test
+ public void sendProgrammer() throws JsonProcessingException {
+ MessageProperties messageProperties = new MessageProperties();
+ //必须设置 contentType为 application/json
+ messageProperties.setContentType("application/json");
+ // 必须指定类型
+ messageProperties.getHeaders().put("__TypeId__", Type.PROGRAMMER);
+ Programmer programmer = new Programmer("xiaoming", 34, 52200.21f, new Date());
+ // 序列化与反序列化都使用的Jackson
+ ObjectMapper mapper = new ObjectMapper();
+ String programmerJson = mapper.writeValueAsString(programmer);
+ Message message = new Message(programmerJson.getBytes(), messageProperties);
+ rabbitTemplate.send("objectTopic", "object", message);
+ }
+
+
+ @Test
+ public void sendProductManager() throws JsonProcessingException {
+ MessageProperties messageProperties = new MessageProperties();
+ messageProperties.setContentType("application/json");
+ messageProperties.getHeaders().put("__TypeId__", Type.MANAGER);
+ ProductManager manager = new ProductManager("xiaohong", 21, new Date());
+ ObjectMapper mapper = new ObjectMapper();
+ String managerJson = mapper.writeValueAsString(manager);
+ Message message = new Message(managerJson.getBytes(), messageProperties);
+ rabbitTemplate.send("objectTopic", "object", message);
+ }
+}
```
\ No newline at end of file
diff --git a/spring/spring-redis-annotation/README.md b/spring/spring-redis-annotation/README.md
index cf8abeb..9a06830 100644
--- a/spring/spring-redis-annotation/README.md
+++ b/spring/spring-redis-annotation/README.md
@@ -1,530 +1,570 @@
-# spring 整合 redis (注解方式)
-
-## 一、说明
-
-### 1.1 Redis 客户端说明
-
-关于spring 整合 mybatis 本用例提供两种整合方法:
-
-1. jedis: 官方推荐的java客户端,能够胜任redis的大多数基本使用;
-2. redisson:也是官方推荐的客户端,比起jedis提供了更多高级的功能,比如分布式锁、集合数据切片等功能。同时提供了丰富而全面的中英文版本的wiki。
-
-注:关于redis其他语言官方推荐的客户端可以在[客户端](http://www.redis.cn/clients.html)该网页查看,其中官方推荐的用了黄色星星:star:标注。
-
-
-
-
-
-### 1.2 Redis可视化软件
-
-推荐**Redis Desktop Manager** 作为可视化查看工具,可以直观看到用例中测试关于存储实体对象序列化的情况。
-
-### 1.3 项目结构说明
-
-1. jedis和redisson的配置类和单元测试分别位于config和test下对应的包中,其中集群的配置类以cluster开头。
-2. 实体类Programmer.java用于测试Redisson序列化与反序列化
-
-
-
-
-
-### 1.3 依赖说明
-
-除了spring的基本依赖外,需要导入jedis 和 redisson 对应的客户端依赖包
-
-```xml
-
- redis.clients
- jedis
- 3.0.0
-
-
- org.redisson
- redisson
- 3.9.1
-
-
-
- io.netty
- netty-all
- 4.1.32.Final
-
-```
-
-
-
-## 二、spring 整合 jedis
-
-#### 2.1 新建基本配置文件和其映射类
-
-```properties
-redis.host=127.0.0.1
-redis.port=6379
-# 连接超时时间
-redis.timeout=2000
-# 最大空闲连接数
-redis.maxIdle=8
-# 最大连接数
-redis.maxTotal=16
-```
-
-```java
-@Configuration
-@PropertySource(value = "classpath:jedis.properties")
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-public class RedisProperty {
-
- @Value("${redis.host}")
- private String host;
- @Value("${redis.port}")
- private int port;
- @Value("${redis.timeout}")
- private int timeout;
- @Value("${redis.maxIdle}")
- private int maxIdle;
- @Value("${redis.maxTotal}")
- private int maxTotal;
-}
-```
-
-
-
-#### 2.2 单机配置
-
-```java
-/**
- * @author : heibaiying
- * @description : Jedis 单机配置
- */
-@Configuration
-@ComponentScan(value = "com.heibaiying.*")
-public class SingleJedisConfig {
-
- @Bean
- public JedisPool jedisPool(RedisProperty property) {
- JedisPoolConfig poolConfig = new JedisPoolConfig();
- poolConfig.setMaxIdle(property.getMaxIdle());
- poolConfig.setMaxTotal(property.getMaxTotal());
- return new JedisPool(poolConfig, property.getHost(), property.getPort(), property.getTimeout());
- }
-
- @Bean(destroyMethod = "close")
- public Jedis jedis(JedisPool jedisPool) {
- return jedisPool.getResource();
- }
-}
-```
-
-#### 2.3 集群配置
-
-```java
-@Configuration
-@ComponentScan(value = "com.heibaiying.*")
-public class ClusterJedisConfig {
-
- @Bean
- public JedisCluster jedisCluster(RedisProperty property) {
- JedisPoolConfig poolConfig = new JedisPoolConfig();
- poolConfig.setMaxIdle(property.getMaxIdle());
- poolConfig.setMaxTotal(property.getMaxTotal());
- Set nodes = new HashSet();
- nodes.add(new HostAndPort("127.0.0.1", 6379));
- nodes.add(new HostAndPort("127.0.0.1", 6380));
- return new JedisCluster(nodes, 2000);
- }
-}
-```
-
-#### 2.4 单机版本测试用例
-
-1.需要注意的是,对于jedis而言,单机版本和集群版本注入的实例是不同的;
-
-2.jedis本身并不支持序列化于反序列化操作,如果需要存储实体类,需要序列化后存入。(redisson本身就支持序列化于反序列化,详见下文)
-
-```java
-/**
- * @author : heibaiying
- * @description :redis 单机版测试
- */
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = SingleJedisConfig.class)
-public class JedisSamples {
-
- @Autowired
- private Jedis jedis;
-
- @Test
- public void Set() {
- jedis.set("hello", "spring annotation");
- }
-
- @Test
- public void Get() {
- String s = jedis.get("hello");
- System.out.println(s);
- }
-
- @Test
- public void setEx() {
- String s = jedis.setex("spring", 10, "我会在10秒后过期");
- System.out.println(s);
- }
-
-}
-```
-
-#### 2.5 集群版本测试用例
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = ClusterJedisConfig.class)
-public class JedisClusterSamples {
-
- @Autowired
- private JedisCluster jedisCluster;
-
- @Test
- public void Set() {
- jedisCluster.set("hello", "spring");
- }
-
- @Test
- public void Get() {
- String s = jedisCluster.get("hello");
- System.out.println(s);
- }
-
- @Test
- public void setEx() {
- String s = jedisCluster.setex("spring", 10, "我会在10秒后过期");
- System.out.println(s);
- }
-
-
-}
-```
-
-
-
-## 三、spring 整合 redisson
-
-#### 2.1 单机配置
-
-```java
-/**
- * @author : heibaiying
- * @description : redisson 单机配置
- */
-@Configuration
-public class SingalRedissonConfig {
-
- @Bean
- public RedissonClient redissonClient() {
- Config config = new Config();
- config.setTransportMode(TransportMode.NIO);
- config.useSingleServer().setAddress("redis://127.0.0.1:6379");
- return Redisson.create(config);
- }
-
-}
-```
-
-#### 2.2 集群配置
-
-```java
-/**
- * @author : heibaiying
- * @description : redisson 集群配置
- */
-@Configuration
-public class ClusterRedissonConfig {
-
- //@Bean
- public RedissonClient redissonClient() {
- Config config = new Config();
- config.useClusterServers()
- .setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
- //可以用"rediss://"来启用SSL连接
- .addNodeAddress("redis://127.0.0.1:6379", "redis://127.0.0.1:6380")
- .addNodeAddress("redis://127.0.0.1:6381");
- return Redisson.create(config);
- }
-
-}
-```
-
-#### 2.3 存储基本类型测试用例
-
-1. 这里需要注意的是,对于Redisson而言, 单机和集群最后在使用的时候注入的都是RedissonClient,这和jedis是不同的。
-
-```java
-/**
- * @author : heibaiying
- * @description :redisson 操作普通数据类型
- */
-
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = SingalRedissonConfig.class)
-public class RedissonSamples {
-
- @Autowired
- private RedissonClient redissonClient;
-
- @Test
- public void Set() {
- // key 存在则更新 不存在则删除
- RBucket rBucket = redissonClient.getBucket("redisson");
- rBucket.set("annotation Value");
- redissonClient.shutdown();
- }
-
- @Test
- public void Get() {
- // key 存在则更新 不存在则删除
- RBucket rBucket = redissonClient.getBucket("redisson");
- System.out.println(rBucket.get());
- }
-
- @Test
- public void SetEx() {
- // key 存在则更新 不存在则删除
- RBucket rBucket = redissonClient.getBucket("redissonEx");
- rBucket.set("我在十秒后会消失", 10, TimeUnit.SECONDS);
- }
-
-
- @After
- public void close() {
- redissonClient.shutdown();
- }
-}
-
-```
-
-#### 2.4 存储实体对象测试用例
-
-```java
-/**
- * @author : heibaiying
- * @description :redisson 对象序列化与反序列化
- */
-
-
-@RunWith(SpringRunner.class)
-@ContextConfiguration(classes = SingalRedissonConfig.class)
-public class RedissonObjectSamples {
-
- @Autowired
- private RedissonClient redissonClient;
-
- // Redisson的对象编码类是用于将对象进行序列化和反序列化 默认采用Jackson
-
- @Test
- public void Set() {
- RBucket rBucket = redissonClient.getBucket("programmer");
- rBucket.set(new Programmer("xiaoming", 12, 5000.21f, new Date()));
- redissonClient.shutdown();
- //存储结果: {"@class":"com.heibaiying.com.heibaiying.bean.Programmer","age":12,"birthday":["java.util.Date",1545714986590],"name":"xiaoming","salary":5000.21}
- }
-
- @Test
- public void Get() {
- RBucket rBucket = redissonClient.getBucket("programmer");
- System.out.println(rBucket.get());
- }
-
- @After
- public void close() {
- redissonClient.shutdown();
- }
-}
-```
-
-## 附:Redis的数据结构和操作命令
-
-### 1.1 预备
-
-#### 1.1.1 全局命令
-
-1. 查看所有键: **keys \***
-
-2. 查看键总数:**dbsize**
-
-3. 检查键是否存在:**exists key**
-
-4. 删除键:**del key [key ...]** 支持删除多个键
-
-5. 键过期:**expire key seconds**
-
- ttl命令会返回键的剩余过期时间, 它有3种返回值:
-
- - 大于等于0的整数: 键剩余的过期时间。
- - -1: 键没设置过期时间。
- - -2: 键不存在
-
-6. 键的数据结构 **type key**
-
-#### 1.1.2 数据结构和内部编码
-
-type命令实际返回的就是当前键的数据结构类型, 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合)
-
-#### 1.1.3 单线程架构
-
-1. 纯内存访问, Redis将所有数据放在内存中, 内存的响应时长大约为100纳秒, 这是Redis达到每秒万级别访问的重要基础。
-2. 非阻塞I/O, Redis使用epoll作为I/O多路复用技术的实现, 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间, 如图2-6所示。
-3. 单线程避免了线程切换和竞态产生的消耗。
-
-### 1.2 字符串
-
-| 作用 | 格式 | 参数或示例 |
-| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds: 为键设置秒级过期时间。
px milliseconds: 为键设置毫秒级过期时间。
nx: 键必须不存在, 才可以设置成功, 用于添加。
xx: 与nx相反, 键必须存在, 才可以设置成功, 用于更新。 |
-| 获取值 | get key | r如果获取的键不存在 ,则返回nil(空) |
-| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 |
-| 批量获取值 | mget key [key ...] | mget a b c d |
-| 计数 | incr key decr key incrby key increment(指定数值自增)
decrby key decrement(指定数值自减)
incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。
键不存在,创建键,并按照值为0自增或自减, 返回结果为1。 |
-| 追加值 | append key value | 向字符串的默认追加值 |
-| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
-| 设置并返回原值 | getset key value | |
-| 设置指定位置的租字符串 | setrange key offeset value | |
-| 获取部分字符串 | getrange key start end | |
-
-### 1.3 哈希
-
-| 作用 | 格式 | 参数或示例 |
-| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| 设置值 | hset key field value | hset user:1 name tom
hset user:1 age 12 |
-| 获取值 | hget key field | hget user:1 name |
-| 删除field | hdel key field [field ...] | |
-| 计算field个数 | hlen key | |
-| 批量设置或获取field-value | hmget key field [field]
hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin
hmget user:1 name city |
-| 判断field是否存在 | hexists key field | |
-| 获取所有field | hkeys key | |
-| 获取所有value | hvals key | |
-| 获取所有的filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。
获取全部 可以使用hscan命令, 该命令会渐进式遍历哈希类型 |
-| 计数 | hincrby key field
hincrbyfloat key field | |
-
-### 1.4 列表
-
-| 作用 | 格式 | 参数或示例 |
-| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| 增 | 左侧插入:lpush key value [value ...] 右侧插入:rpush key value [value ...] 某个指定元素前后插入:linsert key before\|after pivot value | |
-| 查 | 获取指定范围内的元素列表:lrange key start end 获取列表指定索引下标的元素:lindex key index 获取列表指定长度:llen key | lrange listkey 0 -1 |
-| 删 | 从列表左侧弹出元素:lpop key 从列表右侧弹出元素:rpop key 删除指定元素:lrem key count value 截取列表:ltrim key start end | count>0, 从左到右, 删除最多count个元素。
count<0, 从右到左, 删除最多count绝对值个元素。
count=0, 删除所有 |
-| 改 | 修改指定索引下标的元素:lset key index newValue | |
-| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout | key[key...]: 多个列表的键。 timeout: 阻塞时间\|等待时间(单位: 秒) |
-
-
-
-### 1.5 集合
-
-集合(set) 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素**, 并且集合中的元素是无序的, **不能通过索引下标获取元素**。
-
-**集合内操作**:
-
-| 作用 | 格式 | 参数或示例 |
-| -------------------- | ------------------------------ | ----------------------------------------- |
-| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 |
-| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 |
-| 计算元素个数 | scard key | |
-| 判断元素是否在集合中 | sismember key element | |
-| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素,count 默认为1 |
-| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素,spop 会 |
-| 获取集合中所有元素 | smembers key | 可用sscan 代替 |
-
-**集合间操作**:
-
-| 作用 | 格式 |
-| ---------------------------- | ------------------------------------------------------------ |
-| 求多个集合的交集 | sinter key [key ...] |
-| 求多个集合的并集 | suinon key [key ...] |
-| 求多个集合的差集 | sdiff key [key ...] |
-| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...] |
-
-### 1.6 有序集合
-
-有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数(score) 作为排序的依据。
-
-**集合内操作**:
-
-| 作用 | 格式 | 参数或示例 |
-| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| 添加成员 | zadd key score member [score member ...] | nx: member必须不存在, 才可设置成功, 用于添加。
xx: member必须存在, 才可以设置成功, 用于更新。
ch: 返回此次操作后, 有序集合元素和分数发生变化的个数
incr: 对score做增加, 相当于后面介绍的zincrby。 |
-| 计算成员个数 | zcard key | |
-| 计算某个成员的分数 | zscore key member | |
-| 计算某个成员的排名 | zrank key member zrevrank key member | zrank是从分数从低到高返回排名, zrevrank反之。 |
-| 删除成员 | zrem key member [member ...] | |
-| 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom |
-| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回, zrevrange反之。 |
-| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回, zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间(小括号) 和闭区间(中括号) , -inf和+inf分别代表无限小和无限大 |
-| 删除指定排名内的升序元素 | zremrangerank key start end | |
-| 删除指定分数范围的成员 | zremrangebyscore key min max | |
-
-**集合间操作**:
-
-| 作用 | 格式 |
-| ---- | ------------------------------------------------------------ |
-| 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
-| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
-
-- destination: 交集计算结果保存到这个键。
-- numkeys: 需要做交集计算键的个数。
-- key[key...]: 需要做交集计算的键。
-- weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重, 每个键的权重默认是1。
-- aggregate sum|min|max: 计算成员交集后, 分值可以按照sum(和) 、min(最小值) 、 max(最大值) 做汇总, 默认值是sum。
-
-### 1.7 键管理
-
-#### 1.7.1 单个键管理
-
-##### 1.键重命名
-
-**rename key newkey**
-
- 为了防止被强行rename, Redis提供了renamenx命令, 确保只有newKey不存在时候才被覆盖。
-
-##### 2. 随机返回键
-
- **random key**
-
-##### 3.键过期
-
-- expire key seconds: 键在seconds秒后过期。
-- expireat key timestamp: 键在秒级时间戳timestamp后过期。
-- pexpire key milliseconds: 键在milliseconds毫秒后过期。
-- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期
-
-注意:
-
-1. 如果expire key的键不存在, 返回结果为0
-2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样
-3. persist key t命令可以将键的过期时间清除
-4. 对于字符串类型键, 执行set命令会去掉过期时间, 这个问题很容易在开发中被忽视
-5. Redis不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
-6. setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间
-
-#### 1.7.2 键遍历
-
-##### 1. 全量键遍历
-
-**keys pattern**
-
-##### 2. 渐进式遍历
-
-scan cursor \[match pattern] \[count number]
-
-- cursor是必需参数, 实际上cursor是一个游标, 第一次遍历从0开始, 每次scan遍历完都会返回当前游标的值, 直到游标值为0, 表示遍历结束。
-- match pattern是可选参数, 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。
-- count number是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是10, 此参数可以适当增大。
-
-#### 1.7.3 数据库管理
-
-##### 1.切换数据库
-
-**select dbIndex**
-
-##### 2.flushdb/flushall
-
+# spring 整合 redis (注解方式)
+
## 目录
+一、说明
+ 1.1 Redis 客户端说明
+ 1.2 Redis可视化软件
+ 1.3 项目结构说明
+ 1.3 依赖说明
+二、spring 整合 jedis
+ 2.1 新建基本配置文件和其映射类
+ 2.2 单机配置
+ 2.3 集群配置
+ 2.4 单机版本测试用例
+ 2.5 集群版本测试用例
+三、spring 整合 redisson
+ 2.1 单机配置
+ 2.2 集群配置
+ 2.3 存储基本类型测试用例
+ 2.4 存储实体对象测试用例
+附:Redis的数据结构和操作命令
+ 1.1 预备
+ 1.1.1 全局命令
+ 1.1.2 数据结构和内部编码
+ 1.1.3 单线程架构
+ 1.2 字符串
+ 1.3 哈希
+ 1.4 列表
+ 1.5 集合
+ 1.6 有序集合
+ 1.7 键管理
+ 1.7.1 单个键管理
+ 1.键重命名
+ 2. 随机返回键
+ 3.键过期
+ 1.7.2 键遍历
+ 1. 全量键遍历
+ 2. 渐进式遍历
+ 1.7.3 数据库管理
+ 1.切换数据库
+ 2.flushdb/flushall
+## 正文
+
+
+## 一、说明
+
+### 1.1 Redis 客户端说明
+
+关于spring 整合 mybatis 本用例提供两种整合方法:
+
+1. jedis: 官方推荐的java客户端,能够胜任redis的大多数基本使用;
+2. redisson:也是官方推荐的客户端,比起jedis提供了更多高级的功能,比如分布式锁、集合数据切片等功能。同时提供了丰富而全面的中英文版本的wiki。
+
+注:关于redis其他语言官方推荐的客户端可以在[客户端](http://www.redis.cn/clients.html)该网页查看,其中官方推荐的用了黄色星星:star:标注。
+
+
+
+
+
+### 1.2 Redis可视化软件
+
+推荐**Redis Desktop Manager** 作为可视化查看工具,可以直观看到用例中测试关于存储实体对象序列化的情况。
+
+### 1.3 项目结构说明
+
+1. jedis和redisson的配置类和单元测试分别位于config和test下对应的包中,其中集群的配置类以cluster开头。
+2. 实体类Programmer.java用于测试Redisson序列化与反序列化
+
+
+
+
+
+### 1.3 依赖说明
+
+除了spring的基本依赖外,需要导入jedis 和 redisson 对应的客户端依赖包
+
+```xml
+
+ redis.clients
+ jedis
+ 3.0.0
+
+
+ org.redisson
+ redisson
+ 3.9.1
+
+
+
+ io.netty
+ netty-all
+ 4.1.32.Final
+
+```
+
+
+
+## 二、spring 整合 jedis
+
+#### 2.1 新建基本配置文件和其映射类
+
+```properties
+redis.host=127.0.0.1
+redis.port=6379
+# 连接超时时间
+redis.timeout=2000
+# 最大空闲连接数
+redis.maxIdle=8
+# 最大连接数
+redis.maxTotal=16
+```
+
+```java
+@Configuration
+@PropertySource(value = "classpath:jedis.properties")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class RedisProperty {
+
+ @Value("${redis.host}")
+ private String host;
+ @Value("${redis.port}")
+ private int port;
+ @Value("${redis.timeout}")
+ private int timeout;
+ @Value("${redis.maxIdle}")
+ private int maxIdle;
+ @Value("${redis.maxTotal}")
+ private int maxTotal;
+}
+```
+
+
+
+#### 2.2 单机配置
+
+```java
+/**
+ * @author : heibaiying
+ * @description : Jedis 单机配置
+ */
+@Configuration
+@ComponentScan(value = "com.heibaiying.*")
+public class SingleJedisConfig {
+
+ @Bean
+ public JedisPool jedisPool(RedisProperty property) {
+ JedisPoolConfig poolConfig = new JedisPoolConfig();
+ poolConfig.setMaxIdle(property.getMaxIdle());
+ poolConfig.setMaxTotal(property.getMaxTotal());
+ return new JedisPool(poolConfig, property.getHost(), property.getPort(), property.getTimeout());
+ }
+
+ @Bean(destroyMethod = "close")
+ public Jedis jedis(JedisPool jedisPool) {
+ return jedisPool.getResource();
+ }
+}
+```
+
+#### 2.3 集群配置
+
+```java
+@Configuration
+@ComponentScan(value = "com.heibaiying.*")
+public class ClusterJedisConfig {
+
+ @Bean
+ public JedisCluster jedisCluster(RedisProperty property) {
+ JedisPoolConfig poolConfig = new JedisPoolConfig();
+ poolConfig.setMaxIdle(property.getMaxIdle());
+ poolConfig.setMaxTotal(property.getMaxTotal());
+ Set nodes = new HashSet();
+ nodes.add(new HostAndPort("127.0.0.1", 6379));
+ nodes.add(new HostAndPort("127.0.0.1", 6380));
+ return new JedisCluster(nodes, 2000);
+ }
+}
+```
+
+#### 2.4 单机版本测试用例
+
+1.需要注意的是,对于jedis而言,单机版本和集群版本注入的实例是不同的;
+
+2.jedis本身并不支持序列化于反序列化操作,如果需要存储实体类,需要序列化后存入。(redisson本身就支持序列化于反序列化,详见下文)
+
+```java
+/**
+ * @author : heibaiying
+ * @description :redis 单机版测试
+ */
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = SingleJedisConfig.class)
+public class JedisSamples {
+
+ @Autowired
+ private Jedis jedis;
+
+ @Test
+ public void Set() {
+ jedis.set("hello", "spring annotation");
+ }
+
+ @Test
+ public void Get() {
+ String s = jedis.get("hello");
+ System.out.println(s);
+ }
+
+ @Test
+ public void setEx() {
+ String s = jedis.setex("spring", 10, "我会在10秒后过期");
+ System.out.println(s);
+ }
+
+}
+```
+
+#### 2.5 集群版本测试用例
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = ClusterJedisConfig.class)
+public class JedisClusterSamples {
+
+ @Autowired
+ private JedisCluster jedisCluster;
+
+ @Test
+ public void Set() {
+ jedisCluster.set("hello", "spring");
+ }
+
+ @Test
+ public void Get() {
+ String s = jedisCluster.get("hello");
+ System.out.println(s);
+ }
+
+ @Test
+ public void setEx() {
+ String s = jedisCluster.setex("spring", 10, "我会在10秒后过期");
+ System.out.println(s);
+ }
+
+
+}
+```
+
+
+
+## 三、spring 整合 redisson
+
+#### 2.1 单机配置
+
+```java
+/**
+ * @author : heibaiying
+ * @description : redisson 单机配置
+ */
+@Configuration
+public class SingalRedissonConfig {
+
+ @Bean
+ public RedissonClient redissonClient() {
+ Config config = new Config();
+ config.setTransportMode(TransportMode.NIO);
+ config.useSingleServer().setAddress("redis://127.0.0.1:6379");
+ return Redisson.create(config);
+ }
+
+}
+```
+
+#### 2.2 集群配置
+
+```java
+/**
+ * @author : heibaiying
+ * @description : redisson 集群配置
+ */
+@Configuration
+public class ClusterRedissonConfig {
+
+ //@Bean
+ public RedissonClient redissonClient() {
+ Config config = new Config();
+ config.useClusterServers()
+ .setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
+ //可以用"rediss://"来启用SSL连接
+ .addNodeAddress("redis://127.0.0.1:6379", "redis://127.0.0.1:6380")
+ .addNodeAddress("redis://127.0.0.1:6381");
+ return Redisson.create(config);
+ }
+
+}
+```
+
+#### 2.3 存储基本类型测试用例
+
+1. 这里需要注意的是,对于Redisson而言, 单机和集群最后在使用的时候注入的都是RedissonClient,这和jedis是不同的。
+
+```java
+/**
+ * @author : heibaiying
+ * @description :redisson 操作普通数据类型
+ */
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = SingalRedissonConfig.class)
+public class RedissonSamples {
+
+ @Autowired
+ private RedissonClient redissonClient;
+
+ @Test
+ public void Set() {
+ // key 存在则更新 不存在则删除
+ RBucket rBucket = redissonClient.getBucket("redisson");
+ rBucket.set("annotation Value");
+ redissonClient.shutdown();
+ }
+
+ @Test
+ public void Get() {
+ // key 存在则更新 不存在则删除
+ RBucket rBucket = redissonClient.getBucket("redisson");
+ System.out.println(rBucket.get());
+ }
+
+ @Test
+ public void SetEx() {
+ // key 存在则更新 不存在则删除
+ RBucket rBucket = redissonClient.getBucket("redissonEx");
+ rBucket.set("我在十秒后会消失", 10, TimeUnit.SECONDS);
+ }
+
+
+ @After
+ public void close() {
+ redissonClient.shutdown();
+ }
+}
+
+```
+
+#### 2.4 存储实体对象测试用例
+
+```java
+/**
+ * @author : heibaiying
+ * @description :redisson 对象序列化与反序列化
+ */
+
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration(classes = SingalRedissonConfig.class)
+public class RedissonObjectSamples {
+
+ @Autowired
+ private RedissonClient redissonClient;
+
+ // Redisson的对象编码类是用于将对象进行序列化和反序列化 默认采用Jackson
+
+ @Test
+ public void Set() {
+ RBucket rBucket = redissonClient.getBucket("programmer");
+ rBucket.set(new Programmer("xiaoming", 12, 5000.21f, new Date()));
+ redissonClient.shutdown();
+ //存储结果: {"@class":"com.heibaiying.com.heibaiying.bean.Programmer","age":12,"birthday":["java.util.Date",1545714986590],"name":"xiaoming","salary":5000.21}
+ }
+
+ @Test
+ public void Get() {
+ RBucket rBucket = redissonClient.getBucket("programmer");
+ System.out.println(rBucket.get());
+ }
+
+ @After
+ public void close() {
+ redissonClient.shutdown();
+ }
+}
+```
+
+## 附:Redis的数据结构和操作命令
+
+### 1.1 预备
+
+#### 1.1.1 全局命令
+
+1. 查看所有键: **keys \***
+
+2. 查看键总数:**dbsize**
+
+3. 检查键是否存在:**exists key**
+
+4. 删除键:**del key [key ...]** 支持删除多个键
+
+5. 键过期:**expire key seconds**
+
+ ttl命令会返回键的剩余过期时间, 它有3种返回值:
+
+ - 大于等于0的整数: 键剩余的过期时间。
+ - -1: 键没设置过期时间。
+ - -2: 键不存在
+
+6. 键的数据结构 **type key**
+
+#### 1.1.2 数据结构和内部编码
+
+type命令实际返回的就是当前键的数据结构类型, 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合)
+
+#### 1.1.3 单线程架构
+
+1. 纯内存访问, Redis将所有数据放在内存中, 内存的响应时长大约为100纳秒, 这是Redis达到每秒万级别访问的重要基础。
+2. 非阻塞I/O, Redis使用epoll作为I/O多路复用技术的实现, 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间, 如图2-6所示。
+3. 单线程避免了线程切换和竞态产生的消耗。
+
+### 1.2 字符串
+
+| 作用 | 格式 | 参数或示例 |
+| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds: 为键设置秒级过期时间。
px milliseconds: 为键设置毫秒级过期时间。
nx: 键必须不存在, 才可以设置成功, 用于添加。
xx: 与nx相反, 键必须存在, 才可以设置成功, 用于更新。 |
+| 获取值 | get key | r如果获取的键不存在 ,则返回nil(空) |
+| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 |
+| 批量获取值 | mget key [key ...] | mget a b c d |
+| 计数 | incr key decr key incrby key increment(指定数值自增)
decrby key decrement(指定数值自减)
incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。
键不存在,创建键,并按照值为0自增或自减, 返回结果为1。 |
+| 追加值 | append key value | 向字符串的默认追加值 |
+| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
+| 设置并返回原值 | getset key value | |
+| 设置指定位置的租字符串 | setrange key offeset value | |
+| 获取部分字符串 | getrange key start end | |
+
+### 1.3 哈希
+
+| 作用 | 格式 | 参数或示例 |
+| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 设置值 | hset key field value | hset user:1 name tom
hset user:1 age 12 |
+| 获取值 | hget key field | hget user:1 name |
+| 删除field | hdel key field [field ...] | |
+| 计算field个数 | hlen key | |
+| 批量设置或获取field-value | hmget key field [field]
hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin
hmget user:1 name city |
+| 判断field是否存在 | hexists key field | |
+| 获取所有field | hkeys key | |
+| 获取所有value | hvals key | |
+| 获取所有的filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。
获取全部 可以使用hscan命令, 该命令会渐进式遍历哈希类型 |
+| 计数 | hincrby key field
hincrbyfloat key field | |
+
+### 1.4 列表
+
+| 作用 | 格式 | 参数或示例 |
+| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 增 | 左侧插入:lpush key value [value ...] 右侧插入:rpush key value [value ...] 某个指定元素前后插入:linsert key before\|after pivot value | |
+| 查 | 获取指定范围内的元素列表:lrange key start end 获取列表指定索引下标的元素:lindex key index 获取列表指定长度:llen key | lrange listkey 0 -1 |
+| 删 | 从列表左侧弹出元素:lpop key 从列表右侧弹出元素:rpop key 删除指定元素:lrem key count value 截取列表:ltrim key start end | count>0, 从左到右, 删除最多count个元素。
count<0, 从右到左, 删除最多count绝对值个元素。
count=0, 删除所有 |
+| 改 | 修改指定索引下标的元素:lset key index newValue | |
+| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout | key[key...]: 多个列表的键。 timeout: 阻塞时间\|等待时间(单位: 秒) |
+
+
+
+### 1.5 集合
+
+集合(set) 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素**, 并且集合中的元素是无序的, **不能通过索引下标获取元素**。
+
+**集合内操作**:
+
+| 作用 | 格式 | 参数或示例 |
+| -------------------- | ------------------------------ | ----------------------------------------- |
+| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 |
+| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 |
+| 计算元素个数 | scard key | |
+| 判断元素是否在集合中 | sismember key element | |
+| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素,count 默认为1 |
+| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素,spop 会 |
+| 获取集合中所有元素 | smembers key | 可用sscan 代替 |
+
+**集合间操作**:
+
+| 作用 | 格式 |
+| ---------------------------- | ------------------------------------------------------------ |
+| 求多个集合的交集 | sinter key [key ...] |
+| 求多个集合的并集 | suinon key [key ...] |
+| 求多个集合的差集 | sdiff key [key ...] |
+| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...] |
+
+### 1.6 有序集合
+
+有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数(score) 作为排序的依据。
+
+**集合内操作**:
+
+| 作用 | 格式 | 参数或示例 |
+| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 添加成员 | zadd key score member [score member ...] | nx: member必须不存在, 才可设置成功, 用于添加。
xx: member必须存在, 才可以设置成功, 用于更新。
ch: 返回此次操作后, 有序集合元素和分数发生变化的个数
incr: 对score做增加, 相当于后面介绍的zincrby。 |
+| 计算成员个数 | zcard key | |
+| 计算某个成员的分数 | zscore key member | |
+| 计算某个成员的排名 | zrank key member zrevrank key member | zrank是从分数从低到高返回排名, zrevrank反之。 |
+| 删除成员 | zrem key member [member ...] | |
+| 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom |
+| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回, zrevrange反之。 |
+| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回, zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间(小括号) 和闭区间(中括号) , -inf和+inf分别代表无限小和无限大 |
+| 删除指定排名内的升序元素 | zremrangerank key start end | |
+| 删除指定分数范围的成员 | zremrangebyscore key min max | |
+
+**集合间操作**:
+
+| 作用 | 格式 |
+| ---- | ------------------------------------------------------------ |
+| 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
+| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
+
+- destination: 交集计算结果保存到这个键。
+- numkeys: 需要做交集计算键的个数。
+- key[key...]: 需要做交集计算的键。
+- weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重, 每个键的权重默认是1。
+- aggregate sum|min|max: 计算成员交集后, 分值可以按照sum(和) 、min(最小值) 、 max(最大值) 做汇总, 默认值是sum。
+
+### 1.7 键管理
+
+#### 1.7.1 单个键管理
+
+##### 1.键重命名
+
+**rename key newkey**
+
+ 为了防止被强行rename, Redis提供了renamenx命令, 确保只有newKey不存在时候才被覆盖。
+
+##### 2. 随机返回键
+
+ **random key**
+
+##### 3.键过期
+
+- expire key seconds: 键在seconds秒后过期。
+- expireat key timestamp: 键在秒级时间戳timestamp后过期。
+- pexpire key milliseconds: 键在milliseconds毫秒后过期。
+- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期
+
+注意:
+
+1. 如果expire key的键不存在, 返回结果为0
+2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样
+3. persist key t命令可以将键的过期时间清除
+4. 对于字符串类型键, 执行set命令会去掉过期时间, 这个问题很容易在开发中被忽视
+5. Redis不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
+6. setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间
+
+#### 1.7.2 键遍历
+
+##### 1. 全量键遍历
+
+**keys pattern**
+
+##### 2. 渐进式遍历
+
+scan cursor \[match pattern] \[count number]
+
+- cursor是必需参数, 实际上cursor是一个游标, 第一次遍历从0开始, 每次scan遍历完都会返回当前游标的值, 直到游标值为0, 表示遍历结束。
+- match pattern是可选参数, 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。
+- count number是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是10, 此参数可以适当增大。
+
+#### 1.7.3 数据库管理
+
+##### 1.切换数据库
+
+**select dbIndex**
+
+##### 2.flushdb/flushall
+
flushdb/flushall命令用于清除数据库, 两者的区别的是flushdb只清除当前数据库, flushall会清除所有数据库。
\ No newline at end of file
diff --git a/spring/spring-redis/README.md b/spring/spring-redis/README.md
index ab37266..e76ce09 100644
--- a/spring/spring-redis/README.md
+++ b/spring/spring-redis/README.md
@@ -1,553 +1,593 @@
-# spring 整合 redis (xml配置方式)
-
-## 一、说明
-
-### 1.1 Redis 客户端说明
-
-关于spring 整合 mybatis 本用例提供两种整合方法:
-
-1. jedis: 官方推荐的java客户端,能够胜任redis的大多数基本使用;
-2. redisson:也是官方推荐的客户端,比起jedis提供了更多高级的功能,比如分布式锁、集合数据切片等功能。同时提供了丰富而全面的中英文版本的wiki。
-
-注:关于redis其他语言官方推荐的客户端可以在[客户端](http://www.redis.cn/clients.html)该网页查看,其中官方推荐的用了黄色星星:star:标注。
-
-
-
-
-
-### 1.2 Redis可视化软件
-
-推荐**Redis Desktop Manager** 作为可视化查看工具,可以直观看到用例中测试关于存储实体对象序列化的情况。
-
-### 1.3 项目结构说明
-
-1. jedis和redisson的配置和单元测试分别位于resources和test下对应的包中,其中集群的配置文件以cluster结尾。所有配置按照需要在springApplication.xml用import导入。
-2. 实体类Programmer.java用于测试Redisson序列化与反序列化
-
-
-
-**springapplication.xml文件:**
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-### 1.3 依赖说明
-
-除了spring的基本依赖外,需要导入jedis 和 redisson 对应的客户端依赖包
-
-```xml
-
- redis.clients
- jedis
- 3.0.0
-
-
- org.redisson
- redisson
- 3.9.1
-
-
-
- io.netty
- netty-all
- 4.1.32.Final
-
-```
-
-
-
-## 二、spring 整合 jedis
-
-#### 2.1 新建基本配置文件
-
-```properties
-redis.host=127.0.0.1
-redis.port=6379
-# 连接超时时间
-redis.timeout=2000
-# 最大空闲连接数
-redis.maxIdle=8
-# 最大连接数
-redis.maxTotal=16
-```
-
-#### 2.2 单机配置
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.3 集群配置
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.4 单机版本测试用例
-
-1.需要注意的是,对于jedis而言,单机版本和集群版本注入的实例是不同的;
-
-2.jedis本身并不支持序列化于反序列化操作,如果需要存储实体类,需要序列化后存入。(redisson本身就支持序列化于反序列化,详见下文)
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration({"classpath:springApplication.xml"})
-public class JedisSamples {
-
- @Autowired
- private Jedis jedis;
-
- @Test
- public void Set() {
- jedis.set("hello", "spring");
- }
-
- @Test
- public void Get() {
- String s = jedis.get("hello");
- System.out.println(s);
- }
-
- @Test
- public void setEx() {
- String s = jedis.setex("spring", 10, "我会在10秒后过期");
- System.out.println(s);
- }
-
-}
-
-```
-
-#### 2.5 集群版本测试用例
-
-```java
-
-@RunWith(SpringRunner.class)
-@ContextConfiguration({"classpath:springApplication.xml"})
-public class JedisClusterSamples {
-
- @Autowired
- private JedisCluster jedisCluster;
-
- @Test
- public void Set() {
- jedisCluster.set("hello", "spring");
- }
-
- @Test
- public void Get() {
- String s = jedisCluster.get("hello");
- System.out.println(s);
- }
-
- @Test
- public void setEx() {
- String s = jedisCluster.setex("spring", 10, "我会在10秒后过期");
- System.out.println(s);
- }
-
-}
-```
-
-
-
-## 三、spring 整合 redisson
-
-#### 2.1 单机配置
-
-```xml
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.2 集群配置
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.3 存储基本类型测试用例
-
-1. 这里需要注意的是,对于Redisson而言, 单机和集群最后在使用的时候注入的都是RedissonClient,这和jedis是不同的。
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration({"classpath:springApplication.xml"})
-public class RedissonSamples {
-
- @Autowired
- private RedissonClient redissonClient;
-
- @Test
- public void Set() {
- // key 存在则更新 不存在则删除
- RBucket rBucket = redissonClient.getBucket("redisson");
- rBucket.set("firstValue");
- }
-
- @Test
- public void Get() {
- // key 存在则更新 不存在则删除
- RBucket rBucket = redissonClient.getBucket("redisson");
- System.out.println(rBucket.get());
- }
-
- @Test
- public void SetEx() {
- // key 存在则更新 不存在则删除
- RBucket rBucket = redissonClient.getBucket("redissonEx");
- rBucket.set("我在十秒后会消失", 10, TimeUnit.SECONDS);
- }
-
-
- @After
- public void close() {
- redissonClient.shutdown();
- }
-}
-```
-
-#### 2.4 存储实体对象测试用例
-
-```java
-@RunWith(SpringRunner.class)
-@ContextConfiguration({"classpath:springApplication.xml"})
-public class RedissonObjectSamples {
-
- @Autowired
- private RedissonClient redissonClient;
-
- // Redisson的对象编码类是用于将对象进行序列化和反序列化 默认采用Jackson
-
- @Test
- public void Set() {
- RBucket rBucket = redissonClient.getBucket("programmer");
- rBucket.set(new Programmer("xiaoming", 12, 5000.21f, new Date()));
- //存储结果: {"@class":"com.heibaiying.bean.Programmer","age":12,"birthday":["java.util.Date",1545714986590],"name":"xiaoming","salary":5000.21}
- }
-
- @Test
- public void Get() {
- RBucket rBucket = redissonClient.getBucket("programmer");
- System.out.println(rBucket.get());
- }
-
- @After
- public void close() {
- redissonClient.shutdown();
- }
-}
-
-```
-
-## 附:Redis的数据结构和操作命令
-
-### 1.1 预备
-
-#### 1.1.1 全局命令
-
-1. 查看所有键: **keys \***
-
-2. 查看键总数:**dbsize**
-
-3. 检查键是否存在:**exists key**
-
-4. 删除键:**del key [key ...]** 支持删除多个键
-
-5. 键过期:**expire key seconds**
-
- ttl命令会返回键的剩余过期时间, 它有3种返回值:
-
- - 大于等于0的整数: 键剩余的过期时间。
- - -1: 键没设置过期时间。
- - -2: 键不存在
-
-6. 键的数据结构 **type key**
-
-#### 1.1.2 数据结构和内部编码
-
-type命令实际返回的就是当前键的数据结构类型, 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合)
-
-#### 1.1.3 单线程架构
-
-1. 纯内存访问, Redis将所有数据放在内存中, 内存的响应时长大约为100纳秒, 这是Redis达到每秒万级别访问的重要基础。
-2. 非阻塞I/O, Redis使用epoll作为I/O多路复用技术的实现, 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间, 如图2-6所示。
-3. 单线程避免了线程切换和竞态产生的消耗。
-
-### 1.2 字符串
-
-| 作用 | 格式 | 参数或示例 |
-| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds: 为键设置秒级过期时间。
px milliseconds: 为键设置毫秒级过期时间。
nx: 键必须不存在, 才可以设置成功, 用于添加。
xx: 与nx相反, 键必须存在, 才可以设置成功, 用于更新。 |
-| 获取值 | get key | r如果获取的键不存在 ,则返回nil(空) |
-| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 |
-| 批量获取值 | mget key [key ...] | mget a b c d |
-| 计数 | incr key decr key incrby key increment(指定数值自增)
decrby key decrement(指定数值自减)
incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。
键不存在,创建键,并按照值为0自增或自减, 返回结果为1。 |
-| 追加值 | append key value | 向字符串的默认追加值 |
-| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
-| 设置并返回原值 | getset key value | |
-| 设置指定位置的租字符串 | setrange key offeset value | |
-| 获取部分字符串 | getrange key start end | |
-
-### 1.3 哈希
-
-| 作用 | 格式 | 参数或示例 |
-| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| 设置值 | hset key field value | hset user:1 name tom
hset user:1 age 12 |
-| 获取值 | hget key field | hget user:1 name |
-| 删除field | hdel key field [field ...] | |
-| 计算field个数 | hlen key | |
-| 批量设置或获取field-value | hmget key field [field]
hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin
hmget user:1 name city |
-| 判断field是否存在 | hexists key field | |
-| 获取所有field | hkeys key | |
-| 获取所有value | hvals key | |
-| 获取所有的filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。
获取全部 可以使用hscan命令, 该命令会渐进式遍历哈希类型 |
-| 计数 | hincrby key field
hincrbyfloat key field | |
-
-### 1.4 列表
-
-| 作用 | 格式 | 参数或示例 |
-| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| 增 | 左侧插入:lpush key value [value ...] 右侧插入:rpush key value [value ...] 某个指定元素前后插入:linsert key before\|after pivot value | |
-| 查 | 获取指定范围内的元素列表:lrange key start end 获取列表指定索引下标的元素:lindex key index 获取列表指定长度:llen key | lrange listkey 0 -1 |
-| 删 | 从列表左侧弹出元素:lpop key 从列表右侧弹出元素:rpop key 删除指定元素:lrem key count value 截取列表:ltrim key start end | count>0, 从左到右, 删除最多count个元素。
count<0, 从右到左, 删除最多count绝对值个元素。
count=0, 删除所有 |
-| 改 | 修改指定索引下标的元素:lset key index newValue | |
-| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout | key[key...]: 多个列表的键。 timeout: 阻塞时间\|等待时间(单位: 秒) |
-
-
-
-### 1.5 集合
-
-集合(set) 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素**, 并且集合中的元素是无序的, **不能通过索引下标获取元素**。
-
-**集合内操作**:
-
-| 作用 | 格式 | 参数或示例 |
-| -------------------- | ------------------------------ | ----------------------------------------- |
-| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 |
-| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 |
-| 计算元素个数 | scard key | |
-| 判断元素是否在集合中 | sismember key element | |
-| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素,count 默认为1 |
-| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素,spop 会 |
-| 获取集合中所有元素 | smembers key | 可用sscan 代替 |
-
-**集合间操作**:
-
-| 作用 | 格式 |
-| ---------------------------- | ------------------------------------------------------------ |
-| 求多个集合的交集 | sinter key [key ...] |
-| 求多个集合的并集 | suinon key [key ...] |
-| 求多个集合的差集 | sdiff key [key ...] |
-| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...] |
-
-### 1.6 有序集合
-
-有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数(score) 作为排序的依据。
-
-**集合内操作**:
-
-| 作用 | 格式 | 参数或示例 |
-| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| 添加成员 | zadd key score member [score member ...] | nx: member必须不存在, 才可设置成功, 用于添加。
xx: member必须存在, 才可以设置成功, 用于更新。
ch: 返回此次操作后, 有序集合元素和分数发生变化的个数
incr: 对score做增加, 相当于后面介绍的zincrby。 |
-| 计算成员个数 | zcard key | |
-| 计算某个成员的分数 | zscore key member | |
-| 计算某个成员的排名 | zrank key member zrevrank key member | zrank是从分数从低到高返回排名, zrevrank反之。 |
-| 删除成员 | zrem key member [member ...] | |
-| 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom |
-| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回, zrevrange反之。 |
-| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回, zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间(小括号) 和闭区间(中括号) , -inf和+inf分别代表无限小和无限大 |
-| 删除指定排名内的升序元素 | zremrangerank key start end | |
-| 删除指定分数范围的成员 | zremrangebyscore key min max | |
-
-**集合间操作**:
-
-| 作用 | 格式 |
-| ---- | ------------------------------------------------------------ |
-| 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
-| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
-
-- destination: 交集计算结果保存到这个键。
-- numkeys: 需要做交集计算键的个数。
-- key[key...]: 需要做交集计算的键。
-- weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重, 每个键的权重默认是1。
-- aggregate sum|min|max: 计算成员交集后, 分值可以按照sum(和) 、min(最小值) 、 max(最大值) 做汇总, 默认值是sum。
-
-### 1.7 键管理
-
-#### 1.7.1 单个键管理
-
-##### 1.键重命名
-
-**rename key newkey**
-
- 为了防止被强行rename, Redis提供了renamenx命令, 确保只有newKey不存在时候才被覆盖。
-
-##### 2. 随机返回键
-
- **random key**
-
-##### 3.键过期
-
-- expire key seconds: 键在seconds秒后过期。
-- expireat key timestamp: 键在秒级时间戳timestamp后过期。
-- pexpire key milliseconds: 键在milliseconds毫秒后过期。
-- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期
-
-注意:
-
-1. 如果expire key的键不存在, 返回结果为0
-2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样
-3. persist key t命令可以将键的过期时间清除
-4. 对于字符串类型键, 执行set命令会去掉过期时间, 这个问题很容易在开发中被忽视
-5. Redis不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
-6. setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间
-
-#### 1.7.2 键遍历
-
-##### 1. 全量键遍历
-
-**keys pattern**
-
-##### 2. 渐进式遍历
-
-scan cursor \[match pattern] \[count number]
-
-- cursor是必需参数, 实际上cursor是一个游标, 第一次遍历从0开始, 每次scan遍历完都会返回当前游标的值, 直到游标值为0, 表示遍历结束。
-- match pattern是可选参数, 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。
-- count number是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是10, 此参数可以适当增大。
-
-#### 1.7.3 数据库管理
-
-##### 1.切换数据库
-
-**select dbIndex**
-
-##### 2.flushdb/flushall
-
+# spring 整合 redis (xml配置方式)
+
## 目录
+一、说明
+ 1.1 Redis 客户端说明
+ 1.2 Redis可视化软件
+ 1.3 项目结构说明
+ 1.3 依赖说明
+二、spring 整合 jedis
+ 2.1 新建基本配置文件
+ 2.2 单机配置
+ 2.3 集群配置
+ 2.4 单机版本测试用例
+ 2.5 集群版本测试用例
+三、spring 整合 redisson
+ 2.1 单机配置
+ 2.2 集群配置
+ 2.3 存储基本类型测试用例
+ 2.4 存储实体对象测试用例
+附:Redis的数据结构和操作命令
+ 1.1 预备
+ 1.1.1 全局命令
+ 1.1.2 数据结构和内部编码
+ 1.1.3 单线程架构
+ 1.2 字符串
+ 1.3 哈希
+ 1.4 列表
+ 1.5 集合
+ 1.6 有序集合
+ 1.7 键管理
+ 1.7.1 单个键管理
+ 1.键重命名
+ 2. 随机返回键
+ 3.键过期
+ 1.7.2 键遍历
+ 1. 全量键遍历
+ 2. 渐进式遍历
+ 1.7.3 数据库管理
+ 1.切换数据库
+ 2.flushdb/flushall
+## 正文
+
+
+## 一、说明
+
+### 1.1 Redis 客户端说明
+
+关于spring 整合 mybatis 本用例提供两种整合方法:
+
+1. jedis: 官方推荐的java客户端,能够胜任redis的大多数基本使用;
+2. redisson:也是官方推荐的客户端,比起jedis提供了更多高级的功能,比如分布式锁、集合数据切片等功能。同时提供了丰富而全面的中英文版本的wiki。
+
+注:关于redis其他语言官方推荐的客户端可以在[客户端](http://www.redis.cn/clients.html)该网页查看,其中官方推荐的用了黄色星星:star:标注。
+
+
+
+
+
+### 1.2 Redis可视化软件
+
+推荐**Redis Desktop Manager** 作为可视化查看工具,可以直观看到用例中测试关于存储实体对象序列化的情况。
+
+### 1.3 项目结构说明
+
+1. jedis和redisson的配置和单元测试分别位于resources和test下对应的包中,其中集群的配置文件以cluster结尾。所有配置按照需要在springApplication.xml用import导入。
+2. 实体类Programmer.java用于测试Redisson序列化与反序列化
+
+
+
+**springapplication.xml文件:**
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### 1.3 依赖说明
+
+除了spring的基本依赖外,需要导入jedis 和 redisson 对应的客户端依赖包
+
+```xml
+
+ redis.clients
+ jedis
+ 3.0.0
+
+
+ org.redisson
+ redisson
+ 3.9.1
+
+
+
+ io.netty
+ netty-all
+ 4.1.32.Final
+
+```
+
+
+
+## 二、spring 整合 jedis
+
+#### 2.1 新建基本配置文件
+
+```properties
+redis.host=127.0.0.1
+redis.port=6379
+# 连接超时时间
+redis.timeout=2000
+# 最大空闲连接数
+redis.maxIdle=8
+# 最大连接数
+redis.maxTotal=16
+```
+
+#### 2.2 单机配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.3 集群配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.4 单机版本测试用例
+
+1.需要注意的是,对于jedis而言,单机版本和集群版本注入的实例是不同的;
+
+2.jedis本身并不支持序列化于反序列化操作,如果需要存储实体类,需要序列化后存入。(redisson本身就支持序列化于反序列化,详见下文)
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration({"classpath:springApplication.xml"})
+public class JedisSamples {
+
+ @Autowired
+ private Jedis jedis;
+
+ @Test
+ public void Set() {
+ jedis.set("hello", "spring");
+ }
+
+ @Test
+ public void Get() {
+ String s = jedis.get("hello");
+ System.out.println(s);
+ }
+
+ @Test
+ public void setEx() {
+ String s = jedis.setex("spring", 10, "我会在10秒后过期");
+ System.out.println(s);
+ }
+
+}
+
+```
+
+#### 2.5 集群版本测试用例
+
+```java
+
+@RunWith(SpringRunner.class)
+@ContextConfiguration({"classpath:springApplication.xml"})
+public class JedisClusterSamples {
+
+ @Autowired
+ private JedisCluster jedisCluster;
+
+ @Test
+ public void Set() {
+ jedisCluster.set("hello", "spring");
+ }
+
+ @Test
+ public void Get() {
+ String s = jedisCluster.get("hello");
+ System.out.println(s);
+ }
+
+ @Test
+ public void setEx() {
+ String s = jedisCluster.setex("spring", 10, "我会在10秒后过期");
+ System.out.println(s);
+ }
+
+}
+```
+
+
+
+## 三、spring 整合 redisson
+
+#### 2.1 单机配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.2 集群配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.3 存储基本类型测试用例
+
+1. 这里需要注意的是,对于Redisson而言, 单机和集群最后在使用的时候注入的都是RedissonClient,这和jedis是不同的。
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration({"classpath:springApplication.xml"})
+public class RedissonSamples {
+
+ @Autowired
+ private RedissonClient redissonClient;
+
+ @Test
+ public void Set() {
+ // key 存在则更新 不存在则删除
+ RBucket rBucket = redissonClient.getBucket("redisson");
+ rBucket.set("firstValue");
+ }
+
+ @Test
+ public void Get() {
+ // key 存在则更新 不存在则删除
+ RBucket rBucket = redissonClient.getBucket("redisson");
+ System.out.println(rBucket.get());
+ }
+
+ @Test
+ public void SetEx() {
+ // key 存在则更新 不存在则删除
+ RBucket rBucket = redissonClient.getBucket("redissonEx");
+ rBucket.set("我在十秒后会消失", 10, TimeUnit.SECONDS);
+ }
+
+
+ @After
+ public void close() {
+ redissonClient.shutdown();
+ }
+}
+```
+
+#### 2.4 存储实体对象测试用例
+
+```java
+@RunWith(SpringRunner.class)
+@ContextConfiguration({"classpath:springApplication.xml"})
+public class RedissonObjectSamples {
+
+ @Autowired
+ private RedissonClient redissonClient;
+
+ // Redisson的对象编码类是用于将对象进行序列化和反序列化 默认采用Jackson
+
+ @Test
+ public void Set() {
+ RBucket rBucket = redissonClient.getBucket("programmer");
+ rBucket.set(new Programmer("xiaoming", 12, 5000.21f, new Date()));
+ //存储结果: {"@class":"com.heibaiying.bean.Programmer","age":12,"birthday":["java.util.Date",1545714986590],"name":"xiaoming","salary":5000.21}
+ }
+
+ @Test
+ public void Get() {
+ RBucket rBucket = redissonClient.getBucket("programmer");
+ System.out.println(rBucket.get());
+ }
+
+ @After
+ public void close() {
+ redissonClient.shutdown();
+ }
+}
+
+```
+
+## 附:Redis的数据结构和操作命令
+
+### 1.1 预备
+
+#### 1.1.1 全局命令
+
+1. 查看所有键: **keys \***
+
+2. 查看键总数:**dbsize**
+
+3. 检查键是否存在:**exists key**
+
+4. 删除键:**del key [key ...]** 支持删除多个键
+
+5. 键过期:**expire key seconds**
+
+ ttl命令会返回键的剩余过期时间, 它有3种返回值:
+
+ - 大于等于0的整数: 键剩余的过期时间。
+ - -1: 键没设置过期时间。
+ - -2: 键不存在
+
+6. 键的数据结构 **type key**
+
+#### 1.1.2 数据结构和内部编码
+
+type命令实际返回的就是当前键的数据结构类型, 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合)
+
+#### 1.1.3 单线程架构
+
+1. 纯内存访问, Redis将所有数据放在内存中, 内存的响应时长大约为100纳秒, 这是Redis达到每秒万级别访问的重要基础。
+2. 非阻塞I/O, Redis使用epoll作为I/O多路复用技术的实现, 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间, 如图2-6所示。
+3. 单线程避免了线程切换和竞态产生的消耗。
+
+### 1.2 字符串
+
+| 作用 | 格式 | 参数或示例 |
+| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds: 为键设置秒级过期时间。
px milliseconds: 为键设置毫秒级过期时间。
nx: 键必须不存在, 才可以设置成功, 用于添加。
xx: 与nx相反, 键必须存在, 才可以设置成功, 用于更新。 |
+| 获取值 | get key | r如果获取的键不存在 ,则返回nil(空) |
+| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 |
+| 批量获取值 | mget key [key ...] | mget a b c d |
+| 计数 | incr key decr key incrby key increment(指定数值自增)
decrby key decrement(指定数值自减)
incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。
键不存在,创建键,并按照值为0自增或自减, 返回结果为1。 |
+| 追加值 | append key value | 向字符串的默认追加值 |
+| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 |
+| 设置并返回原值 | getset key value | |
+| 设置指定位置的租字符串 | setrange key offeset value | |
+| 获取部分字符串 | getrange key start end | |
+
+### 1.3 哈希
+
+| 作用 | 格式 | 参数或示例 |
+| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 设置值 | hset key field value | hset user:1 name tom
hset user:1 age 12 |
+| 获取值 | hget key field | hget user:1 name |
+| 删除field | hdel key field [field ...] | |
+| 计算field个数 | hlen key | |
+| 批量设置或获取field-value | hmget key field [field]
hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin
hmget user:1 name city |
+| 判断field是否存在 | hexists key field | |
+| 获取所有field | hkeys key | |
+| 获取所有value | hvals key | |
+| 获取所有的filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。
获取全部 可以使用hscan命令, 该命令会渐进式遍历哈希类型 |
+| 计数 | hincrby key field
hincrbyfloat key field | |
+
+### 1.4 列表
+
+| 作用 | 格式 | 参数或示例 |
+| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 增 | 左侧插入:lpush key value [value ...] 右侧插入:rpush key value [value ...] 某个指定元素前后插入:linsert key before\|after pivot value | |
+| 查 | 获取指定范围内的元素列表:lrange key start end 获取列表指定索引下标的元素:lindex key index 获取列表指定长度:llen key | lrange listkey 0 -1 |
+| 删 | 从列表左侧弹出元素:lpop key 从列表右侧弹出元素:rpop key 删除指定元素:lrem key count value 截取列表:ltrim key start end | count>0, 从左到右, 删除最多count个元素。
count<0, 从右到左, 删除最多count绝对值个元素。
count=0, 删除所有 |
+| 改 | 修改指定索引下标的元素:lset key index newValue | |
+| 阻塞操作 | blpop key [key ...] timeout brpop key [key ...] timeout | key[key...]: 多个列表的键。 timeout: 阻塞时间\|等待时间(单位: 秒) |
+
+
+
+### 1.5 集合
+
+集合(set) 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素**, 并且集合中的元素是无序的, **不能通过索引下标获取元素**。
+
+**集合内操作**:
+
+| 作用 | 格式 | 参数或示例 |
+| -------------------- | ------------------------------ | ----------------------------------------- |
+| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 |
+| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 |
+| 计算元素个数 | scard key | |
+| 判断元素是否在集合中 | sismember key element | |
+| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素,count 默认为1 |
+| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素,spop 会 |
+| 获取集合中所有元素 | smembers key | 可用sscan 代替 |
+
+**集合间操作**:
+
+| 作用 | 格式 |
+| ---------------------------- | ------------------------------------------------------------ |
+| 求多个集合的交集 | sinter key [key ...] |
+| 求多个集合的并集 | suinon key [key ...] |
+| 求多个集合的差集 | sdiff key [key ...] |
+| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...] |
+
+### 1.6 有序集合
+
+有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数(score) 作为排序的依据。
+
+**集合内操作**:
+
+| 作用 | 格式 | 参数或示例 |
+| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| 添加成员 | zadd key score member [score member ...] | nx: member必须不存在, 才可设置成功, 用于添加。
xx: member必须存在, 才可以设置成功, 用于更新。
ch: 返回此次操作后, 有序集合元素和分数发生变化的个数
incr: 对score做增加, 相当于后面介绍的zincrby。 |
+| 计算成员个数 | zcard key | |
+| 计算某个成员的分数 | zscore key member | |
+| 计算某个成员的排名 | zrank key member zrevrank key member | zrank是从分数从低到高返回排名, zrevrank反之。 |
+| 删除成员 | zrem key member [member ...] | |
+| 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom |
+| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回, zrevrange反之。 |
+| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count] zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回, zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间(小括号) 和闭区间(中括号) , -inf和+inf分别代表无限小和无限大 |
+| 删除指定排名内的升序元素 | zremrangerank key start end | |
+| 删除指定分数范围的成员 | zremrangebyscore key min max | |
+
+**集合间操作**:
+
+| 作用 | 格式 |
+| ---- | ------------------------------------------------------------ |
+| 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
+| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] |
+
+- destination: 交集计算结果保存到这个键。
+- numkeys: 需要做交集计算键的个数。
+- key[key...]: 需要做交集计算的键。
+- weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重, 每个键的权重默认是1。
+- aggregate sum|min|max: 计算成员交集后, 分值可以按照sum(和) 、min(最小值) 、 max(最大值) 做汇总, 默认值是sum。
+
+### 1.7 键管理
+
+#### 1.7.1 单个键管理
+
+##### 1.键重命名
+
+**rename key newkey**
+
+ 为了防止被强行rename, Redis提供了renamenx命令, 确保只有newKey不存在时候才被覆盖。
+
+##### 2. 随机返回键
+
+ **random key**
+
+##### 3.键过期
+
+- expire key seconds: 键在seconds秒后过期。
+- expireat key timestamp: 键在秒级时间戳timestamp后过期。
+- pexpire key milliseconds: 键在milliseconds毫秒后过期。
+- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期
+
+注意:
+
+1. 如果expire key的键不存在, 返回结果为0
+2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样
+3. persist key t命令可以将键的过期时间清除
+4. 对于字符串类型键, 执行set命令会去掉过期时间, 这个问题很容易在开发中被忽视
+5. Redis不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置
+6. setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间
+
+#### 1.7.2 键遍历
+
+##### 1. 全量键遍历
+
+**keys pattern**
+
+##### 2. 渐进式遍历
+
+scan cursor \[match pattern] \[count number]
+
+- cursor是必需参数, 实际上cursor是一个游标, 第一次遍历从0开始, 每次scan遍历完都会返回当前游标的值, 直到游标值为0, 表示遍历结束。
+- match pattern是可选参数, 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。
+- count number是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是10, 此参数可以适当增大。
+
+#### 1.7.3 数据库管理
+
+##### 1.切换数据库
+
+**select dbIndex**
+
+##### 2.flushdb/flushall
+
flushdb/flushall命令用于清除数据库, 两者的区别的是flushdb只清除当前数据库, flushall会清除所有数据库。
\ No newline at end of file
diff --git a/spring/spring-scheduling-annotation/README.md b/spring/spring-scheduling-annotation/README.md
index f7f5c7e..8d92441 100644
--- a/spring/spring-scheduling-annotation/README.md
+++ b/spring/spring-scheduling-annotation/README.md
@@ -1,192 +1,201 @@
-# spring 定时任务(注解方式)
-
-## 一、说明
-
-### 1.1 项目结构说明
-
-关于任务的调度配置定义在ServletConfig.java中,为方便观察项目定时执行的情况,项目以web的方式构建。
-
-
+# spring 定时任务(注解方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring scheduling
+ 2.1 创建定时任务
+ 2.2 配置定时任务
+## 正文
-
-### 1.2 依赖说明
-
-导入基本依赖
-
-```xml
-
-
- 4.0.0
-
- com.heibaiying
- spring-scheduling
- 1.0-SNAPSHOT
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
- 8
- 8
-
-
-
-
-
- 5.1.3.RELEASE
-
-
-
-
- org.springframework
- spring-context
- ${spring-base-version}
-
-
- org.springframework
- spring-beans
- ${spring-base-version}
-
-
- org.springframework
- spring-core
- ${spring-base-version}
-
-
- org.springframework
- spring-web
- ${spring-base-version}
-
-
- org.springframework
- spring-webmvc
- ${spring-base-version}
-
-
- javax.servlet
- javax.servlet-api
- 4.0.1
- provided
-
-
-
-
-```
-
-
-
-## 二、spring scheduling
-
-#### 2.1 创建定时任务
-
-```java
-/**
- * @author : heibaiying
- */
-@Component
-public class Task {
-
- /**
- * 基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。
- */
- @Scheduled(fixedDelay = 5000, initialDelay = 1000)
- public void methodA() {
- Thread thread = Thread.currentThread();
- System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
- thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
- }
-
- /**
- * 基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。
- */
- @Scheduled(fixedRate = 5000)
- @Async
- public void methodB() throws InterruptedException {
- Thread thread = Thread.currentThread();
- System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
- thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
- Thread.sleep(10 * 1000);
- }
-
- @Scheduled(cron = "0/10 * * * * ?")
- public void methodC() {
- Thread thread = Thread.currentThread();
- System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
- thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
- }
-}
-
-```
-
-#### 2.2 配置定时任务
-
-```java
-/**
- * @author : heibaiying
- * spring 主配置类
- */
-@Configuration
-@EnableWebMvc
-@EnableScheduling //启用Spring的计划任务执行功能
-@EnableAsync //启用Spring的异步方法执行功能
-@ComponentScan(basePackages = {"com.heibaiying.task"})
-public class ServletConfig implements WebMvcConfigurer, AsyncConfigurer, SchedulingConfigurer {
-
- private ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
-
- // 任务执行器线程池配置
- @Override
- public Executor getAsyncExecutor() {
- executor.setCorePoolSize(5);
- executor.setMaxPoolSize(10);
- executor.setQueueCapacity(100);
- executor.setThreadNamePrefix("MyExecutor-");
- executor.initialize();
- return executor;
- }
-
- // 这个方法可以监听到异步程序发生的错误
- @Override
- public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
- return new AsyncUncaughtExceptionHandler() {
- @Override
- public void handleUncaughtException(Throwable ex, Method method, Object... params) {
- System.out.println(method.getName() + "发生错误:" + ex.getMessage());
- }
- };
- }
-
- // 如果程序结束,需要关闭线程池 不然程序无法完全退出 只能kill才能完全退出
- @PreDestroy
- public void destroy() {
- if (executor != null) {
- executor.shutdown();
- }
- }
-
- // 调度程序线程池配置
- @Override
- public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
- taskRegistrar.setScheduler(taskExecutor());
- }
-
- // 如果程序结束,需要关闭线程池
- @Bean(destroyMethod = "shutdown")
- public Executor taskExecutor() {
- return Executors.newScheduledThreadPool(50);
- }
-}
-```
-
-**关于调度程序线程池作用说明**:
-
-按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
-
-**关于任务执行线程池作用说明**:
-
-按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
-
+## 一、说明
+
+### 1.1 项目结构说明
+
+关于任务的调度配置定义在ServletConfig.java中,为方便观察项目定时执行的情况,项目以web的方式构建。
+
+
+
+
+
+### 1.2 依赖说明
+
+导入基本依赖
+
+```xml
+
+
+ 4.0.0
+
+ com.heibaiying
+ spring-scheduling
+ 1.0-SNAPSHOT
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
+ 5.1.3.RELEASE
+
+
+
+
+ org.springframework
+ spring-context
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-beans
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-core
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-web
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-webmvc
+ ${spring-base-version}
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+
+
+```
+
+
+
+## 二、spring scheduling
+
+#### 2.1 创建定时任务
+
+```java
+/**
+ * @author : heibaiying
+ */
+@Component
+public class Task {
+
+ /**
+ * 基于间隔的触发器,其中间隔是从上一个任务的 完成时间 开始计算, 时间单位值以毫秒为单位。
+ */
+ @Scheduled(fixedDelay = 5000, initialDelay = 1000)
+ public void methodA() {
+ Thread thread = Thread.currentThread();
+ System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
+ thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
+ }
+
+ /**
+ * 基于间隔的触发器,其中间隔是从上一个任务的 开始时间 开始测量的。
+ */
+ @Scheduled(fixedRate = 5000)
+ @Async
+ public void methodB() throws InterruptedException {
+ Thread thread = Thread.currentThread();
+ System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
+ thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
+ Thread.sleep(10 * 1000);
+ }
+
+ @Scheduled(cron = "0/10 * * * * ?")
+ public void methodC() {
+ Thread thread = Thread.currentThread();
+ System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
+ thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
+ }
+}
+
+```
+
+#### 2.2 配置定时任务
+
+```java
+/**
+ * @author : heibaiying
+ * spring 主配置类
+ */
+@Configuration
+@EnableWebMvc
+@EnableScheduling //启用Spring的计划任务执行功能
+@EnableAsync //启用Spring的异步方法执行功能
+@ComponentScan(basePackages = {"com.heibaiying.task"})
+public class ServletConfig implements WebMvcConfigurer, AsyncConfigurer, SchedulingConfigurer {
+
+ private ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+
+ // 任务执行器线程池配置
+ @Override
+ public Executor getAsyncExecutor() {
+ executor.setCorePoolSize(5);
+ executor.setMaxPoolSize(10);
+ executor.setQueueCapacity(100);
+ executor.setThreadNamePrefix("MyExecutor-");
+ executor.initialize();
+ return executor;
+ }
+
+ // 这个方法可以监听到异步程序发生的错误
+ @Override
+ public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+ return new AsyncUncaughtExceptionHandler() {
+ @Override
+ public void handleUncaughtException(Throwable ex, Method method, Object... params) {
+ System.out.println(method.getName() + "发生错误:" + ex.getMessage());
+ }
+ };
+ }
+
+ // 如果程序结束,需要关闭线程池 不然程序无法完全退出 只能kill才能完全退出
+ @PreDestroy
+ public void destroy() {
+ if (executor != null) {
+ executor.shutdown();
+ }
+ }
+
+ // 调度程序线程池配置
+ @Override
+ public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+ taskRegistrar.setScheduler(taskExecutor());
+ }
+
+ // 如果程序结束,需要关闭线程池
+ @Bean(destroyMethod = "shutdown")
+ public Executor taskExecutor() {
+ return Executors.newScheduledThreadPool(50);
+ }
+}
+```
+
+**关于调度程序线程池作用说明**:
+
+按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
+
+**关于任务执行线程池作用说明**:
+
+按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
+
diff --git a/spring/spring-scheduling/README.md b/spring/spring-scheduling/README.md
index 2131f0e..442ca9a 100644
--- a/spring/spring-scheduling/README.md
+++ b/spring/spring-scheduling/README.md
@@ -1,183 +1,192 @@
-# spring 定时任务(xml配置方式)
-
-## 一、说明
-
-### 1.1 项目结构说明
-
-关于任务的调度配置定义在springApplication.xml中,为方便观察项目定时执行的情况,项目以web的方式构建。
-
-
+# spring 定时任务(xml配置方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring scheduling
+ 2.1 创建定时任务
+ 2.2 配置定时任务
+## 正文
-
-### 1.2 依赖说明
-
-导入基本依赖
-
-```xml
-
-
- 4.0.0
-
- com.heibaiying
- spring-scheduling
- 1.0-SNAPSHOT
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
- 8
- 8
-
-
-
-
-
- 5.1.3.RELEASE
-
-
-
-
- org.springframework
- spring-context
- ${spring-base-version}
-
-
- org.springframework
- spring-beans
- ${spring-base-version}
-
-
- org.springframework
- spring-core
- ${spring-base-version}
-
-
- org.springframework
- spring-web
- ${spring-base-version}
-
-
- org.springframework
- spring-webmvc
- ${spring-base-version}
-
-
- javax.servlet
- javax.servlet-api
- 4.0.1
- provided
-
-
-
-
-```
-
-
-
-## 二、spring scheduling
-
-#### 2.1 创建定时任务
-
-```java
-package com.heibaiying.task;
-
-import org.springframework.scheduling.annotation.Async;
-import org.springframework.scheduling.annotation.Scheduled;
-
-import java.time.LocalDateTime;
-
-/**
- * @author : heibaiying
- */
-public class Task {
-
- public void methodA() {
- Thread thread = Thread.currentThread();
- System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
- thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
- }
-
- @Async
- public void methodB() throws InterruptedException {
- Thread thread = Thread.currentThread();
- System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
- thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
- Thread.sleep(10 * 1000);
- }
-
- public void methodC() {
- Thread thread = Thread.currentThread();
- System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
- thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
- }
-}
-
-```
-
-#### 2.2 配置定时任务
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-**关于调度程序线程池作用说明**:
-
-按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
-
-**关于任务执行线程池作用说明**:
-
-按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
-
+## 一、说明
+
+### 1.1 项目结构说明
+
+关于任务的调度配置定义在springApplication.xml中,为方便观察项目定时执行的情况,项目以web的方式构建。
+
+
+
+
+
+### 1.2 依赖说明
+
+导入基本依赖
+
+```xml
+
+
+ 4.0.0
+
+ com.heibaiying
+ spring-scheduling
+ 1.0-SNAPSHOT
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
+
+
+
+ 5.1.3.RELEASE
+
+
+
+
+ org.springframework
+ spring-context
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-beans
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-core
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-web
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-webmvc
+ ${spring-base-version}
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+
+
+```
+
+
+
+## 二、spring scheduling
+
+#### 2.1 创建定时任务
+
+```java
+package com.heibaiying.task;
+
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author : heibaiying
+ */
+public class Task {
+
+ public void methodA() {
+ Thread thread = Thread.currentThread();
+ System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
+ thread.getName(), thread.getId(), "methodA方法执行", LocalDateTime.now()));
+ }
+
+ @Async
+ public void methodB() throws InterruptedException {
+ Thread thread = Thread.currentThread();
+ System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
+ thread.getName(), thread.getId(), "methodB方法执行", LocalDateTime.now()));
+ Thread.sleep(10 * 1000);
+ }
+
+ public void methodC() {
+ Thread thread = Thread.currentThread();
+ System.out.println(String.format("线程名称:%s ; 线程ID:%s ; 调用方法:%s ; 调用时间:%s",
+ thread.getName(), thread.getId(), "methodC方法执行", LocalDateTime.now()));
+ }
+}
+
+```
+
+#### 2.2 配置定时任务
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**关于调度程序线程池作用说明**:
+
+按照例子 我们有methodA 、 methodB 、methodC 三个方法 其中 methodB 是耗时的方法如果不声明调度程序线程池 则methodB 会阻塞 methodA 、methodC 方法的执行 因为调度程序是单线程的
+
+**关于任务执行线程池作用说明**:
+
+按照例子 如果我们声明 methodB 是按照 fixedRate=5000 方法执行的 ,理论上不管任务耗时多久,任务都应该是每5秒执行一次,但是实际上任务是被加入执行队列,也不会立即被执行,因为默认执行任务是单线程的,这个时候需要开启@EnableAsync 并指定方法是 @Async 异步的,并且配置执行任务线程池(如果不配置就使用默认的线程池配置)
+
diff --git a/spring/spring-websocket-annotation/README.md b/spring/spring-websocket-annotation/README.md
index f68c97f..44000a4 100644
--- a/spring/spring-websocket-annotation/README.md
+++ b/spring/spring-websocket-annotation/README.md
@@ -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的方式构建。
-
-
+# spring websocket(注解方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring websocket
+ 2.1 创建消息处理类,继承自TextWebSocketHandler
+ 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
+ 2.3 创建websocket的配置类
+ 2.4 前端 websocket 的实现
+ 2.5 简单登录的实现
+## 正文
-
-### 1.2 依赖说明
-
-除了基本的spring 依赖外,还需要导入webSocket的依赖包
-
-```xml
-
-
- org.springframework
- spring-websocket
- 5.1.3.RELEASE
-
-```
-
-
-
-## 二、spring websocket
-
-#### 2.1 创建消息处理类,继承自TextWebSocketHandler
-
-```java
-/**
- * @author : heibaiying
- * @description : 自定义消息处理类
- */
-public class CustomHandler extends TextWebSocketHandler {
-
- private Map nameAndSession = new ConcurrentHashMap<>();
-
- // 建立连接时候触发
- @Override
- public void afterConnectionEstablished(WebSocketSession session) {
- String username = getNameFromSession(session);
- nameAndSession.putIfAbsent(username, session);
- }
-
-
- // 关闭连接时候触发
- @Override
- public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
- String username = getNameFromSession(session);
- nameAndSession.remove(username);
- }
-
- // 处理消息
- @Override
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- // 防止中文乱码
- String msg = URLDecoder.decode(message.getPayload(), "utf-8");
- String username = getNameFromSession(session);
- // 简单模拟群发消息
- TextMessage reply = new TextMessage(username + " : " + msg);
- nameAndSession.forEach((s, webSocketSession)
- -> {
- try {
- webSocketSession.sendMessage(reply);
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
- }
-
-
- private String getNameFromSession(WebSocketSession session) {
- Map attributes = session.getAttributes();
- return (String) attributes.get(Constant.USER_NAME);
- }
-}
-
-```
-
-#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
-
-```java
-/**
- * @author : heibaiying
- * @description : 可以按照需求实现权限拦截等功能
- */
-public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
-
- @Override
- public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
- InetSocketAddress remoteAddress = request.getRemoteAddress();
- InetAddress address = remoteAddress.getAddress();
- System.out.println(address);
- /*
- * 最后需要要显示调用父类方法,父类的beforeHandshake方法
- * 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
- * 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
- * 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
- */
- return super.beforeHandshake(request, response, wsHandler, attributes);
- }
-}
-```
-
-#### 2.3 创建websocket的配置类
-
-```java
-/**
- * @author : heibaiying
- * @description :websocket 配置
- */
-@Configuration
-@EnableWebSocket
-public class WebSocketConfig implements WebSocketConfigurer {
-
- @Override
- public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
- registry.addHandler(new CustomHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
- }
-}
-```
-
-#### 2.4 前端 websocket 的实现
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- ${sessionScope.get("username")}您好!欢迎进入群聊大厅!
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.5 简单登录的实现
-
-```java
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- Title
-
-
-
-
-
-```
-
-```java
-@Controller
-public class LoginController {
-
- @PostMapping("login")
- public String login(String username, HttpSession session){
- session.setAttribute(Constant.USER_NAME,username);
- return "chat";
- }
-}
-```
-
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
+2. webconfig 包是基础注解的方式配置web,在spring-base-annotation项目中已经讲解过每个类作用;
+3. CustomHander为消息的自定义处理器;
+4. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
+5. webSocketConfig 是websocket 的主要配置类;
+6. 项目以web的方式构建。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了基本的spring 依赖外,还需要导入webSocket的依赖包
+
+```xml
+
+
+ org.springframework
+ spring-websocket
+ 5.1.3.RELEASE
+
+```
+
+
+
+## 二、spring websocket
+
+#### 2.1 创建消息处理类,继承自TextWebSocketHandler
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 自定义消息处理类
+ */
+public class CustomHandler extends TextWebSocketHandler {
+
+ private Map nameAndSession = new ConcurrentHashMap<>();
+
+ // 建立连接时候触发
+ @Override
+ public void afterConnectionEstablished(WebSocketSession session) {
+ String username = getNameFromSession(session);
+ nameAndSession.putIfAbsent(username, session);
+ }
+
+
+ // 关闭连接时候触发
+ @Override
+ public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
+ String username = getNameFromSession(session);
+ nameAndSession.remove(username);
+ }
+
+ // 处理消息
+ @Override
+ protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+ // 防止中文乱码
+ String msg = URLDecoder.decode(message.getPayload(), "utf-8");
+ String username = getNameFromSession(session);
+ // 简单模拟群发消息
+ TextMessage reply = new TextMessage(username + " : " + msg);
+ nameAndSession.forEach((s, webSocketSession)
+ -> {
+ try {
+ webSocketSession.sendMessage(reply);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+
+ private String getNameFromSession(WebSocketSession session) {
+ Map attributes = session.getAttributes();
+ return (String) attributes.get(Constant.USER_NAME);
+ }
+}
+
+```
+
+#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 可以按照需求实现权限拦截等功能
+ */
+public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
+
+ @Override
+ public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
+ InetSocketAddress remoteAddress = request.getRemoteAddress();
+ InetAddress address = remoteAddress.getAddress();
+ System.out.println(address);
+ /*
+ * 最后需要要显示调用父类方法,父类的beforeHandshake方法
+ * 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
+ * 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
+ * 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
+ */
+ return super.beforeHandshake(request, response, wsHandler, attributes);
+ }
+}
+```
+
+#### 2.3 创建websocket的配置类
+
+```java
+/**
+ * @author : heibaiying
+ * @description :websocket 配置
+ */
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig implements WebSocketConfigurer {
+
+ @Override
+ public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+ registry.addHandler(new CustomHandler(), "/socket").addInterceptors(new CustomHandshakeInterceptor());
+ }
+}
+```
+
+#### 2.4 前端 websocket 的实现
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ ${sessionScope.get("username")}您好!欢迎进入群聊大厅!
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.5 简单登录的实现
+
+```java
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Title
+
+
+
+
+
+```
+
+```java
+@Controller
+public class LoginController {
+
+ @PostMapping("login")
+ public String login(String username, HttpSession session){
+ session.setAttribute(Constant.USER_NAME,username);
+ return "chat";
+ }
+}
+```
+
diff --git a/spring/spring-websocket/README.md b/spring/spring-websocket/README.md
index 7cbcf34..9622f26 100644
--- a/spring/spring-websocket/README.md
+++ b/spring/spring-websocket/README.md
@@ -1,233 +1,245 @@
-# spring websocket(xml配置方式)
-
-## 一、说明
-
-### 1.1 项目结构说明
-
-1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
-2. CustomHander为消息的自定义处理器;
-3. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
-4. 项目以web的方式构建。
-
-
+# spring websocket(xml配置方式)
+
## 目录
+一、说明
+ 1.1 项目结构说明
+ 1.2 依赖说明
+二、spring websocket
+ 2.1 创建消息处理类,继承自TextWebSocketHandler
+ 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
+ 2.3 配置websocket
+ 2.4 前端 websocket 的实现
+ 2.5 简单登录的实现
+## 正文
-
-### 1.2 依赖说明
-
-除了基本的spring 依赖外,还需要导入webSocket的依赖包
-
-```xml
-
-
- org.springframework
- spring-websocket
- 5.1.3.RELEASE
-
-```
-
-
-
-## 二、spring websocket
-
-#### 2.1 创建消息处理类,继承自TextWebSocketHandler
-
-```java
-/**
- * @author : heibaiying
- * @description : 自定义消息处理类
- */
-public class CustomHandler extends TextWebSocketHandler {
-
- private Map nameAndSession = new ConcurrentHashMap<>();
-
- // 建立连接时候触发
- @Override
- public void afterConnectionEstablished(WebSocketSession session) {
- String username = getNameFromSession(session);
- nameAndSession.putIfAbsent(username, session);
- }
-
-
- // 关闭连接时候触发
- @Override
- public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
- String username = getNameFromSession(session);
- nameAndSession.remove(username);
- }
-
- // 处理消息
- @Override
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- // 防止中文乱码
- String msg = URLDecoder.decode(message.getPayload(), "utf-8");
- String username = getNameFromSession(session);
- // 简单模拟群发消息
- TextMessage reply = new TextMessage(username + " : " + msg);
- nameAndSession.forEach((s, webSocketSession)
- -> {
- try {
- webSocketSession.sendMessage(reply);
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
- }
-
-
- private String getNameFromSession(WebSocketSession session) {
- Map attributes = session.getAttributes();
- return (String) attributes.get(Constant.USER_NAME);
- }
-}
-
-```
-
-#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
-
-```java
-/**
- * @author : heibaiying
- * @description : 可以按照需求实现权限拦截等功能
- */
-public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
-
- @Override
- public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
- InetSocketAddress remoteAddress = request.getRemoteAddress();
- InetAddress address = remoteAddress.getAddress();
- System.out.println(address);
- /*
- * 最后需要要显示调用父类方法,父类的beforeHandshake方法
- * 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
- * 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
- * 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
- */
- return super.beforeHandshake(request, response, wsHandler, attributes);
- }
-}
-```
-
-#### 2.3 配置websocket
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.4 前端 websocket 的实现
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- ${sessionScope.get("username")}您好!欢迎进入群聊大厅!
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 2.5 简单登录的实现
-
-```java
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- Title
-
-
-
-
-
-```
-
-```java
-@Controller
-public class LoginController {
-
- @PostMapping("login")
- public String login(String username, HttpSession session){
- session.setAttribute(Constant.USER_NAME,username);
- return "chat";
- }
-}
-```
-
+## 一、说明
+
+### 1.1 项目结构说明
+
+1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在session当中;
+2. CustomHander为消息的自定义处理器;
+3. CustomHandershakerInterceptor为自定义的 websocket 的握手拦截器;
+4. 项目以web的方式构建。
+
+
+
+
+
+### 1.2 依赖说明
+
+除了基本的spring 依赖外,还需要导入webSocket的依赖包
+
+```xml
+
+
+ org.springframework
+ spring-websocket
+ 5.1.3.RELEASE
+
+```
+
+
+
+## 二、spring websocket
+
+#### 2.1 创建消息处理类,继承自TextWebSocketHandler
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 自定义消息处理类
+ */
+public class CustomHandler extends TextWebSocketHandler {
+
+ private Map nameAndSession = new ConcurrentHashMap<>();
+
+ // 建立连接时候触发
+ @Override
+ public void afterConnectionEstablished(WebSocketSession session) {
+ String username = getNameFromSession(session);
+ nameAndSession.putIfAbsent(username, session);
+ }
+
+
+ // 关闭连接时候触发
+ @Override
+ public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
+ String username = getNameFromSession(session);
+ nameAndSession.remove(username);
+ }
+
+ // 处理消息
+ @Override
+ protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+ // 防止中文乱码
+ String msg = URLDecoder.decode(message.getPayload(), "utf-8");
+ String username = getNameFromSession(session);
+ // 简单模拟群发消息
+ TextMessage reply = new TextMessage(username + " : " + msg);
+ nameAndSession.forEach((s, webSocketSession)
+ -> {
+ try {
+ webSocketSession.sendMessage(reply);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+
+ private String getNameFromSession(WebSocketSession session) {
+ Map attributes = session.getAttributes();
+ return (String) attributes.get(Constant.USER_NAME);
+ }
+}
+
+```
+
+#### 2.2 创建websocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的)
+
+```java
+/**
+ * @author : heibaiying
+ * @description : 可以按照需求实现权限拦截等功能
+ */
+public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
+
+ @Override
+ public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
+ InetSocketAddress remoteAddress = request.getRemoteAddress();
+ InetAddress address = remoteAddress.getAddress();
+ System.out.println(address);
+ /*
+ * 最后需要要显示调用父类方法,父类的beforeHandshake方法
+ * 把ServerHttpRequest 中session中对应的值拷贝到WebSocketSession中。
+ * 如果我们没有实现这个方法,我们在最后的handler处理中 是拿不到 session中的值
+ * 作为测试 可以注释掉下面这一行 可以发现自定义处理器中session的username总是为空
+ */
+ return super.beforeHandshake(request, response, wsHandler, attributes);
+ }
+}
+```
+
+#### 2.3 配置websocket
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.4 前端 websocket 的实现
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ ${sessionScope.get("username")}您好!欢迎进入群聊大厅!
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 2.5 简单登录的实现
+
+```java
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Title
+
+
+
+
+
+```
+
+```java
+@Controller
+public class LoginController {
+
+ @PostMapping("login")
+ public String login(String username, HttpSession session){
+ session.setAttribute(Constant.USER_NAME,username);
+ return "chat";
+ }
+}
+```
+
diff --git a/spring/springmvc-base-annotation/README.md b/spring/springmvc-base-annotation/README.md
index a9111ad..aec812f 100644
--- a/spring/springmvc-base-annotation/README.md
+++ b/spring/springmvc-base-annotation/README.md
@@ -1,937 +1,953 @@
-# springmvc基础(基于注解)
-
-
-
-## 一、搭建hello spring工程
-
-### 1.1 项目搭建
-
-1.新建maven web工程,并引入相应的依赖
-
-```xml
-
- 5.1.3.RELEASE
-
-
-
-
- org.springframework
- spring-context
- ${spring-base-version}
-
-
- org.springframework
- spring-beans
- ${spring-base-version}
-
-
- org.springframework
- spring-core
- ${spring-base-version}
-
-
- org.springframework
- spring-web
- ${spring-base-version}
-
-
- org.springframework
- spring-webmvc
- ${spring-base-version}
-
-
- javax.servlet
- javax.servlet-api
- 4.0.1
- provided
-
-
-```
-
-2.得益于servlet3.0和spring的支持,我们可以在没有web.xml的情况下完成关于servlet配置。
-
- 新建DispatcherServletInitializer.java文件,这个类的作用相当于我们在xml方式下web.xml中配置的DispatcherServlet
-
-```java
-package com.heibaiying.config;
-
-import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
-
-/**
- * @author : heibaiying
- */
-
-public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
-
- protected Class>[] getRootConfigClasses() {
- return new Class[0];
- }
-
- protected Class>[] getServletConfigClasses() {
- return new Class[]{ServletConfig.class};
- }
-
- protected String[] getServletMappings() {
- return new String[]{"/"};
- }
-}
-
-```
-
-3.新建ServletConfig.java,文件内容如下(这个类相当于我们在xml配置方式中的springApplication.xml)
-
-```java
-package com.heibaiying.config;
-
-import com.heibaiying.exception.NoAuthExceptionResolver;
-import com.heibaiying.interceptors.MyFirstInterceptor;
-import com.heibaiying.interceptors.MySecondInterceptor;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.servlet.HandlerExceptionResolver;
-import org.springframework.web.servlet.ViewResolver;
-import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
-import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import org.springframework.web.servlet.view.InternalResourceViewResolver;
-
-import java.util.List;
-
-/**
- * @author : heibaiying
- */
-@Configuration
-@EnableWebMvc
-@ComponentScan(basePackages = {"com.heibaiying.controller"})
-public class ServletConfig implements WebMvcConfigurer {
-
- /**
- * 配置视图解析器
- */
- @Bean
- public ViewResolver viewResolver() {
- InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
- internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
- internalResourceViewResolver.setSuffix(".jsp");
- internalResourceViewResolver.setExposeContextBeansAsAttributes(true);
- return internalResourceViewResolver;
- }
-
- /**
- * 配置静态资源处理器
- */
- public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
- configurer.enable();
- }
-
-}
-
-```
-
-4.在src 下新建controller用于测试
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.exception.NoAuthException;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-
-/**
- * @author : heibaiying
- * @description : hello spring
- */
-
-@Controller
-@RequestMapping("mvc")
-public class HelloController {
-
- @RequestMapping("hello")
- private String hello() {
- return "hello";
- }
-
-}
-
-```
-
-5.在WEB-INF 下新建jsp文件夹,新建hello.jsp 文件
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- Title
-
-
- Hello Spring MVC!
-
-
-```
-
-6.启动tomcat服务,访问localhost:8080/mvc/hello
-
-### 1.2 相关注解说明
-
-**1.@Configuration**
-
-@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
-
-**2.@EnableWebMvc**
-
-简单的说就是提供了部分springmvc的功能,例如格式转换和参数绑定。
-
-
-
-## 二、配置自定义拦截器
-
-1.创建自定义拦截器,实现接口HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
-
-```java
-package com.heibaiying.interceptors;
-
-import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * @author : heibaiying
- * @description : spring5 中 preHandle,postHandle,afterCompletion 在接口中被声明为默认方法
- */
-public class MyFirstInterceptor implements HandlerInterceptor {
-
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
- System.out.println("进入第一个拦截器preHandle");
- return true;
- }
-
- // 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入afterCompletion这个方法
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
- System.out.println("进入第一个拦截器postHandle");
- }
-
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
- System.out.println("进入第一个拦截器afterCompletion");
- }
-}
-```
-
-```java
-package com.heibaiying.interceptors;
-
-import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * @author : heibaiying
- * @description : spring5 中 preHandle,postHandle,afterCompletion 在接口中被声明为默认方法
- */
-public class MySecondInterceptor implements HandlerInterceptor {
-
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
- System.out.println("进入第二个拦截器preHandle");
- return true;
- }
-
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
- System.out.println("进入第二个拦截器postHandle");
- }
-
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
- System.out.println("进入第二个拦截器afterCompletion");
- }
-}
-
-```
-
-2.在ServletConfig.java中注册自定义拦截器
-
-```java
- /**
- * 添加自定义拦截器
- */
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/mvc/**").excludePathPatterns("mvc/login");
- registry.addInterceptor(new MySecondInterceptor()).addPathPatterns("/mvc/**");
- }
-```
-
-3.关于多个拦截器方法执行顺序的说明
-
-拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
-
-
-
-## 三、全局异常处理
-
-1.定义自定义异常
-
-```java
-package com.heibaiying.exception;
-
-/**
- * @author : heibaiying
- * @description : 自定义无权限异常
- */
-public class NoAuthException extends RuntimeException {
-
- public NoAuthException() {
- super();
- }
-
- public NoAuthException(String message) {
- super(message);
- }
-
- public NoAuthException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public NoAuthException(Throwable cause) {
- super(cause);
- }
-
-}
-
-```
-
-2.实现自定义异常处理器
-
-```java
-package com.heibaiying.exception;
-
-import org.springframework.web.servlet.HandlerExceptionResolver;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * @author : heibaiying
- * @description : 无权限异常处理机制
- */
-public class NoAuthExceptionResolver implements HandlerExceptionResolver {
-
- public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
- if (ex instanceof NoAuthException && !isAjax(request)) {
- return new ModelAndView("NoAuthPage");
- }
- return new ModelAndView();
- }
-
- // 判断是否是Ajax请求
- private boolean isAjax(HttpServletRequest request) {
- return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
- }
-}
-
-```
-
-3.在ServletConfig.java注册自定义异常处理器
-
-```java
-/**
-* 添加全局异常处理器
-*/
-public void configureHandlerExceptionResolvers(List resolvers) {
- resolvers.add(new NoAuthExceptionResolver());
-}
-```
-
-4.定义测试controller,抛出自定义异常
-
-```java
-@Controller
-@RequestMapping("mvc")
-public class HelloController {
-
- @RequestMapping("hello")
- private String hello() {
- return "hello";
- }
-
-
- @RequestMapping("auth")
- private void auth() {
- throw new NoAuthException("没有对应的访问权限!");
- }
-}
-```
-
-注:调用这个controller时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入postHandle这个方法 但一定会进入afterCompletion这个方法
-
-
-
-## 四、参数绑定
-
-### 4.1 参数绑定
-
-1.新建Programmer.java
-
-```java
-package com.heibaiying.bean;
-
-import lombok.Data;
-
-/**
- * @author : heibaiying
- * @description :
- */
-@Data
-public class Programmer {
-
- private String name;
-
- private int age;
-
- private float salary;
-
- private String birthday;
-}
-
-```
-
-注:@Data 是lombok包下的注解,用来生成相应的set、get方法,使得类的书写更为简洁。
-
-2.新建ParamBindController.java 文件
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.bean.Programmer;
-import org.springframework.format.annotation.DateTimeFormat;
-import org.springframework.format.datetime.DateFormatter;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.web.bind.WebDataBinder;
-import org.springframework.web.bind.annotation.InitBinder;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-
-import java.util.Date;
-
-/**
- * @author : heibaiying
- * @description :参数绑定
- */
-@Controller
-public class ParamBindController {
-
- @InitBinder
- protected void initBinder(WebDataBinder binder) {
- binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
- }
-
-
- // 参数绑定与日期格式转换
- @RequestMapping("param")
- public String param(String name, int age, double salary, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday, Model model) {
- model.addAttribute("name", name);
- model.addAttribute("age", age);
- model.addAttribute("salary", salary);
- model.addAttribute("birthday", birthday);
- return "param";
- }
-
- @RequestMapping("param2")
- public String param2(String name, int age, double salary, Date birthday, Model model) {
- model.addAttribute("name", name);
- model.addAttribute("age", age);
- model.addAttribute("salary", salary);
- model.addAttribute("birthday", birthday);
- return "param";
- }
-
-
- @PostMapping("param3")
- public String param3(Programmer programmer, String extendParam, Model model) {
- System.out.println("extendParam" + extendParam);
- model.addAttribute("p", programmer);
- return "param";
- }
-
-}
-
-```
-
-3.新建param.jsp 文件
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- Restful
-
-
-
- - 姓名:${empty name ? p.name : name}
- - 年龄:${empty age ? p.age : age}
- - 薪酬:${empty salary ? p.salary : salary}
- - 生日:${empty birthday ? p.birthday : birthday}
-
-
-
-
-```
-
-4.启动tomcat,用[postman](https://www.getpostman.com/)软件发送请求进行测试
-
-### 4.2 关于日期格式转换的三种方法
-
-1.如上实例代码所示,在对应的controller中初始化绑定
-
-```java
-@InitBinder
- protected void initBinder(WebDataBinder binder) {
- binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
- }
-```
-
-2.利用@DateTimeFormat注解,如果是用实体类去接收参数,则在对应的属性上声明
-
-```java
-public String param(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday)
-```
-
-3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在ServletConfig.java中进行注册
-
-```java
-package com.heibaiying.convert;
-
-import org.springframework.core.convert.converter.Converter;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-/**
- * @author : heibaiying
- * @description :
- */
-public class CustomDateConverter implements Converter {
-
- public Date convert(String s) {
- try {
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- return simpleDateFormat.parse(s);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
-}
-
-```
-
-ServletConfig.java
-
-```java
-/**
- * 添加全局日期处理
- */
-public void addFormatters(FormatterRegistry registry) {
- registry.addConverter(new CustomDateConverter());
-}
-```
-
-
-
-## 五、数据校验
-
-1.spring支持的数据校验是JSR303的标准,需要引入依赖的jar包
-
-```java
-
-
- org.hibernate.validator
- hibernate-validator
- 6.0.13.Final
-
-
- javax.validation
- validation-api
- 2.0.1.Final
-
-```
-
-2.新建测试ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上bindingResult参数,这个参数中包含了校验的结果
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.bean.Programmer;
-import org.hibernate.validator.constraints.Length;
-import org.springframework.format.annotation.DateTimeFormat;
-import org.springframework.format.datetime.DateFormatter;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.validation.BindingResult;
-import org.springframework.validation.ObjectError;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.WebDataBinder;
-import org.springframework.web.bind.annotation.InitBinder;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.validation.constraints.Min;
-import javax.validation.constraints.NotNull;
-import java.util.Date;
-import java.util.List;
-
-/**
- * @author : heibaiying
- * @description :数据校验
- */
-@RestController
-public class ParamValidController {
-
-
- @PostMapping("validate")
- public void valid(@Validated Programmer programmer,
- BindingResult bindingResult) {
- List allErrors = bindingResult.getAllErrors();
- for (ObjectError error : allErrors) {
- System.out.println(error.getDefaultMessage());
- }
- }
-
-}
-
-```
-
-3.在Programmer.java的对应属性上加上注解约束(支持的注解可以在javax.validation.constraints包中查看)
-
-```java
-package com.heibaiying.bean;
-
-import lombok.Data;
-
-import javax.validation.constraints.Min;
-import javax.validation.constraints.NotNull;
-
-/**
- * @author : heibaiying
- * @description :
- */
-@Data
-public class Programmer {
-
- @NotNull
- private String name;
-
- @Min(value = 0,message = "年龄不能为负数!" )
- private int age;
-
- @Min(value = 0,message = "薪酬不能为负数!" )
- private float salary;
-
- private String birthday;
-}
-
-```
-
-## 六、文件上传与下载
-
-#### 6.1 文件上传
-
-1.在ServletConfig.java中进行配置,使之支持文件上传
-
-```java
-/**
-* 配置文件上传
-*/
-@Bean
-public CommonsMultipartResolver multipartResolver(){
- CommonsMultipartResolver resolver = new CommonsMultipartResolver();
- resolver.setMaxUploadSize(1024*1000*10);
- resolver.setMaxUploadSizePerFile(1024*1000);
- resolver.setDefaultEncoding("utf-8");
- return resolver;
-}
-```
-
-2.新建测试上传的FileController.java
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.utils.FileUtil;
-import org.apache.commons.io.FileUtils;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.multipart.MultipartFile;
-
-import javax.servlet.http.HttpSession;
-import java.io.File;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-
-/**
- * @author : heibaiying
- * @description : 文件上传
- */
-
-@Controller
-public class FileController {
-
- @GetMapping("file")
- public String filePage() {
- return "file";
- }
-
-
- /***
- * 单文件上传
- */
- @PostMapping("upFile")
- public String upFile(MultipartFile file, HttpSession session) {
- //保存在项目根目录下image文件夹下,如果文件夹不存在则创建
- FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
- // success.jsp 就是一个简单的成功页面
- return "success";
- }
-
- /***
- * 多文件上传 多个文件用同一个名字
- */
- @PostMapping("upFiles")
- public String upFiles(@RequestParam(name = "file") MultipartFile[] files, HttpSession session) {
- for (MultipartFile file : files) {
- FileUtil.saveFile(file, session.getServletContext().getRealPath("images"));
- }
- return "success";
- }
-
- /***
- * 多文件上传方式2 分别为不同文件指定不同名字
- */
- @PostMapping("upFiles2")
- public String upFile(String extendParam,
- @RequestParam(name = "file1") MultipartFile file1,
- @RequestParam(name = "file2") MultipartFile file2, HttpSession session) {
- String realPath = session.getServletContext().getRealPath("images2");
- FileUtil.saveFile(file1, realPath);
- FileUtil.saveFile(file2, realPath);
- System.out.println("extendParam:" + extendParam);
- return "success";
- }
-}
-
-```
-
-3.其中工具类FileUtil.java代码如下
-
-```java
-package com.heibaiying.utils;
-
-import org.springframework.web.multipart.MultipartFile;
-import java.io.*;
-
-/**
- * @author : heibaiying
- * @description : 文件上传工具类
- */
-
-public class FileUtil {
-
- public static String saveFile(MultipartFile file, String path) {
- InputStream inputStream = null;
- FileOutputStream outputStream = null;
- String fullPath = path + File.separator + file.getOriginalFilename();
- try {
- File saveDir = new File(path);
- if (!saveDir.exists()) {
- saveDir.mkdirs();
- }
- outputStream = new FileOutputStream(new File(fullPath));
- inputStream = file.getInputStream();
- byte[] bytes = new byte[1024 * 1024];
- int read;
- while ((read = inputStream.read(bytes)) != -1) {
- outputStream.write(bytes, 0, read);
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (outputStream != null) {
- try {
- outputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- return fullPath;
- }
-
-}
-```
-
-4.新建用于上传的jsp页面,上传文件时表单必须声明 enctype="multipart/form-data"
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- 文件上传
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 6.2 文件下载
-
-1.在fileController.java中加上方法:
-
-```java
- /***
- * 上传用于下载的文件
- */
- @PostMapping("upFileForDownload")
- public String upFileForDownload(MultipartFile file, HttpSession session, Model model) throws UnsupportedEncodingException {
- String path = FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
- model.addAttribute("filePath", URLEncoder.encode(path,"utf-8"));
- model.addAttribute("fileName", file.getOriginalFilename());
- return "fileDownload";
- }
-
- /***
- * 下载文件
- */
- @GetMapping("download")
- public ResponseEntity downloadFile(String filePath) throws IOException {
- HttpHeaders headers = new HttpHeaders();
- File file = new File(filePath);
- // 解决文件名中文乱码
- String fileName=new String(file.getName().getBytes("UTF-8"),"iso-8859-1");
- headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
- headers.setContentDispositionFormData("attachment", fileName);
-
- return new ResponseEntity(FileUtils.readFileToByteArray(file),
- headers, HttpStatus.CREATED);
- }
-```
-
-2.其中fileDownload.jsp 如下:
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- 文件下载
-
-
- ${fileName}
-
-
-```
-
-
-
-## 七、Restful风格的请求
-
-1.新建Pet.java实体类
-
-```java
-package com.heibaiying.bean;
-
-import lombok.Data;
-
-/**
- * @author : heibaiying
- * @description :测试restful风格的实体类
- */
-
-@Data
-public class Pet {
-
- private String ownerId;
-
- private String petId;
-}
-
-```
-
-2.新建RestfulController.java,用@PathVariable和@ModelAttribute注解进行参数绑定。
-
-注: 在REST中,资源通过URL进行识别和定位。REST中的行为是通过HTTP方法定义的。在进行不同行为时对应HTTP方法和Spring注解分别如下:
-
-- 创建资源时:POST(PostMapping)
-- 读取资源时:GET( @GetMapping)
-- 更新资源时:PUT或PATCH(PutMapping、PatchMapping)
-- 删除资源时:DELETE(DeleteMapping)
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.bean.Pet;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-/**
- * @author : heibaiying
- * @description : Restful 风格的请求
- */
-
-@RestController
-public class RestfulController {
-
- @GetMapping("restful/owners/{ownerId}/pets/{petId}")
- public void get(@PathVariable String ownerId, @PathVariable String petId) {
- System.out.println("ownerId:" + ownerId);
- System.out.println("petId:" + petId);
- }
-
- @GetMapping("restful2/owners/{ownerId}/pets/{petId}")
- public void get(@ModelAttribute Pet pet) {
- System.out.println("ownerId:" + pet.getOwnerId());
- System.out.println("petId:" + pet.getPetId());
- }
-
-}
-
-```
-
+# springmvc基础(基于注解)
+
+
+
## 目录
+一、搭建hello spring工程
+ 1.1 项目搭建
+ 1.2 相关注解说明
+二、配置自定义拦截器
+三、全局异常处理
+四、参数绑定
+ 4.1 参数绑定
+ 4.2 关于日期格式转换的三种方法
+五、数据校验
+六、文件上传与下载
+ 6.1 文件上传
+ 6.2 文件下载
+七、Restful风格的请求
+## 正文
+
+
+## 一、搭建hello spring工程
+
+### 1.1 项目搭建
+
+1.新建maven web工程,并引入相应的依赖
+
+```xml
+
+ 5.1.3.RELEASE
+
+
+
+
+ org.springframework
+ spring-context
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-beans
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-core
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-web
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-webmvc
+ ${spring-base-version}
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+```
+
+2.得益于servlet3.0和spring的支持,我们可以在没有web.xml的情况下完成关于servlet配置。
+
+ 新建DispatcherServletInitializer.java文件,这个类的作用相当于我们在xml方式下web.xml中配置的DispatcherServlet
+
+```java
+package com.heibaiying.config;
+
+import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
+
+/**
+ * @author : heibaiying
+ */
+
+public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
+
+ protected Class>[] getRootConfigClasses() {
+ return new Class[0];
+ }
+
+ protected Class>[] getServletConfigClasses() {
+ return new Class[]{ServletConfig.class};
+ }
+
+ protected String[] getServletMappings() {
+ return new String[]{"/"};
+ }
+}
+
+```
+
+3.新建ServletConfig.java,文件内容如下(这个类相当于我们在xml配置方式中的springApplication.xml)
+
+```java
+package com.heibaiying.config;
+
+import com.heibaiying.exception.NoAuthExceptionResolver;
+import com.heibaiying.interceptors.MyFirstInterceptor;
+import com.heibaiying.interceptors.MySecondInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.ViewResolver;
+import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.view.InternalResourceViewResolver;
+
+import java.util.List;
+
+/**
+ * @author : heibaiying
+ */
+@Configuration
+@EnableWebMvc
+@ComponentScan(basePackages = {"com.heibaiying.controller"})
+public class ServletConfig implements WebMvcConfigurer {
+
+ /**
+ * 配置视图解析器
+ */
+ @Bean
+ public ViewResolver viewResolver() {
+ InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
+ internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
+ internalResourceViewResolver.setSuffix(".jsp");
+ internalResourceViewResolver.setExposeContextBeansAsAttributes(true);
+ return internalResourceViewResolver;
+ }
+
+ /**
+ * 配置静态资源处理器
+ */
+ public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
+ configurer.enable();
+ }
+
+}
+
+```
+
+4.在src 下新建controller用于测试
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.exception.NoAuthException;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * @author : heibaiying
+ * @description : hello spring
+ */
+
+@Controller
+@RequestMapping("mvc")
+public class HelloController {
+
+ @RequestMapping("hello")
+ private String hello() {
+ return "hello";
+ }
+
+}
+
+```
+
+5.在WEB-INF 下新建jsp文件夹,新建hello.jsp 文件
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Title
+
+
+ Hello Spring MVC!
+
+
+```
+
+6.启动tomcat服务,访问localhost:8080/mvc/hello
+
+### 1.2 相关注解说明
+
+**1.@Configuration**
+
+@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
+
+**2.@EnableWebMvc**
+
+简单的说就是提供了部分springmvc的功能,例如格式转换和参数绑定。
+
+
+
+## 二、配置自定义拦截器
+
+1.创建自定义拦截器,实现接口HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
+
+```java
+package com.heibaiying.interceptors;
+
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author : heibaiying
+ * @description : spring5 中 preHandle,postHandle,afterCompletion 在接口中被声明为默认方法
+ */
+public class MyFirstInterceptor implements HandlerInterceptor {
+
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+ System.out.println("进入第一个拦截器preHandle");
+ return true;
+ }
+
+ // 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入afterCompletion这个方法
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
+ System.out.println("进入第一个拦截器postHandle");
+ }
+
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+ System.out.println("进入第一个拦截器afterCompletion");
+ }
+}
+```
+
+```java
+package com.heibaiying.interceptors;
+
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author : heibaiying
+ * @description : spring5 中 preHandle,postHandle,afterCompletion 在接口中被声明为默认方法
+ */
+public class MySecondInterceptor implements HandlerInterceptor {
+
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+ System.out.println("进入第二个拦截器preHandle");
+ return true;
+ }
+
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
+ System.out.println("进入第二个拦截器postHandle");
+ }
+
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+ System.out.println("进入第二个拦截器afterCompletion");
+ }
+}
+
+```
+
+2.在ServletConfig.java中注册自定义拦截器
+
+```java
+ /**
+ * 添加自定义拦截器
+ */
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/mvc/**").excludePathPatterns("mvc/login");
+ registry.addInterceptor(new MySecondInterceptor()).addPathPatterns("/mvc/**");
+ }
+```
+
+3.关于多个拦截器方法执行顺序的说明
+
+拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
+
+
+
+## 三、全局异常处理
+
+1.定义自定义异常
+
+```java
+package com.heibaiying.exception;
+
+/**
+ * @author : heibaiying
+ * @description : 自定义无权限异常
+ */
+public class NoAuthException extends RuntimeException {
+
+ public NoAuthException() {
+ super();
+ }
+
+ public NoAuthException(String message) {
+ super(message);
+ }
+
+ public NoAuthException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public NoAuthException(Throwable cause) {
+ super(cause);
+ }
+
+}
+
+```
+
+2.实现自定义异常处理器
+
+```java
+package com.heibaiying.exception;
+
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author : heibaiying
+ * @description : 无权限异常处理机制
+ */
+public class NoAuthExceptionResolver implements HandlerExceptionResolver {
+
+ public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+ if (ex instanceof NoAuthException && !isAjax(request)) {
+ return new ModelAndView("NoAuthPage");
+ }
+ return new ModelAndView();
+ }
+
+ // 判断是否是Ajax请求
+ private boolean isAjax(HttpServletRequest request) {
+ return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
+ }
+}
+
+```
+
+3.在ServletConfig.java注册自定义异常处理器
+
+```java
+/**
+* 添加全局异常处理器
+*/
+public void configureHandlerExceptionResolvers(List resolvers) {
+ resolvers.add(new NoAuthExceptionResolver());
+}
+```
+
+4.定义测试controller,抛出自定义异常
+
+```java
+@Controller
+@RequestMapping("mvc")
+public class HelloController {
+
+ @RequestMapping("hello")
+ private String hello() {
+ return "hello";
+ }
+
+
+ @RequestMapping("auth")
+ private void auth() {
+ throw new NoAuthException("没有对应的访问权限!");
+ }
+}
+```
+
+注:调用这个controller时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入postHandle这个方法 但一定会进入afterCompletion这个方法
+
+
+
+## 四、参数绑定
+
+### 4.1 参数绑定
+
+1.新建Programmer.java
+
+```java
+package com.heibaiying.bean;
+
+import lombok.Data;
+
+/**
+ * @author : heibaiying
+ * @description :
+ */
+@Data
+public class Programmer {
+
+ private String name;
+
+ private int age;
+
+ private float salary;
+
+ private String birthday;
+}
+
+```
+
+注:@Data 是lombok包下的注解,用来生成相应的set、get方法,使得类的书写更为简洁。
+
+2.新建ParamBindController.java 文件
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.bean.Programmer;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.format.datetime.DateFormatter;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.util.Date;
+
+/**
+ * @author : heibaiying
+ * @description :参数绑定
+ */
+@Controller
+public class ParamBindController {
+
+ @InitBinder
+ protected void initBinder(WebDataBinder binder) {
+ binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
+ }
+
+
+ // 参数绑定与日期格式转换
+ @RequestMapping("param")
+ public String param(String name, int age, double salary, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday, Model model) {
+ model.addAttribute("name", name);
+ model.addAttribute("age", age);
+ model.addAttribute("salary", salary);
+ model.addAttribute("birthday", birthday);
+ return "param";
+ }
+
+ @RequestMapping("param2")
+ public String param2(String name, int age, double salary, Date birthday, Model model) {
+ model.addAttribute("name", name);
+ model.addAttribute("age", age);
+ model.addAttribute("salary", salary);
+ model.addAttribute("birthday", birthday);
+ return "param";
+ }
+
+
+ @PostMapping("param3")
+ public String param3(Programmer programmer, String extendParam, Model model) {
+ System.out.println("extendParam" + extendParam);
+ model.addAttribute("p", programmer);
+ return "param";
+ }
+
+}
+
+```
+
+3.新建param.jsp 文件
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Restful
+
+
+
+ - 姓名:${empty name ? p.name : name}
+ - 年龄:${empty age ? p.age : age}
+ - 薪酬:${empty salary ? p.salary : salary}
+ - 生日:${empty birthday ? p.birthday : birthday}
+
+
+
+
+```
+
+4.启动tomcat,用[postman](https://www.getpostman.com/)软件发送请求进行测试
+
+### 4.2 关于日期格式转换的三种方法
+
+1.如上实例代码所示,在对应的controller中初始化绑定
+
+```java
+@InitBinder
+ protected void initBinder(WebDataBinder binder) {
+ binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
+ }
+```
+
+2.利用@DateTimeFormat注解,如果是用实体类去接收参数,则在对应的属性上声明
+
+```java
+public String param(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday)
+```
+
+3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在ServletConfig.java中进行注册
+
+```java
+package com.heibaiying.convert;
+
+import org.springframework.core.convert.converter.Converter;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @author : heibaiying
+ * @description :
+ */
+public class CustomDateConverter implements Converter {
+
+ public Date convert(String s) {
+ try {
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ return simpleDateFormat.parse(s);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
+
+```
+
+ServletConfig.java
+
+```java
+/**
+ * 添加全局日期处理
+ */
+public void addFormatters(FormatterRegistry registry) {
+ registry.addConverter(new CustomDateConverter());
+}
+```
+
+
+
+## 五、数据校验
+
+1.spring支持的数据校验是JSR303的标准,需要引入依赖的jar包
+
+```java
+
+
+ org.hibernate.validator
+ hibernate-validator
+ 6.0.13.Final
+
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+
+```
+
+2.新建测试ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上bindingResult参数,这个参数中包含了校验的结果
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.bean.Programmer;
+import org.hibernate.validator.constraints.Length;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.format.datetime.DateFormatter;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.ObjectError;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author : heibaiying
+ * @description :数据校验
+ */
+@RestController
+public class ParamValidController {
+
+
+ @PostMapping("validate")
+ public void valid(@Validated Programmer programmer,
+ BindingResult bindingResult) {
+ List allErrors = bindingResult.getAllErrors();
+ for (ObjectError error : allErrors) {
+ System.out.println(error.getDefaultMessage());
+ }
+ }
+
+}
+
+```
+
+3.在Programmer.java的对应属性上加上注解约束(支持的注解可以在javax.validation.constraints包中查看)
+
+```java
+package com.heibaiying.bean;
+
+import lombok.Data;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author : heibaiying
+ * @description :
+ */
+@Data
+public class Programmer {
+
+ @NotNull
+ private String name;
+
+ @Min(value = 0,message = "年龄不能为负数!" )
+ private int age;
+
+ @Min(value = 0,message = "薪酬不能为负数!" )
+ private float salary;
+
+ private String birthday;
+}
+
+```
+
+## 六、文件上传与下载
+
+#### 6.1 文件上传
+
+1.在ServletConfig.java中进行配置,使之支持文件上传
+
+```java
+/**
+* 配置文件上传
+*/
+@Bean
+public CommonsMultipartResolver multipartResolver(){
+ CommonsMultipartResolver resolver = new CommonsMultipartResolver();
+ resolver.setMaxUploadSize(1024*1000*10);
+ resolver.setMaxUploadSizePerFile(1024*1000);
+ resolver.setDefaultEncoding("utf-8");
+ return resolver;
+}
+```
+
+2.新建测试上传的FileController.java
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.utils.FileUtil;
+import org.apache.commons.io.FileUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpSession;
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * @author : heibaiying
+ * @description : 文件上传
+ */
+
+@Controller
+public class FileController {
+
+ @GetMapping("file")
+ public String filePage() {
+ return "file";
+ }
+
+
+ /***
+ * 单文件上传
+ */
+ @PostMapping("upFile")
+ public String upFile(MultipartFile file, HttpSession session) {
+ //保存在项目根目录下image文件夹下,如果文件夹不存在则创建
+ FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
+ // success.jsp 就是一个简单的成功页面
+ return "success";
+ }
+
+ /***
+ * 多文件上传 多个文件用同一个名字
+ */
+ @PostMapping("upFiles")
+ public String upFiles(@RequestParam(name = "file") MultipartFile[] files, HttpSession session) {
+ for (MultipartFile file : files) {
+ FileUtil.saveFile(file, session.getServletContext().getRealPath("images"));
+ }
+ return "success";
+ }
+
+ /***
+ * 多文件上传方式2 分别为不同文件指定不同名字
+ */
+ @PostMapping("upFiles2")
+ public String upFile(String extendParam,
+ @RequestParam(name = "file1") MultipartFile file1,
+ @RequestParam(name = "file2") MultipartFile file2, HttpSession session) {
+ String realPath = session.getServletContext().getRealPath("images2");
+ FileUtil.saveFile(file1, realPath);
+ FileUtil.saveFile(file2, realPath);
+ System.out.println("extendParam:" + extendParam);
+ return "success";
+ }
+}
+
+```
+
+3.其中工具类FileUtil.java代码如下
+
+```java
+package com.heibaiying.utils;
+
+import org.springframework.web.multipart.MultipartFile;
+import java.io.*;
+
+/**
+ * @author : heibaiying
+ * @description : 文件上传工具类
+ */
+
+public class FileUtil {
+
+ public static String saveFile(MultipartFile file, String path) {
+ InputStream inputStream = null;
+ FileOutputStream outputStream = null;
+ String fullPath = path + File.separator + file.getOriginalFilename();
+ try {
+ File saveDir = new File(path);
+ if (!saveDir.exists()) {
+ saveDir.mkdirs();
+ }
+ outputStream = new FileOutputStream(new File(fullPath));
+ inputStream = file.getInputStream();
+ byte[] bytes = new byte[1024 * 1024];
+ int read;
+ while ((read = inputStream.read(bytes)) != -1) {
+ outputStream.write(bytes, 0, read);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return fullPath;
+ }
+
+}
+```
+
+4.新建用于上传的jsp页面,上传文件时表单必须声明 enctype="multipart/form-data"
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ 文件上传
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 6.2 文件下载
+
+1.在fileController.java中加上方法:
+
+```java
+ /***
+ * 上传用于下载的文件
+ */
+ @PostMapping("upFileForDownload")
+ public String upFileForDownload(MultipartFile file, HttpSession session, Model model) throws UnsupportedEncodingException {
+ String path = FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
+ model.addAttribute("filePath", URLEncoder.encode(path,"utf-8"));
+ model.addAttribute("fileName", file.getOriginalFilename());
+ return "fileDownload";
+ }
+
+ /***
+ * 下载文件
+ */
+ @GetMapping("download")
+ public ResponseEntity downloadFile(String filePath) throws IOException {
+ HttpHeaders headers = new HttpHeaders();
+ File file = new File(filePath);
+ // 解决文件名中文乱码
+ String fileName=new String(file.getName().getBytes("UTF-8"),"iso-8859-1");
+ headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+ headers.setContentDispositionFormData("attachment", fileName);
+
+ return new ResponseEntity(FileUtils.readFileToByteArray(file),
+ headers, HttpStatus.CREATED);
+ }
+```
+
+2.其中fileDownload.jsp 如下:
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ 文件下载
+
+
+ ${fileName}
+
+
+```
+
+
+
+## 七、Restful风格的请求
+
+1.新建Pet.java实体类
+
+```java
+package com.heibaiying.bean;
+
+import lombok.Data;
+
+/**
+ * @author : heibaiying
+ * @description :测试restful风格的实体类
+ */
+
+@Data
+public class Pet {
+
+ private String ownerId;
+
+ private String petId;
+}
+
+```
+
+2.新建RestfulController.java,用@PathVariable和@ModelAttribute注解进行参数绑定。
+
+注: 在REST中,资源通过URL进行识别和定位。REST中的行为是通过HTTP方法定义的。在进行不同行为时对应HTTP方法和Spring注解分别如下:
+
+- 创建资源时:POST(PostMapping)
+- 读取资源时:GET( @GetMapping)
+- 更新资源时:PUT或PATCH(PutMapping、PatchMapping)
+- 删除资源时:DELETE(DeleteMapping)
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.bean.Pet;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author : heibaiying
+ * @description : Restful 风格的请求
+ */
+
+@RestController
+public class RestfulController {
+
+ @GetMapping("restful/owners/{ownerId}/pets/{petId}")
+ public void get(@PathVariable String ownerId, @PathVariable String petId) {
+ System.out.println("ownerId:" + ownerId);
+ System.out.println("petId:" + petId);
+ }
+
+ @GetMapping("restful2/owners/{ownerId}/pets/{petId}")
+ public void get(@ModelAttribute Pet pet) {
+ System.out.println("ownerId:" + pet.getOwnerId());
+ System.out.println("petId:" + pet.getPetId());
+ }
+
+}
+
+```
+
diff --git a/spring/springmvc-base/README.md b/spring/springmvc-base/README.md
index 16c0fb3..18982b1 100644
--- a/spring/springmvc-base/README.md
+++ b/spring/springmvc-base/README.md
@@ -1,928 +1,944 @@
-# springmvc基础(基于xml配置)
-
-
-
-## 一、搭建hello spring工程
-
-### 1.1 项目搭建
-
-1.新建maven web工程,并引入相应的依赖
-
-```xml
-
- 5.1.3.RELEASE
-
-
-
-
- org.springframework
- spring-context
- ${spring-base-version}
-
-
- org.springframework
- spring-beans
- ${spring-base-version}
-
-
- org.springframework
- spring-core
- ${spring-base-version}
-
-
- org.springframework
- spring-web
- ${spring-base-version}
-
-
- org.springframework
- spring-webmvc
- ${spring-base-version}
-
-
- javax.servlet
- javax.servlet-api
- 4.0.1
- provided
-
-
-```
-
-2.配置web.xml
-
-```xml
-
-
-
-
-
- springMvc
- org.springframework.web.servlet.DispatcherServlet
-
- contextConfigLocation
- classpath:springApplication.xml
-
- 1
-
-
-
- springMvc
- /
-
-
-
-```
-
-3.在resources下新建springApplication.xml文件,文件内容如下:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-4.在src 下新建controller用于测试
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.exception.NoAuthException;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-
-/**
- * @author : heibaiying
- * @description : hello spring
- */
-
-@Controller
-@RequestMapping("mvc")
-public class HelloController {
-
- @RequestMapping("hello")
- private String hello() {
- return "hello";
- }
-
-}
-
-```
-
-5.在WEB-INF 下新建jsp文件夹,新建hello.jsp 文件
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- Title
-
-
- Hello Spring MVC!
-
-
-```
-
-6.启动tomcat服务,访问localhost:8080/mvc/hello
-
-### 1.2 相关配置讲解
-
-**1.\**
-
-在web.xml配置中,我们将DispatcherServlet的拦截路径设置为“\”,则spring会捕获所有web请求,包括对静态资源的请求,为了正确处理对静态资源的请求,spring提供了两种解决方案:
-
-- 配置\ : 配置后,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。
-
-- 配置\ :指定静态资源的位置和路径映射:
-
- ```xml
-
-
-
- ```
-
-**2.\**
-
- 会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter
-两个bean,用以支持@Controllers分发请求。并提供了数据绑定、参数转换、json转换等功能,所以必须加上这个配置。
-
-
-
-## 二、配置自定义拦截器
-
-1.创建自定义拦截器,实现接口HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
-
-```java
-package com.heibaiying.interceptors;
-
-import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * @author : heibaiying
- * @description : spring5 中 preHandle,postHandle,afterCompletion 在接口中被声明为默认方法
- */
-public class MyFirstInterceptor implements HandlerInterceptor {
-
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
- System.out.println("进入第一个拦截器preHandle");
- return true;
- }
-
- // 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入afterCompletion这个方法
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
- System.out.println("进入第一个拦截器postHandle");
- }
-
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
- System.out.println("进入第一个拦截器afterCompletion");
- }
-}
-```
-
-```java
-package com.heibaiying.interceptors;
-
-import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * @author : heibaiying
- * @description : spring5 中 preHandle,postHandle,afterCompletion 在接口中被声明为默认方法
- */
-public class MySecondInterceptor implements HandlerInterceptor {
-
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
- System.out.println("进入第二个拦截器preHandle");
- return true;
- }
-
- public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
- System.out.println("进入第二个拦截器postHandle");
- }
-
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
- System.out.println("进入第二个拦截器afterCompletion");
- }
-}
-
-```
-
-2.在springApplication.xml中注册自定义拦截器
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-3.关于多个拦截器方法执行顺序的说明
-
-拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
-
-
-
-## 三、全局异常处理
-
-1.定义自定义异常
-
-```java
-package com.heibaiying.exception;
-
-/**
- * @author : heibaiying
- * @description : 自定义无权限异常
- */
-public class NoAuthException extends RuntimeException {
-
- public NoAuthException() {
- super();
- }
-
- public NoAuthException(String message) {
- super(message);
- }
-
- public NoAuthException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public NoAuthException(Throwable cause) {
- super(cause);
- }
-
-}
-
-```
-
-2.实现自定义异常处理器
-
-```java
-package com.heibaiying.exception;
-
-import org.springframework.web.servlet.HandlerExceptionResolver;
-import org.springframework.web.servlet.ModelAndView;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * @author : heibaiying
- * @description : 无权限异常处理机制
- */
-public class NoAuthExceptionResolver implements HandlerExceptionResolver {
-
- public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
- if (ex instanceof NoAuthException && !isAjax(request)) {
- return new ModelAndView("NoAuthPage");
- }
- return new ModelAndView();
- }
-
- // 判断是否是Ajax请求
- private boolean isAjax(HttpServletRequest request) {
- return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
- }
-}
-
-```
-
-3.在springApplication.xml注册自定义异常处理器
-
-```xml
-
-
-```
-
-4.定义测试controller,抛出自定义异常
-
-```java
-@Controller
-@RequestMapping("mvc")
-public class HelloController {
-
- @RequestMapping("hello")
- private String hello() {
- return "hello";
- }
-
-
- @RequestMapping("auth")
- private void auth() {
- throw new NoAuthException("没有对应的访问权限!");
- }
-}
-```
-
-注:调用这个controller时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入postHandle这个方法 但一定会进入afterCompletion这个方法
-
-
-
-## 四、参数绑定
-
-### 4.1 参数绑定
-
-1.新建Programmer.java
-
-```java
-package com.heibaiying.bean;
-
-import lombok.Data;
-
-/**
- * @author : heibaiying
- * @description :
- */
-@Data
-public class Programmer {
-
- private String name;
-
- private int age;
-
- private float salary;
-
- private String birthday;
-}
-
-```
-
-注:@Data 是lombok包下的注解,用来生成相应的set、get方法,使得类的书写更为简洁。
-
-2.新建ParamBindController.java 文件
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.bean.Programmer;
-import org.springframework.format.annotation.DateTimeFormat;
-import org.springframework.format.datetime.DateFormatter;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.web.bind.WebDataBinder;
-import org.springframework.web.bind.annotation.InitBinder;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-
-import java.util.Date;
-
-/**
- * @author : heibaiying
- * @description :参数绑定
- */
-@Controller
-public class ParamBindController {
-
- @InitBinder
- protected void initBinder(WebDataBinder binder) {
- binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
- }
-
-
- // 参数绑定与日期格式转换
- @RequestMapping("param")
- public String param(String name, int age, double salary, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday, Model model) {
- model.addAttribute("name", name);
- model.addAttribute("age", age);
- model.addAttribute("salary", salary);
- model.addAttribute("birthday", birthday);
- return "param";
- }
-
- @RequestMapping("param2")
- public String param2(String name, int age, double salary, Date birthday, Model model) {
- model.addAttribute("name", name);
- model.addAttribute("age", age);
- model.addAttribute("salary", salary);
- model.addAttribute("birthday", birthday);
- return "param";
- }
-
-
- @PostMapping("param3")
- public String param3(Programmer programmer, String extendParam, Model model) {
- System.out.println("extendParam" + extendParam);
- model.addAttribute("p", programmer);
- return "param";
- }
-
-}
-
-```
-
-3.新建param.jsp 文件
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- Restful
-
-
-
- - 姓名:${empty name ? p.name : name}
- - 年龄:${empty age ? p.age : age}
- - 薪酬:${empty salary ? p.salary : salary}
- - 生日:${empty birthday ? p.birthday : birthday}
-
-
-
-
-```
-
-4.启动tomcat,用[postman](https://www.getpostman.com/)软件发送请求进行测试
-
-### 4.2 关于日期格式转换的三种方法
-
-1.如上实例代码所示,在对应的controller中初始化绑定
-
-```java
-@InitBinder
- protected void initBinder(WebDataBinder binder) {
- binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
- }
-```
-
-2.利用@DateTimeFormat注解,如果是用实体类去接收参数,则在对应的属性上声明
-
-```java
-public String param(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday)
-```
-
-3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在springApplication.xml中进行注册
-
-```java
-package com.heibaiying.convert;
-
-import org.springframework.core.convert.converter.Converter;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-/**
- * @author : heibaiying
- * @description :
- */
-public class CustomDateConverter implements Converter {
-
- public Date convert(String s) {
- try {
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- return simpleDateFormat.parse(s);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
-}
-
-```
-
-springApplication.xml
-
-```xml
-
-
-
-
-
-
-
-
-```
-
-
-
-## 五、数据校验
-
-1.spring支持的数据校验是JSR303的标准,需要引入依赖的jar包
-
-```java
-
-
- org.hibernate.validator
- hibernate-validator
- 6.0.13.Final
-
-
- javax.validation
- validation-api
- 2.0.1.Final
-
-```
-
-2.新建测试ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上bindingResult参数,这个参数中包含了校验的结果
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.bean.Programmer;
-import org.hibernate.validator.constraints.Length;
-import org.springframework.format.annotation.DateTimeFormat;
-import org.springframework.format.datetime.DateFormatter;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.validation.BindingResult;
-import org.springframework.validation.ObjectError;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.WebDataBinder;
-import org.springframework.web.bind.annotation.InitBinder;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.validation.constraints.Min;
-import javax.validation.constraints.NotNull;
-import java.util.Date;
-import java.util.List;
-
-/**
- * @author : heibaiying
- * @description :数据校验
- */
-@RestController
-public class ParamValidController {
-
-
- @PostMapping("validate")
- public void valid(@Validated Programmer programmer,
- BindingResult bindingResult) {
- List allErrors = bindingResult.getAllErrors();
- for (ObjectError error : allErrors) {
- System.out.println(error.getDefaultMessage());
- }
- }
-
-}
-
-```
-
-3.在Programmer.java的对应属性上加上注解约束(支持的注解可以在javax.validation.constraints包中查看)
-
-```java
-package com.heibaiying.bean;
-
-import lombok.Data;
-
-import javax.validation.constraints.Min;
-import javax.validation.constraints.NotNull;
-
-/**
- * @author : heibaiying
- * @description :
- */
-@Data
-public class Programmer {
-
- @NotNull
- private String name;
-
- @Min(value = 0,message = "年龄不能为负数!" )
- private int age;
-
- @Min(value = 0,message = "薪酬不能为负数!" )
- private float salary;
-
- private String birthday;
-}
-
-```
-
-## 六、文件上传与下载
-
-#### 6.1 文件上传
-
-1.在springApplication.xml中进行配置,使之支持文件上传
-
-```xml
-
-
-
-
-
-
-
-
-```
-
-2.新建测试上传的FileController.java
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.utils.FileUtil;
-import org.apache.commons.io.FileUtils;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.multipart.MultipartFile;
-
-import javax.servlet.http.HttpSession;
-import java.io.File;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-
-/**
- * @author : heibaiying
- * @description : 文件上传
- */
-
-@Controller
-public class FileController {
-
- @GetMapping("file")
- public String filePage() {
- return "file";
- }
-
-
- /***
- * 单文件上传
- */
- @PostMapping("upFile")
- public String upFile(MultipartFile file, HttpSession session) {
- //保存在项目根目录下image文件夹下,如果文件夹不存在则创建
- FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
- // success.jsp 就是一个简单的成功页面
- return "success";
- }
-
- /***
- * 多文件上传 多个文件用同一个名字
- */
- @PostMapping("upFiles")
- public String upFiles(@RequestParam(name = "file") MultipartFile[] files, HttpSession session) {
- for (MultipartFile file : files) {
- FileUtil.saveFile(file, session.getServletContext().getRealPath("images"));
- }
- return "success";
- }
-
- /***
- * 多文件上传方式2 分别为不同文件指定不同名字
- */
- @PostMapping("upFiles2")
- public String upFile(String extendParam,
- @RequestParam(name = "file1") MultipartFile file1,
- @RequestParam(name = "file2") MultipartFile file2, HttpSession session) {
- String realPath = session.getServletContext().getRealPath("images2");
- FileUtil.saveFile(file1, realPath);
- FileUtil.saveFile(file2, realPath);
- System.out.println("extendParam:" + extendParam);
- return "success";
- }
-}
-
-```
-
-3.其中工具类FileUtil.java代码如下
-
-```java
-package com.heibaiying.utils;
-
-import org.springframework.web.multipart.MultipartFile;
-import java.io.*;
-
-/**
- * @author : heibaiying
- * @description : 文件上传工具类
- */
-
-public class FileUtil {
-
- public static String saveFile(MultipartFile file, String path) {
- InputStream inputStream = null;
- FileOutputStream outputStream = null;
- String fullPath = path + File.separator + file.getOriginalFilename();
- try {
- File saveDir = new File(path);
- if (!saveDir.exists()) {
- saveDir.mkdirs();
- }
- outputStream = new FileOutputStream(new File(fullPath));
- inputStream = file.getInputStream();
- byte[] bytes = new byte[1024 * 1024];
- int read;
- while ((read = inputStream.read(bytes)) != -1) {
- outputStream.write(bytes, 0, read);
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (inputStream != null) {
- try {
- inputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if (outputStream != null) {
- try {
- outputStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- return fullPath;
- }
-
-}
-```
-
-4.新建用于上传的jsp页面,上传文件时表单必须声明 enctype="multipart/form-data"
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- 文件上传
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### 6.2 文件下载
-
-1.在fileController.java中加上方法:
-
-```java
- /***
- * 上传用于下载的文件
- */
- @PostMapping("upFileForDownload")
- public String upFileForDownload(MultipartFile file, HttpSession session, Model model) throws UnsupportedEncodingException {
- String path = FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
- model.addAttribute("filePath", URLEncoder.encode(path,"utf-8"));
- model.addAttribute("fileName", file.getOriginalFilename());
- return "fileDownload";
- }
-
- /***
- * 下载文件
- */
- @GetMapping("download")
- public ResponseEntity downloadFile(String filePath) throws IOException {
- HttpHeaders headers = new HttpHeaders();
- File file = new File(filePath);
- // 解决文件名中文乱码
- String fileName=new String(file.getName().getBytes("UTF-8"),"iso-8859-1");
- headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
- headers.setContentDispositionFormData("attachment", fileName);
-
- return new ResponseEntity(FileUtils.readFileToByteArray(file),
- headers, HttpStatus.CREATED);
- }
-```
-
-2.其中fileDownload.jsp 如下:
-
-```jsp
-<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
-
- 文件下载
-
-
- ${fileName}
-
-
-```
-
-
-
-## 七、Restful风格的请求
-
-1.新建Pet.java实体类
-
-```java
-package com.heibaiying.bean;
-
-import lombok.Data;
-
-/**
- * @author : heibaiying
- * @description :测试restful风格的实体类
- */
-
-@Data
-public class Pet {
-
- private String ownerId;
-
- private String petId;
-}
-
-```
-
-2.新建RestfulController.java,用@PathVariable和@ModelAttribute注解进行参数绑定。
-
-注: 在REST中,资源通过URL进行识别和定位。REST中的行为是通过HTTP方法定义的。在进行不同行为时对应HTTP方法和Spring注解分别如下:
-
-- 创建资源时:POST(PostMapping)
-- 读取资源时:GET( @GetMapping)
-- 更新资源时:PUT或PATCH(PutMapping、PatchMapping)
-- 删除资源时:DELETE(DeleteMapping)
-
-```java
-package com.heibaiying.controller;
-
-import com.heibaiying.bean.Pet;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-/**
- * @author : heibaiying
- * @description : Restful 风格的请求
- */
-
-@RestController
-public class RestfulController {
-
- @GetMapping("restful/owners/{ownerId}/pets/{petId}")
- public void get(@PathVariable String ownerId, @PathVariable String petId) {
- System.out.println("ownerId:" + ownerId);
- System.out.println("petId:" + petId);
- }
-
- @GetMapping("restful2/owners/{ownerId}/pets/{petId}")
- public void get(@ModelAttribute Pet pet) {
- System.out.println("ownerId:" + pet.getOwnerId());
- System.out.println("petId:" + pet.getPetId());
- }
-
-}
-
-```
-
+# springmvc基础(基于xml配置)
+
+
+
## 目录
+一、搭建hello spring工程
+ 1.1 项目搭建
+ 1.2 相关配置讲解
+二、配置自定义拦截器
+三、全局异常处理
+四、参数绑定
+ 4.1 参数绑定
+ 4.2 关于日期格式转换的三种方法
+五、数据校验
+六、文件上传与下载
+ 6.1 文件上传
+ 6.2 文件下载
+七、Restful风格的请求
+## 正文
+
+
+## 一、搭建hello spring工程
+
+### 1.1 项目搭建
+
+1.新建maven web工程,并引入相应的依赖
+
+```xml
+
+ 5.1.3.RELEASE
+
+
+
+
+ org.springframework
+ spring-context
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-beans
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-core
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-web
+ ${spring-base-version}
+
+
+ org.springframework
+ spring-webmvc
+ ${spring-base-version}
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ provided
+
+
+```
+
+2.配置web.xml
+
+```xml
+
+
+
+
+
+ springMvc
+ org.springframework.web.servlet.DispatcherServlet
+
+ contextConfigLocation
+ classpath:springApplication.xml
+
+ 1
+
+
+
+ springMvc
+ /
+
+
+
+```
+
+3.在resources下新建springApplication.xml文件,文件内容如下:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+4.在src 下新建controller用于测试
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.exception.NoAuthException;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * @author : heibaiying
+ * @description : hello spring
+ */
+
+@Controller
+@RequestMapping("mvc")
+public class HelloController {
+
+ @RequestMapping("hello")
+ private String hello() {
+ return "hello";
+ }
+
+}
+
+```
+
+5.在WEB-INF 下新建jsp文件夹,新建hello.jsp 文件
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Title
+
+
+ Hello Spring MVC!
+
+
+```
+
+6.启动tomcat服务,访问localhost:8080/mvc/hello
+
+### 1.2 相关配置讲解
+
+**1.\**
+
+在web.xml配置中,我们将DispatcherServlet的拦截路径设置为“\”,则spring会捕获所有web请求,包括对静态资源的请求,为了正确处理对静态资源的请求,spring提供了两种解决方案:
+
+- 配置\ : 配置后,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。
+
+- 配置\ :指定静态资源的位置和路径映射:
+
+ ```xml
+
+
+
+ ```
+
+**2.\**
+
+ 会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter
+两个bean,用以支持@Controllers分发请求。并提供了数据绑定、参数转换、json转换等功能,所以必须加上这个配置。
+
+
+
+## 二、配置自定义拦截器
+
+1.创建自定义拦截器,实现接口HandlerInterceptor(这里我们创建两个拦截器,用于测试拦截器方法的执行顺序)
+
+```java
+package com.heibaiying.interceptors;
+
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author : heibaiying
+ * @description : spring5 中 preHandle,postHandle,afterCompletion 在接口中被声明为默认方法
+ */
+public class MyFirstInterceptor implements HandlerInterceptor {
+
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+ System.out.println("进入第一个拦截器preHandle");
+ return true;
+ }
+
+ // 需要注意的是,如果对应的程序报错,不一定会进入这个方法 但一定会进入afterCompletion这个方法
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
+ System.out.println("进入第一个拦截器postHandle");
+ }
+
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+ System.out.println("进入第一个拦截器afterCompletion");
+ }
+}
+```
+
+```java
+package com.heibaiying.interceptors;
+
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author : heibaiying
+ * @description : spring5 中 preHandle,postHandle,afterCompletion 在接口中被声明为默认方法
+ */
+public class MySecondInterceptor implements HandlerInterceptor {
+
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+ System.out.println("进入第二个拦截器preHandle");
+ return true;
+ }
+
+ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
+ System.out.println("进入第二个拦截器postHandle");
+ }
+
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+ System.out.println("进入第二个拦截器afterCompletion");
+ }
+}
+
+```
+
+2.在springApplication.xml中注册自定义拦截器
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+3.关于多个拦截器方法执行顺序的说明
+
+拦截器的执行顺序是按声明的先后顺序执行的,先声明的拦截器中的preHandle方法会先执行,然而它的postHandle方法和afterCompletion方法却会后执行。
+
+
+
+## 三、全局异常处理
+
+1.定义自定义异常
+
+```java
+package com.heibaiying.exception;
+
+/**
+ * @author : heibaiying
+ * @description : 自定义无权限异常
+ */
+public class NoAuthException extends RuntimeException {
+
+ public NoAuthException() {
+ super();
+ }
+
+ public NoAuthException(String message) {
+ super(message);
+ }
+
+ public NoAuthException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public NoAuthException(Throwable cause) {
+ super(cause);
+ }
+
+}
+
+```
+
+2.实现自定义异常处理器
+
+```java
+package com.heibaiying.exception;
+
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author : heibaiying
+ * @description : 无权限异常处理机制
+ */
+public class NoAuthExceptionResolver implements HandlerExceptionResolver {
+
+ public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+ if (ex instanceof NoAuthException && !isAjax(request)) {
+ return new ModelAndView("NoAuthPage");
+ }
+ return new ModelAndView();
+ }
+
+ // 判断是否是Ajax请求
+ private boolean isAjax(HttpServletRequest request) {
+ return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
+ }
+}
+
+```
+
+3.在springApplication.xml注册自定义异常处理器
+
+```xml
+
+
+```
+
+4.定义测试controller,抛出自定义异常
+
+```java
+@Controller
+@RequestMapping("mvc")
+public class HelloController {
+
+ @RequestMapping("hello")
+ private String hello() {
+ return "hello";
+ }
+
+
+ @RequestMapping("auth")
+ private void auth() {
+ throw new NoAuthException("没有对应的访问权限!");
+ }
+}
+```
+
+注:调用这个controller时,同时也可以验证在拦截器部分提到的:如果对应的程序报错,拦截器不一定会进入postHandle这个方法 但一定会进入afterCompletion这个方法
+
+
+
+## 四、参数绑定
+
+### 4.1 参数绑定
+
+1.新建Programmer.java
+
+```java
+package com.heibaiying.bean;
+
+import lombok.Data;
+
+/**
+ * @author : heibaiying
+ * @description :
+ */
+@Data
+public class Programmer {
+
+ private String name;
+
+ private int age;
+
+ private float salary;
+
+ private String birthday;
+}
+
+```
+
+注:@Data 是lombok包下的注解,用来生成相应的set、get方法,使得类的书写更为简洁。
+
+2.新建ParamBindController.java 文件
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.bean.Programmer;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.format.datetime.DateFormatter;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.util.Date;
+
+/**
+ * @author : heibaiying
+ * @description :参数绑定
+ */
+@Controller
+public class ParamBindController {
+
+ @InitBinder
+ protected void initBinder(WebDataBinder binder) {
+ binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
+ }
+
+
+ // 参数绑定与日期格式转换
+ @RequestMapping("param")
+ public String param(String name, int age, double salary, @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday, Model model) {
+ model.addAttribute("name", name);
+ model.addAttribute("age", age);
+ model.addAttribute("salary", salary);
+ model.addAttribute("birthday", birthday);
+ return "param";
+ }
+
+ @RequestMapping("param2")
+ public String param2(String name, int age, double salary, Date birthday, Model model) {
+ model.addAttribute("name", name);
+ model.addAttribute("age", age);
+ model.addAttribute("salary", salary);
+ model.addAttribute("birthday", birthday);
+ return "param";
+ }
+
+
+ @PostMapping("param3")
+ public String param3(Programmer programmer, String extendParam, Model model) {
+ System.out.println("extendParam" + extendParam);
+ model.addAttribute("p", programmer);
+ return "param";
+ }
+
+}
+
+```
+
+3.新建param.jsp 文件
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ Restful
+
+
+
+ - 姓名:${empty name ? p.name : name}
+ - 年龄:${empty age ? p.age : age}
+ - 薪酬:${empty salary ? p.salary : salary}
+ - 生日:${empty birthday ? p.birthday : birthday}
+
+
+
+
+```
+
+4.启动tomcat,用[postman](https://www.getpostman.com/)软件发送请求进行测试
+
+### 4.2 关于日期格式转换的三种方法
+
+1.如上实例代码所示,在对应的controller中初始化绑定
+
+```java
+@InitBinder
+ protected void initBinder(WebDataBinder binder) {
+ binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
+ }
+```
+
+2.利用@DateTimeFormat注解,如果是用实体类去接收参数,则在对应的属性上声明
+
+```java
+public String param(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday)
+```
+
+3.使用全局的日期格式绑定,新建自定义日期格式转化类,之后在springApplication.xml中进行注册
+
+```java
+package com.heibaiying.convert;
+
+import org.springframework.core.convert.converter.Converter;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @author : heibaiying
+ * @description :
+ */
+public class CustomDateConverter implements Converter {
+
+ public Date convert(String s) {
+ try {
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ return simpleDateFormat.parse(s);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
+
+```
+
+springApplication.xml
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+
+
+## 五、数据校验
+
+1.spring支持的数据校验是JSR303的标准,需要引入依赖的jar包
+
+```java
+
+
+ org.hibernate.validator
+ hibernate-validator
+ 6.0.13.Final
+
+
+ javax.validation
+ validation-api
+ 2.0.1.Final
+
+```
+
+2.新建测试ParamValidController.java,主要是在需要校验的参数前加上@Validated,声明参数需要被校验,同时加上bindingResult参数,这个参数中包含了校验的结果
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.bean.Programmer;
+import org.hibernate.validator.constraints.Length;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.format.datetime.DateFormatter;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.ObjectError;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author : heibaiying
+ * @description :数据校验
+ */
+@RestController
+public class ParamValidController {
+
+
+ @PostMapping("validate")
+ public void valid(@Validated Programmer programmer,
+ BindingResult bindingResult) {
+ List allErrors = bindingResult.getAllErrors();
+ for (ObjectError error : allErrors) {
+ System.out.println(error.getDefaultMessage());
+ }
+ }
+
+}
+
+```
+
+3.在Programmer.java的对应属性上加上注解约束(支持的注解可以在javax.validation.constraints包中查看)
+
+```java
+package com.heibaiying.bean;
+
+import lombok.Data;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+/**
+ * @author : heibaiying
+ * @description :
+ */
+@Data
+public class Programmer {
+
+ @NotNull
+ private String name;
+
+ @Min(value = 0,message = "年龄不能为负数!" )
+ private int age;
+
+ @Min(value = 0,message = "薪酬不能为负数!" )
+ private float salary;
+
+ private String birthday;
+}
+
+```
+
+## 六、文件上传与下载
+
+#### 6.1 文件上传
+
+1.在springApplication.xml中进行配置,使之支持文件上传
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+2.新建测试上传的FileController.java
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.utils.FileUtil;
+import org.apache.commons.io.FileUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpSession;
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * @author : heibaiying
+ * @description : 文件上传
+ */
+
+@Controller
+public class FileController {
+
+ @GetMapping("file")
+ public String filePage() {
+ return "file";
+ }
+
+
+ /***
+ * 单文件上传
+ */
+ @PostMapping("upFile")
+ public String upFile(MultipartFile file, HttpSession session) {
+ //保存在项目根目录下image文件夹下,如果文件夹不存在则创建
+ FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
+ // success.jsp 就是一个简单的成功页面
+ return "success";
+ }
+
+ /***
+ * 多文件上传 多个文件用同一个名字
+ */
+ @PostMapping("upFiles")
+ public String upFiles(@RequestParam(name = "file") MultipartFile[] files, HttpSession session) {
+ for (MultipartFile file : files) {
+ FileUtil.saveFile(file, session.getServletContext().getRealPath("images"));
+ }
+ return "success";
+ }
+
+ /***
+ * 多文件上传方式2 分别为不同文件指定不同名字
+ */
+ @PostMapping("upFiles2")
+ public String upFile(String extendParam,
+ @RequestParam(name = "file1") MultipartFile file1,
+ @RequestParam(name = "file2") MultipartFile file2, HttpSession session) {
+ String realPath = session.getServletContext().getRealPath("images2");
+ FileUtil.saveFile(file1, realPath);
+ FileUtil.saveFile(file2, realPath);
+ System.out.println("extendParam:" + extendParam);
+ return "success";
+ }
+}
+
+```
+
+3.其中工具类FileUtil.java代码如下
+
+```java
+package com.heibaiying.utils;
+
+import org.springframework.web.multipart.MultipartFile;
+import java.io.*;
+
+/**
+ * @author : heibaiying
+ * @description : 文件上传工具类
+ */
+
+public class FileUtil {
+
+ public static String saveFile(MultipartFile file, String path) {
+ InputStream inputStream = null;
+ FileOutputStream outputStream = null;
+ String fullPath = path + File.separator + file.getOriginalFilename();
+ try {
+ File saveDir = new File(path);
+ if (!saveDir.exists()) {
+ saveDir.mkdirs();
+ }
+ outputStream = new FileOutputStream(new File(fullPath));
+ inputStream = file.getInputStream();
+ byte[] bytes = new byte[1024 * 1024];
+ int read;
+ while ((read = inputStream.read(bytes)) != -1) {
+ outputStream.write(bytes, 0, read);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return fullPath;
+ }
+
+}
+```
+
+4.新建用于上传的jsp页面,上传文件时表单必须声明 enctype="multipart/form-data"
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ 文件上传
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 6.2 文件下载
+
+1.在fileController.java中加上方法:
+
+```java
+ /***
+ * 上传用于下载的文件
+ */
+ @PostMapping("upFileForDownload")
+ public String upFileForDownload(MultipartFile file, HttpSession session, Model model) throws UnsupportedEncodingException {
+ String path = FileUtil.saveFile(file, session.getServletContext().getRealPath("/image"));
+ model.addAttribute("filePath", URLEncoder.encode(path,"utf-8"));
+ model.addAttribute("fileName", file.getOriginalFilename());
+ return "fileDownload";
+ }
+
+ /***
+ * 下载文件
+ */
+ @GetMapping("download")
+ public ResponseEntity downloadFile(String filePath) throws IOException {
+ HttpHeaders headers = new HttpHeaders();
+ File file = new File(filePath);
+ // 解决文件名中文乱码
+ String fileName=new String(file.getName().getBytes("UTF-8"),"iso-8859-1");
+ headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+ headers.setContentDispositionFormData("attachment", fileName);
+
+ return new ResponseEntity(FileUtils.readFileToByteArray(file),
+ headers, HttpStatus.CREATED);
+ }
+```
+
+2.其中fileDownload.jsp 如下:
+
+```jsp
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+
+
+ 文件下载
+
+
+ ${fileName}
+
+
+```
+
+
+
+## 七、Restful风格的请求
+
+1.新建Pet.java实体类
+
+```java
+package com.heibaiying.bean;
+
+import lombok.Data;
+
+/**
+ * @author : heibaiying
+ * @description :测试restful风格的实体类
+ */
+
+@Data
+public class Pet {
+
+ private String ownerId;
+
+ private String petId;
+}
+
+```
+
+2.新建RestfulController.java,用@PathVariable和@ModelAttribute注解进行参数绑定。
+
+注: 在REST中,资源通过URL进行识别和定位。REST中的行为是通过HTTP方法定义的。在进行不同行为时对应HTTP方法和Spring注解分别如下:
+
+- 创建资源时:POST(PostMapping)
+- 读取资源时:GET( @GetMapping)
+- 更新资源时:PUT或PATCH(PutMapping、PatchMapping)
+- 删除资源时:DELETE(DeleteMapping)
+
+```java
+package com.heibaiying.controller;
+
+import com.heibaiying.bean.Pet;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author : heibaiying
+ * @description : Restful 风格的请求
+ */
+
+@RestController
+public class RestfulController {
+
+ @GetMapping("restful/owners/{ownerId}/pets/{petId}")
+ public void get(@PathVariable String ownerId, @PathVariable String petId) {
+ System.out.println("ownerId:" + ownerId);
+ System.out.println("petId:" + petId);
+ }
+
+ @GetMapping("restful2/owners/{ownerId}/pets/{petId}")
+ public void get(@ModelAttribute Pet pet) {
+ System.out.println("ownerId:" + pet.getOwnerId());
+ System.out.println("petId:" + pet.getPetId());
+ }
+
+}
+
+```
+