1. 过滤器
一共三种:
1.1 Filter
1.1.1 两种引入方式
- @Component注解到类上,自动引入
package com.wxm.spring.security.demo.web.filter;
import java.io.IOException;
import java.util.Date;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.stereotype.Component;
@Component
public class TimeFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("time filter start");
long start = new Date().getTime();
chain.doFilter(request, response);
System.out.println("time filter 耗时:"+(new Date().getTime()-start));
System.out.println("time filter finish");
}
@Override
public void init(FilterConfig arg0) throws ServletException{
System.out.println("time filter init");
}
@Override
public void destroy(){
System.out.println("time filter destory");
}
}
- WebMvcConfigurer中用@Bean注解引入(详见WebConfig)
1.1.2 要点
- 当使用第三方类库中的Filter时,有可能没办法自己添加@Component注解,所以用这种方式实现引入
- 只能实现调用前的过滤:doFilter
1.2 HandlerInterceptor
1.2.1 要点
- 不同于Filter,仅仅添加Component不能使此拦截器生效,还需要在WebMvcConfigurer.WebMvcConfigurer中配置一下才会生效,详见WebConfig配置类
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(timeInterceptor);
// 注册TimeInterceptor拦截器
}
- 可以在preHandle中向HttpServletRequest中添加属性,例如:request.setAttribute(“startTime”, new Date().getTime());
- 然后在postHandle中获取,例如:long start = (long)request.getAttribute(“startTime”);
- 能获取到ServletRequest和ServletResponse
- 能对调用的方法实现包围:preHandle、postHandle、afterCompletion
- Controller中抛出异常时,就不会调用postHandler方法,但会调用afterCompletion,所有异常都会先被Filter捕获
- 如果异常被ControllerExceptionHandler捕获,该异常将被消耗掉,那么此处的ex将为null:
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
1.2.1 实例
/**
*
*/
package com.wxm.spring.security.demo.web.interceptor;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.ibatis.javassist.expr.NewArray;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/**
* @author wxm
*
*/
@Component
//不同于Filter,仅仅添加Component不能使此拦截器生效,还需要在WebMvcConfigurer.WebMvcConfigurer中配置一下才会生效,详见WebConfig配置类
public class TimeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("TimeInterceptor preHandler");
//对应控制器类名
try {
System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
//对应控制器方法名
System.out.println(((HandlerMethod)handler).getMethod().getClass().getName());
}catch(ClassCastException e) {
}
request.setAttribute("startTime", new Date().getTime());
//return HandlerInterceptor.super.preHandle(request, response, handler);
return true;
}
//Controller中抛出异常时,就不会调用postHandler方法,但会调用afterCompletion
//所有异常都会先被Filter捕获
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("TimeInterceptor postHandle");
long start = (long)request.getAttribute("startTime");
System.out.println("TimeInterceptor 耗时:"+(new Date().getTime()-start));
//HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
//如果异常被ControllerExceptionHandler捕获,该异常将被消耗掉,那么此处的ex将为null
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("TimeInterceptor afterCompletion");
/*
Enumeration<String> attributeNames = request.getAttributeNames();
Iterator<String>iterator = attributeNames.asIterator();
while(iterator.hasNext()) {
String name = iterator.next();
System.out.println("Attribute: "+name+" "+request.getAttribute(name));
}
*/
Long start = (Long)request.getAttribute("startTime");
System.out.println("TimeInterceptor 耗时:"+(new Date().getTime()-start));
System.out.println("ex is "+ex);
//HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
1.3 切片 @Aspect
- @Aspect+@Component实现自动引入
- 需要spring.aop库
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 能获取Controller、方法、返回对象,获取不到ServletRequest和ServletResponse
例如:
// pjp其实就是对应UserController,pjp.proceed()就是调用UserController的对应方法,Object 对应返回对象
// 以UserCongroller中:public User getInfo(@PathVariable(name = “id” ) String id)方法为例
// pjp => UserController
// pjp.proceed() => UserController.getInfo
// object => User
package com.wxm.spring.security.demo.web.aspect;
import java.util.Date;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TimeAspect {
@Around("execution(* com.wxm.spring.security.demo.web.controller.UserController.*(..))")
public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("TimeAspect start");
long start = new Date().getTime();
Object[] args = pjp.getArgs();
for(Object arg : args) {
System.out.println("arg is "+arg);
}
// pjp其实就是对应UserController,pjp.proceed()就是调用UserController的对应方法,Object 对应返回对象
// 以UserCongroller中:public User getInfo(@PathVariable(name = "id" ) String id)方法为例
// pjp => UserController
// pjp.proceed() => UserController.getInfo
// object => User
Object object = pjp.proceed();
System.out.println("TimeInterceptor 耗时:"+(new Date().getTime()-start));
System.out.println("TimeAspect end");
return object;
}
}
1.4 异步拦截器
多线程需要单独的拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
public void configureAsyncSupport(AsyncSupportConfigurer configure)
//configure.registerCallableInterceptors(interceptors):配置Callable对应的拦截器
//configure.registerDeferredResultInterceptors(interceptors):配置DeferredResult对应的拦截器
//configure.setDefaultTimeout(timeout):设置超时时间
//configure.setTaskExecutor(taskExecutor):设置自定义实现的线程池,代替spring默认线程池(无线程回收复用)
}
}
1.5 @ControllerAdvice
@ControllerAdvice用途:
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
2. 全局异常处理
@ControllerAdvice
//@ControllerAdvice用途:
//全局异常处理
//全局数据绑定
//全局数据预处理
package com.wxm.spring.security.demo.web.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.wxm.spring.security.demo.exception.UserNotExistException;
public class ControllerExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
// 捕获UserNotExistException异常
// 返回HashMap
// 返回Http异常为:服务器内部错误500(HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String,Object> handleUserNotExistException(UserNotExistException ex){
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("id", ex.getId());
resultMap.put("message", ex.getMessage());
return resultMap;
}
}
3. 自动化检测
3.1 hamcrest
3.1.1 库依赖
需要引入org.hamcrest库
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
</dependency>
3.1.2 使用
两步:
- 数据对象中引入标注:
@NotBlank(message = “密码不能为空”)
@Past(message = “生日必须是以前的时间”) //判断:过去时间
public class User {
private int id;
@NotBlank(message = "密码不能为空")
private String password;
@Past(message = "生日必须是以前的时间") //判断:过去时间
private Date birthday;
.
.
.
}
- Controller中引入标注:
@RestController
public class UserController {
@PostMapping("/user")
//@Valid和User中的@NotBlank联用,用于参数校验
//BindingResult,@Valid验证失败时,将错误信息放入BindingResult,此时本方法将不会报错
public User create(@Valid @RequestBody User user,BindingResult errors) {
if(errors.hasErrors()) {
errors.getAllErrors().stream().forEach(error->System.out.println(error.getDefaultMessage()));
}
user.setId(1);
System.out.println(user);
return user;
}
.
.
.
}
3.2 @JsonView
3.2.1 使用
- 声明视图接口,例如:UserSimpleView、UserDetailView
- 用@JsonView标注,将接口配置到数据对象的方法上,例如:@JsonView(UserSimpleView.class)
- 用@JsonView标注,将接口配置到Controller的方法上,例如:@JsonView(UserSimpleView.class)
public class User {
public interface UserSimpleView{};
public interface UserDetailView extends UserSimpleView{};
.
.
.
@JsonView(UserSimpleView.class)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@JsonView(UserDetailView.class)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@JsonView(UserSimpleView.class)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@JsonView(UserSimpleView.class)
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
.
.
.
}
@RestController
public class UserController {
@GetMapping("/user")
@JsonView(User.UserSimpleView.class) //定义视图,是否隐藏密码字段
public List<User> query(@RequestParam String username){
List<User>users = new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User());
return users;
}
.
.
.
}
3.3 自定义注解
3.2.1 使用
- 创建注解类
@Target({ ElementType.METHOD,ElementType.FIELD })
//@Taraget表明本注解可以标注在什么元素上(类、方法)
@Retention(RetentionPolicy.RUNTIME)
//注解@Retention可以用来修饰注解,是注解的注解,称为元注解。
//Retention注解有一个属性value,是RetentionPolicy类型的,Enum RetentionPolicy是一个枚举类型,
//这个枚举决定了Retention注解应该如何去保持,也可理解为Rentention 搭配 RententionPolicy使用。RetentionPolicy有3个值:CLASS RUNTIME SOURCE
//按生命周期来划分可分为3类:
//1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
//2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
//3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Constraint(validatedBy = MyConstraintValidator.class)
//MyConstraintValidator是校验器,执行校验逻辑的类
//MyConstraint是校验注解
public @interface MyConstraint {
//message、groups、Payload是必须有的内容,message返回校验报错信息
String message() default "{javax.validation.constraints.NotBlank.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
- 创建验证器
package com.wxm.spring.security.demo.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import com.wxm.spring.security.demo.service.HelloService;
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Object>{
@Autowired
private HelloService HelloService;
@Override
public void initialize(MyConstraint constraintAnnotation) {
System.out.println("my validator init");
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
HelloService.greeting("tom");
System.out.println(value);
return true;
}
}
- 在数据对象上标注
public class User {
@MyConstraint(message = "@MyConstraint是自建的校验注解")
//@MyConstraint是自建的校验注解
private String username;
.
.
.
}
4. 多线程
4.1 2种实现方式
- 使用Callable接口
- 使用DeferrdResult类
4.2 Callable接口方式
@RestController
public class AsyncController {
@RequestMapping("/orderAsync")
public Callable<String> orderAsync() throws InterruptedException {
logger.info("主线程开始");
Callable<String> result = new Callable<String>() {
@Override
public String call() throws Exception{
logger.info("子线程开始");
Thread.sleep(1000);
logger.info("子线程开始");
return "success";
}
};
logger.info("主线程返回");
return result;
}
}
4.3 DeferrdResult类方式
- Controller
@RestController
public class AsyncController {
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
@RequestMapping("/orderAsync")
@RequestMapping("/orderAsyncWithDeferredResult")
public DeferredResult<String> orderAsyncWithDeferredResult() throws InterruptedException {
logger.info("主线程开始");
String orderNumber = RandomStringUtils.random(8);
mockQueue.setPlaceOrder(orderNumber);
DeferredResult<String> result = new DeferredResult<>();
deferredResultHolder.getMap().put(orderNumber, result);
logger.info("主线程返回");
return result;
}
}
- 实现工作队列:DeferredResultHolder、MockQueue
package com.wxm.spring.security.demo.web.async;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
@Component
public class DeferredResultHolder {
private Map<String, DeferredResult<String>> map = new HashMap<String, DeferredResult<String>>();
public Map<String, DeferredResult<String>> getMap() {
return map;
}
public void setMap(Map<String, DeferredResult<String>> map) {
this.map = map;
}
}
package com.wxm.spring.security.demo.web.async;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class MockQueue {
private Logger logger = LoggerFactory.getLogger(getClass());
private String placeOrder;
private String completeOrder;
public String getPlaceOrder() {
return placeOrder;
}
public void setPlaceOrder(String placeOrder) {
new Thread(()->{
logger.info("接到下单请求, "+placeOrder);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.placeOrder = placeOrder;
this.completeOrder = placeOrder;
logger.info("下单请求处理完毕, "+placeOrder);
}).start();
}
public String getCompleteOrder() {
return completeOrder;
}
public void setCompleteOrder(String completeOrder) {
this.completeOrder = completeOrder;
}
@Override
public String toString() {
return "MockQueue [placeOrder=" + placeOrder + ", completeOrder=" + completeOrder + "]";
}
}
- 模拟工作队列处理服务:QueueListener
package com.wxm.spring.security.demo.web.async;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private MockQueue mockQueue;
@Autowired
private DeferredResultHolder deferredResultHolder;
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
new Thread(()-> {
while(true) {
if(StringUtils.isNotBlank(mockQueue.getCompleteOrder())) {
String orderNumber = mockQueue.getCompleteOrder();
logger.info("返回订单处理结果: "+orderNumber);
deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
mockQueue.setCompleteOrder(null);
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
}
}
5. 自动生成文档:Swagger
5.1 依赖包
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.1</version>
</dependency>
使用
- 主程序类中配置
package com.wxm.spring.security.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootApplication
@EnableSwagger2
public class DemoApplication {
public static void main(String[] args) {
// TODO Auto-generated method stub
SpringApplication.run(DemoApplication.class, args);
}
}
- 添加标注
@RestController
public class UserController {
@PostMapping("/user")
//Swagger2注解
@ApiOperation(value = "POST: 创建用户")
public User create(@Valid @RequestBody User user,BindingResult errors) {
.
.
.
}
@DeleteMapping("/user/{id:\\d+}")
//@ApiParam:Swagger2注解
public void delete(@ApiParam(value = "用户id") @PathVariable String id) {
System.out.println("DELETE: "+id);
}
.
.
.
}
- 访问网页
http://localhost:8080/swagger-ui.html
6. maven父子项目
- wxm-spring-security: pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wxm.spring.security</groupId>
<artifactId>wxm-spring-security</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>wxm-spring-security</name>
<description>wxm-spring-security</description>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<wxm.security.version>1.0.0-SNAPSHOT</wxm.security.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
-->
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- wxm-spring-security-core: pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.wxm.spring.security</groupId>
<artifactId>wxm-spring-security</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../wxm-spring-security</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>wxm-spring-security-core</artifactId>
<name>wxm-spring-security-core</name>
<description>wxm-spring-security-core</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-core</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-web</artifactId>
<version>1.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<!--
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
</dependency>
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- wxm-spring-security-app: pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.wxm.spring.security</groupId>
<artifactId>wxm-spring-security</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../wxm-spring-security</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>wxm-spring-security-app</artifactId>
<name>wxm-spring-security-app</name>
<description>wxm-spring-security-app</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.wxm.spring.security</groupId>
<artifactId>wxm-spring-security-core</artifactId>
<version>${wxm.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- wxm-spring-security-browser: pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.wxm.spring.security</groupId>
<artifactId>wxm-spring-security</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../wxm-spring-security</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>wxm-spring-security-browser</artifactId>
<name>wxm-spring-security-browser</name>
<description>wxm-spring-security-browser</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.wxm.spring.security</groupId>
<artifactId>wxm-spring-security-core</artifactId>
<version>${wxm.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-bom</artifactId>
<version>Bean-SR8</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- wxm-spring-security-demo: pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.wxm.spring.security</groupId>
<artifactId>wxm-spring-security</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../wxm-spring-security</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>wxm-spring-security-demo</artifactId>
<name>wxm-spring-security-demo</name>
<description>wxm-spring-security-demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.wxm.spring.security</groupId>
<artifactId>wxm-spring-security-browser</artifactId>
<version>${wxm.security.version}</version>
</dependency>
<dependency>
<groupId>com.wxm.spring.security</groupId>
<artifactId>wxm-spring-security-app</artifactId>
<version>${wxm.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
7. 自动化测试
package com.wxm.spring.security.demo;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.queryParam;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.net.URI;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartFile;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void whenQuerySuccess() throws Exception {
String result = mockMvc.perform(get("/user")
.param("username", "wxm")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) //return 200
.andExpect(jsonPath("$.length()").value(3))
.andReturn().getResponse().getContentAsString();
System.out.print(result);
}
@Test
public void whenQuerySuccess2() throws Exception {
mockMvc.perform(get("/user2")
.param("username", "wxm")
.param("age", "18")
.param("ageTo", "180")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) //return 200
.andExpect(jsonPath("$.length()").value(3));
}
@Test
public void whenQuerySuccess3() throws Exception {
String result = mockMvc.perform(get("/userPage")
.param("username", "wxm")
.param("age", "18")
.param("ageTo", "180")
.param("size", "15") //每页15条数据
.param("page","3") //查第3页
//.param("sort","age,desc")
.param("sort","id,desc")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) //return 200
.andExpect(jsonPath("$.length()").value(3))
.andReturn().getResponse().getContentAsString();
logger.info(result);
}
@Test
public void wheGetInfoSucess() throws Exception {
String result = mockMvc.perform(get("/user/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()) //return 200
.andExpect(jsonPath("$.username").value("tom"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
@Test
public void whenGetInfoFail() throws Exception {
mockMvc.perform(get("/user2/a")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is4xxClientError()); //return 4xx error
}
@Test
public void whenCreateSuccess() throws Exception {
Date date = new Date();
String content = "{\"username\":\"tom\",\"password\":\"myPassword\",\"birthday\":"+date.getTime()+"}";
String result = mockMvc.perform(post("/user")
.contentType(MediaType.APPLICATION_JSON)
.content(content))
.andExpect(status().isOk()) //return 200
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
logger.info(result);
//logger.info("_________________________________");
}
@Test
public void whenUpdateSuccess() throws Exception {
Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
String content = "{\"id\":\"1\",\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
String result = mockMvc.perform(put("/user/1")
.contentType(MediaType.APPLICATION_JSON)
.content(content))
.andExpect(status().isOk()) //return 200
.andExpect(jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
@Test
public void whenDeleteSuccess() throws Exception {
mockMvc.perform(delete("/user/1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk()); //return 200
}
@Test
public void whenUploadSuccess() throws Exception{
//MockMultipartFile中的第一个参数"file"要与FileController.upload(MultipartFile file)中的file一致
mockMvc.perform(fileUpload("/file")
.file(new MockMultipartFile("file", "test.txt","multipart/form-data","hello upload".getBytes("UTF-8"))))
.andExpect(status().isOk()); //return 200
}
}
8. WireMock
8.1 依赖库
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.26.0</version>
</dependency>
8.2 使用
- 下载wiremock-standalone-2.26.0.jar,官网
- 控制台启动服务:java -jar ./wiremock-standalone-2.26.0.jar --port 8062
- 便携服务代码
package com.wxm.spring.security.demo.wiremock;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.ClassPathResource;
import com.github.tomakehurst.wiremock.client.WireMock;
public class MockServer {
public static void main(String[] args) throws IOException {
configureFor(8062);
removeAllMappings();//清空所有配置
mock("/order/1", "01");
/*
ClassPathResource resource = new ClassPathResource("mock/response/01.txt");
String content = StringUtils.join(FileUtils.readLines(resource.getFile(),"UTF-8").toArray(),"\n");
stubFor(get(urlPathEqualTo("/order/1"))
.willReturn(aResponse().withBody(content)
.withStatus(200)
//.withHeader(key, values)...
)
);
*/
/*
stubFor(get(urlPathEqualTo("/order/1"))
.willReturn(aResponse().withBody("{\"id\":1}")
.withStatus(200)
//.withHeader(key, values)...
)
);
*/
}
private static void mock(String url,String file) throws IOException {
ClassPathResource resource = new ClassPathResource("mock/response/"+file+".txt");
String content = StringUtils.join(FileUtils.readLines(resource.getFile(),"UTF-8").toArray(),"\n");
stubFor(get(urlPathEqualTo(url))
.willReturn(aResponse().withBody(content)
.withStatus(200)
//.withHeader(key, values)...
)
);
}
}
- 创建目录:/src/main/resources/mock/response,创建01.txt文件:(MockServer 读取01.txt中的内容返回)
{
"id":1,
"type":"A"
}
- 运行MockServer
- 浏览器访问:http://localhost:8062/order/1,将返回01.txt中的内容
9. 其他
9.1 分页
@PageableDefault(page = 2,size = 17,sort = “username,asc”) Pageable pageable
9.2 restlet client
9.3 多模块时,自动注入会有问题
https://blog.csdn.net/machuang30508/article/details/78616501