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" %> - - - 文件上传 - - - - -
- 请选择上传文件:
- -
- -
- 请选择上传文件(多选):
- -
- -
- 请选择上传文件1:
- 请选择上传文件2:
- 文件内容额外备注:
- -
- - - - -``` - -#### 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" %> + + + 文件上传 + + + + +
+ 请选择上传文件:
+ +
+ +
+ 请选择上传文件(多选):
+ +
+ +
+ 请选择上传文件1:
+ 请选择上传文件2:
+ 文件内容额外备注:
+ +
+ + + + +``` + +#### 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" %> - - - 文件上传 - - - - -
- 请选择上传文件:
- -
- -
- 请选择上传文件(多选):
- -
- -
- 请选择上传文件1:
- 请选择上传文件2:
- 文件内容额外备注:
- -
- - - - -``` - -#### 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" %> + + + 文件上传 + + + + +
+ 请选择上传文件:
+ +
+ +
+ 请选择上传文件(多选):
+ +
+ +
+ 请选择上传文件1:
+ 请选择上传文件2:
+ 文件内容额外备注:
+ +
+ + + + +``` + +#### 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()); + } + +} + +``` +