持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇

使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)一

  • 使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)一
    • 文章說明
    • 简介
      • 项目环境、组件及目标
      • 基本概念
        • Spring Security
        • Spring IO platform
      • 项目架构
    • RESTFUL API
      • 简介
      • Spring Boot 中 RESTful API 开发
        • 测试框架引入
        • 测试用例创建
          • @RequestParam注解的使用
        • 查询用例
          • UserControllerTest.java
          • UserController.java
          • User.java
        • 创建用例
          • UserControllerTest.java
          • UserController.java
          • User.java
        • 修改与删除用例
          • UserControllerTest.java
          • UserConroller.java
          • User.java
        • 定义校验器
          • 校验器接口定义
          • 定义校验器校验实现类
        • 服务器异常处理
          • Spring Boot 的错误处理机制
          • Spring Boot错误处理机制源码分析
          • 自定义错误处理机制
        • RESTful API的拦截(Filter、Interceptor与Aspect)
          • Filter过滤器:记录所有服务的处理时间
            • 组件自定义配置
            • Spring boot 声明配置
            • Filter 应用缺点
          • Interceptor 拦截器
            • 声明拦截器
            • 配置拦截器
            • 正常运行控制台信息
            • 抛出异常控制台信息
            • 拦截器局限性
          • Aspect (AOP) 切片
            • 添加依赖
            • 声明切片
            • 切片局限性
          • 总结 && 执行顺序
        • 使用RESTful方式处理文件服务
          • 文件上传
          • 文件下载
        • 异步处理RESTful服务
          • 使用Callable异步处理RESTful服务
          • 使用DefferredResult异步处理RESTful服务
          • 异步服务的相关配置
        • Swagger自动生成文档
        • WireMock

文章說明

本系列文章是我的通過購買慕課網中Spring Security技术栈开发企业级认证与授权课程编写的學習筆記

简介

项目环境、组件及目标

  1. 开发环境

    Jdk1.8、Idea、Mysql、maven module

  2. 使用框架组件

    spring boot 、spring security、spring social、spring oauth

  3. 搭建项目目标

    深入理解spring security原理、功能及代码

    基于spring security及相关框架独立开发认证授权相关功能

    掌握抽象和封装的常见技巧,可以编写可重用的模块拱他人使用

基本概念

Spring Security

​ Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring IO platform

​ Spring IO是构建现代应用程序的一个有凝聚力的版本化平台。这是一个模块化的企业级分布,提供了一组策略依赖关系,同时让开发人员完全控制只部署他们所需的部分。

主要解决问题:

​ 在使用Spring的时候,经常会使用到第三方库,一般大家都是根据经验挑选一个版本号或挑选最新的,随意性较大,其实这是有问题的,除非做过完整的测试,保证集成该版本的依赖不会出现问题,且后续集成其它第三方库的时候也不会出现问题,否则风险较大,且后续扩展会越来越困难,因为随着业务复杂度的增加,集成的第三方组件会越来会多,依赖之间的关联也会也来越复杂。

优点:
Spring IO平台提供了各种Spring项目及其依赖项的版本。通过指定的配置添加到您的构建脚本中,您就可以声明您的依赖关系,而无需担心版本号,保证最大限度的扩展,而且该版本的依赖是经过测试的,可以完美的与其它组件结合使用。

项目架构

airports
    | - - airport-core   < - - | 
            | - - pom.xml      |
    | - - airport-browser  - - |(依赖)   <-| 
            | - - pom.xml      |           |
    | - - airport-app      - - |         <-|
            | - - pom.xml                  |(选择性依赖) 
    | - - traffic-forecast   - - - - - - - |
            | - - pom.xml
    | - - pom.xml

airports: 项目父模块,只负责管理其下的子项目,只有一个pom文件,并且pom文件中打包方式为

pom

<modules>
        <module>airportsCoremodule>
        <module>airportsBrowsermodule>
        <module>airportsAppmodule>
        <module>trafficForecastmodule>
    modules>
    <packaging>pompackaging>

    <properties>
        <com.chuIllusion.version>1.0-SNAPSHOTcom.chuIllusion.version>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <java.version>1.8java.version>
    properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.spring.platformgroupId>
                <artifactId>platform-bomartifactId>
                <version>Brussels-SR4version>
                <type>pomtype>
                <scope>importscope>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>Dalston.SR2version>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>2.3.2version>
                <configuration>
                    <source>${java.version}source>
                    <target>${java.version}target>
                    <encoding>${project.build.sourceEncoding}encoding>
                configuration>
            plugin>
        plugins>
    build>

airport-core: 核心业务逻辑,提供核心通用功能

pom.xml

引入核心包,包括jdbc、aop及一些基本工具类,并且引入核心组件:spring security、spring oauth、spring social

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-jdbcartifactId>
    dependency>
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>1.3.0version>
    dependency>
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
    dependency>
    <dependency>
        <groupId>commons-langgroupId>
        <artifactId>commons-langartifactId>
    dependency>
    <dependency>
        <groupId>commons-collectionsgroupId>
        <artifactId>commons-collectionsartifactId>
    dependency>
    <dependency>
        <groupId>commons-iogroupId>
        <artifactId>commons-ioartifactId>
    dependency>
    <dependency>
        <groupId>commons-beanutilsgroupId>
        <artifactId>commons-beanutilsartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-configuration-processorartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-aopartifactId>
    dependency>

    
    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-oauth2artifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.socialgroupId>
        <artifactId>spring-social-configartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.socialgroupId>
        <artifactId>spring-social-coreartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.socialgroupId>
        <artifactId>spring-social-securityartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.socialgroupId>
        <artifactId>spring-social-webartifactId>
    dependency>
dependencies>

airport-browser: 浏览器相关业务,依赖核心项目,拓展浏览器项目特有的功能

pom.xml

引入核心模块的支持,并拓展浏览器特有的支持,如session

<dependencies>
    <dependency>
        <groupId>com.chuIllusiongroupId>
        <artifactId>airports.coreartifactId>
        <version>${com.chuIllusion.version}version>
    dependency>

    <dependency>
        <groupId>org.springframework.sessiongroupId>
        <artifactId>spring-sessionartifactId>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-thymeleafartifactId>
    dependency>
dependencies>

airport-app: App或前后端分离项目中相关业务,依赖核心项目,拓展了前后端分离项目的功能

pom.xml

引入核心模块的支持,并拓展App或前后端分离项目特有的支持,如使用redis代替session存储

<dependencies>
    <dependency>
        <groupId>com.chuIllusionsgroupId>
        <artifactId>airports.coreartifactId>
        <version>1.0-SNAPSHOTversion>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
dependencies>

traffic-forecast: 项目程序,根据项目情况,选择性依赖浏览器模块或App模块

pom.xml

根据需求,引用浏览器核心支持或App核心支持,并且引用编译插件的支持

<dependencies>
    
        
        
        
    
    <dependency>
        <groupId>com.chuIllusiongroupId>
        <artifactId>airports.appartifactId>
        <version>${com.chuIllusion.version}version>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
    dependency>
dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-maven-pluginartifactId>
            <version>1.3.3.RELEASEversion>
            <executions>
                <execution>
                    <goals>
                        
                        <goal>repackagegoal>
                    goals>
                execution>
            executions>
        plugin>
    plugins>
    <finalName>trafficForecastfinalName>
build>

注意

  1. spring boot 包扫描机制

    ​ 在项目程序启动前,一定要保证spring boot 启动类一定是在所有依赖项目中父级包,启动类扫描同级包和下级包的所有类才能生效,否则如果其他项目中有使用配置的类(相对于启动类是在启动类的上一级)则无法生效,建议将使用同一命名,以下是我这几个项目包的结构,每个模块中的代码都对应在其标准包下

    ​ 在核心包中:com.chuillusion.core,在浏览器包中:com.chuillusion.browser,在App包中:com.chuillusion.app,在项目程序中:com.chuIllusion.trafiicforecast,启动类在com.chuIllusion包中创建

  2. 依赖配置

    ​ 在项目中引入jdbc的依赖需要配置数据源、session的依赖需要配置session类型,否则系统启动会报错,根据系统报错信息,查找原因,排查原因,则很快可以解决问题

  3. 启动类

    maven 默认 编译成jar包,使用java -jar运行命令即可将项目运行

    但是在项目中我们需要war包,则要继承SpringBootServletInitializer并且实现其config方法,否则会报错误(无法找到入口)

    @SpringBootApplication
    public class TrafficForecastEntryApplication extends SpringBootServletInitializer {
       public TrafficForecastEntryApplication() {
       }
       protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
           return application.sources(new Class[]{TrafficForecastEntryApplication.class});
       }
       public static void main(String[] args) {
           SpringApplication.run(TrafficForecastEntryApplication.class, args);
       }
    }

RESTFUL API

简介

Representational State Transfer,简称REST,一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

特点:

  1. 用URL描述资源

  2. 使用HTTP方法描述行为,使用HTTP状态码表示不同的结果

    HTTP METHOD:GET(从服务器取出资源(一项或多项))、POST(在服务器新建一个资源)、PUT(在服务器更新资源)、DELETE(从服务器删除资源)

    HTTP STATUS CODE:200(请求成功)、401(未授权)、404(未找到资源)、500(服务器内部错误)

  3. 使用JSON交互数据

  4. RESTful只是一种风格,并不是强制的标准

例子:

URL METHOD DESRCIBE
/product GET 列出所有商品
/product?limit=10 GET 返回指定数量的商品
/product?limit=10&type=1 GET 返回指定类型和数量的商品
/product/id GET 获取指定的商品
/product/id/image GET 获取指定商品的所有图片
/product/id/image/id GET 获取指定商品的指定图片
/product POST 新建商品
/product/id PUT 更新指定的商品信息
/prodect/id DELETE 删除指定的商品

成熟度模型

持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇_第1张图片

Spring Boot 中 RESTful API 开发

​ spring boot 对 RESTful API 提供了多个开发注解,在以下将会慢慢介绍

  1. @RestController : 标明此controller提供RESTful API

    @RestController注解相当于@ResponseBody + @Controller合在一起的作用。

  2. @RequestMapping及其变体。映射HTTP请求URL到JAVA方法上

  3. @RequestParam 映射请求参数到JAVA方法的参数

测试框架引入

<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-testartifactId>
dependency>

测试用例创建

建立测试用例的目的,是为了确保我们的服务能按照我们指定的方式去运行,并且获取预期的效果

//如何运行测试用例
@RunWith(SpringRunner.class)
//标记此类为测试类
@SpringBootTest
public class UserControllerTest {
    //web环境 启动的时候spring 已经创建了,直接注入就好了
    @Autowired
    private WebApplicationContext webApplicationContext;

    //伪造一个mvc的环境
    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    //查询用例
    @Test
    public void whenQuerySuccess() throws Exception{
        //模拟请求
        mockMvc.perform(
                MockMvcRequestBuilders.get("/user")//模拟发出get请求
                .contentType(MediaType.APPLICATION_JSON_UTF8))//设置请求的contentType
                .andExpect(MockMvcResultMatchers.status().isOk())//执行之后,期望的返回结果是返回状态码是200
                //返回一个集合,包含三个元素,jsonPath解析返回的内容,并对json进行判断 $.length()集合的长度
                .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));

    }
}

启动测试用例

Error:

Error:java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test

Cause:测试用例位置必须在Spring boot 启动类同名子包下,才能扫描打

Solve:

  1. 更为测试类位置为Spring boot 启动类同名子包下

  2. 添加指定启动类位置

    @SpringBootTest(classes = StartApplication.class)

Error:

java.lang.AssertionError: Status 
Expected :200
Actual  :404

Cause and Solve:未建立对应的服务,应建立相应的服务

服务的创建

@RestController
public class UserController {
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public List query(){
        return null;
    }
}

Error:

Description
java.lang.AssertionError: No value at JSON path "$.length()", exception: json can not be null or empty

Cause:服务中的返回值为null,而测试对返回值的期待是返回数据的长度为3

Solve:使服务中返回指定的数据长度

@RequestMapping(value = "/user",method = RequestMethod.GET)
public List query(){
    List users = new ArrayList<>();
    users.add(new User());
    users.add(new User());
    users.add(new User());
    return users;
}
@RequestParam注解的使用

在测试用例上添加参数传递

mockMvc.perform(
        MockMvcRequestBuilders.get("/user")//模拟发出get请求
        .contentType(MediaType.APPLICATION_JSON_UTF8)//设置请求的contentType
        .param("username","victorys"))//设置参数

在服务中接受参数

public List query(
        @RequestParam(name="username",//请求中属性名
    required = true,//属性必须,否则报错
    defaultValue = "jojo") //没有则设置默认值
                String username){}

查询用例

掌握

  1. @PathVariable 映射URL片段到java方法的参数

  2. 在url声明中使用正则表达式

  3. @JsonView控制json输出内容

    jsonview使用步骤:1)使用接口声明视图;2)在值对象的get方法上指定视图;3)在controller方法上指定视图

UserControllerTest.java
/**
 * 使用RESTful API 查询用户详情
 * /user/1 代表资源
 * get请求代表操作
 * @throws Exception
 */
@Test
public void whenGetInfoSuccess() throws Exception {
    String result = mockMvc.perform(MockMvcRequestBuilders.get("/user/1")//获取用户为1的信息
            .contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(MockMvcResultMatchers.status().isOk())//用状态码判断是成功还是失败,成功就是200
            .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tom"))//期望值
            .andReturn().getResponse().getContentAsString();//获得返回结果,并以字符串形式输出
    System.out.println(result);
}
**
 * 测试使用正则表达式接受资源的服务
 * @throws Exception
 */
@Test
public void whenGetInfoFail() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.get("/user/a")
            .contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(MockMvcResultMatchers.status().is4xxClientError());
}
UserController.java
@RestController
public class UserController {

    @RequestMapping(value = "/user",method = RequestMethod.GET)
    @JsonView(User.UserSimpleView.class)
    public List query(
            @RequestParam(name="username",required = true,defaultValue = "jojo")
                    String username,
            //spring提供的一个分页信息存储对象
            @PageableDefault(page = 2, size = 17, sort = "username,asc") Pageable pageable){
        List users = new ArrayList<>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        return users;
    }

    /**
     * @PathVariable:将请求资源中的片段,映射到请求服务方法中的参数中
     * @JsonView:使用场景:List query()方法中返回的用户信息没有包含密码信息
     *                      User getInfo()方法返回的用户信息有密码信息
     *                      1.在 User 类中 使用接口定义视图
     *                         public interface UserSimpleView {}; //简单视图
     *                         public interface UserDetailView extends UserSimpleView {};//包含简单视图内容和扩展视图内容
     *                      2.在 User 类中值对象get方法中指定视图,UserDetailView可以显示所有UserSimpleView视图的显示内容
     *                         @JsonView(UserSimpleView.class)
     *                         public String getUsername() { return username;}
     *                         @JsonView(UserDetailView.class)
                               public String getPassword() { return password;}
                           3.在controller中指定视图
     * @param id
     * @return
     */
    //@GetMapping("/{id:\\d+}")
    @JsonView(User.UserDetailView.class)
    @RequestMapping(value = "/user/{id:\\d+}",method = RequestMethod.GET)
    public User getInfo(@PathVariable(name = "id") String id) {
        System.out.println("进入getInfo服务");
        User user = new User();
        user.setUsername("tom");
        return user;
    }
}
User.java
public class User {

   public interface UserSimpleView {};
   public interface UserDetailView extends UserSimpleView {};

   private String id;

   //@MyConstraint(message = "这是一个测试")
   //@ApiModelProperty(value = "用户名")
   private String username;

   @NotBlank(message = "密码不能为空")
   private String password;

   @Past(message = "生日必须是过去的时间")
   private Date birthday;

   @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 String getId() {
      return id;
   }

   public void setId(String id) {
      this.id = id;
   }

   @JsonView(UserSimpleView.class)
   public Date getBirthday() {
      return birthday;
   }

   public void setBirthday(Date birthday) {
      this.birthday = birthday;
   }
}

代码重构

使用RequestMapping变体简化代码

@RequestMapping("/user")
public class UserController {

@GetMapping
public List query(){}

@GetMapping("/{id:\\d+}")
public User getInfo(){}

}

创建用例

  1. @RequestBody映射请求体到java方法的参数
  2. 日期类型参数的处理
  3. @Valid注解和BindingResult验证请求参数的合法性并处理校验结果
UserControllerTest.java
/**
 * 使用RESTful API 创建一个用户
 * @throws Exception
 */
@Test
public void whenCreateSuccess() throws Exception {
    Date date = new Date();
    System.out.println(date.getTime());
    String content = "{\"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
    String reuslt = mockMvc.perform(MockMvcRequestBuilders.post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(content))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
            .andReturn().getResponse().getContentAsString();

    System.out.println(reuslt);
}
UserController.java
/**
 * 通过post请求创建用户
 * @ RequestBody:若请求数据是以json字符串格式数据传输,参数无法封装到方法中的参数
 *                  使用requestBody可以解析json格式为方法体中的参数赋值
 * @ Valid 验证、校验参数
 * @param errors 校验错误信息存储器,与@Valid注解成对出现
 *               若校验不接收错误信息,则不会进入方法体,并且返回错误到请求处
 *               若已接受错误信息,则进入方法体,完成方法体内容
 */
@PostMapping
//@ApiOperation(value = "创建用户")
public User create(@Valid @RequestBody User user,BindingResult errors) {

    if(errors.hasErrors()){//是否有错误校验信息
        //流化并且打印错误信息
        errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));
    }

    System.out.println(user.getId());
    System.out.println(user.getUsername());
    System.out.println(user.getPassword());
    System.out.println(user.getBirthday());

    user.setId("1");
    return user;
}
User.java
@NotBlank(message = "密码不能为空")
private String password;

修改与删除用例

UserControllerTest.java
/**
 * 测试更新请求
 * 请求方式put请求
 * @throws Exception
 */
@Test
public void whenUpdateSuccess() throws Exception {
    Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    System.out.println(date.getTime());
    String content = "{\"id\":\"1\", \"username\":\"tom\",\"password\":null,\"birthday\":"+date.getTime()+"}";
    String reuslt = mockMvc.perform(MockMvcRequestBuilders.put("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(content))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
            .andReturn().getResponse().getContentAsString();

    System.out.println(reuslt);
}

/**
 * 请求方式Delete
 */
@Test
public void whenDeleteSuccess() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.delete("/user/1")
            .contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(MockMvcResultMatchers.status().isOk());
}
UserConroller.java
@PutMapping("/{id:\\d+}")
public User update(@Valid @RequestBody User user, BindingResult errors) {

    if (errors.hasErrors()){
        errors.getAllErrors().stream().forEach(error -> {
            FieldError fieldError = (FieldError)error;
            String message = fieldError.getField() + " " + fieldError.getDefaultMessage();
            System.out.println(message);
        });
    }

    System.out.println(user.getId());
    System.out.println(user.getUsername());
    System.out.println(user.getPassword());
    System.out.println(user.getBirthday());

    user.setId("1");
    return user;
}
/**
 * Delete请求
 * @param id
 */
@DeleteMapping("/{id:\\d+}")
public void delete(@PathVariable String id) {
    System.out.println(id);
}
User.java
@NotBlank(message = "密码不能为空")
private String password;

@Past(message = "生日必须是过去的时间")
private Date birthday;

定义校验器

校验器接口定义

MyConstrain.java

/**
 * 自定义校验注解
 *
 *  自定义校验器要声明以下必须的三个方法
 */
@Target({ElementType.METHOD, ElementType.FIELD})//使用在方法和字段上
@Retention(RetentionPolicy.RUNTIME)//运行时注解
@Constraint(validatedBy = MyConstraintValidator.class)//表名此注解用于校验  validatedBy:当前注解,用哪一个类去校验
public @interface MyConstraint {

   /**
    * 错误信息
    */
   String message();
   Class[] groups() default { };
   Class[] payload() default { };
}
定义校验器校验实现类

MyConstraintValidator.java

/**
 * ConstraintValidator
 *     A:使用哪个注解
 *    B:验证类型。如String,那么注解就只有String类型上起作用
 * 在类结构上,不需要声明为spring管理的类
 *        原因:spring扫描到 ConstraintValidator 接口, 自动将其实现类为自己管理的bean
 */
public class MyConstraintValidator implements ConstraintValidator<MyConstraint, Object> {

   /**
    * 在校验器中,可以注入spring容器中的任何Bean
    */
   @Autowired
   private HelloService helloService;

   /**
    * 校验器初始化工作
    * @param constraintAnnotation
    */
   @Override
   public void initialize(MyConstraint constraintAnnotation) {
      System.out.println("my validator init");
   }

   /**
    * 真正的校验逻辑
    * @param value 需要校验的值
    * @param context 验证器上下文
    * @return
    */
   @Override
   public boolean isValid(Object value, ConstraintValidatorContext context) {
      helloService.greeting("tom");//模拟校验逻辑
      System.out.println(value);
      //return false; //失败
      return true; //校验成功
   }
}

服务器异常处理

Spring Boot 的错误处理机制
  1. 在浏览器中输入不存在的资源地址,spring boot跳转到指定的页面,显示如下信息
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Oct 03 18:46:23 CST 2017
There was an unexpected error (type=Not Found, status=404).
No message available
  1. 在模拟非浏览器请求,spring boot返回json数据
{
    "timestamp": 1507028069711,
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/xxx"
}
Spring Boot错误处理机制源码分析
  1. 浏览器发出的请求

    Request Headers:
    Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    
  2. 非浏览器请求

    Request Headers:
    Accept:*/*
    
  3. 源码分析

    @Controller //本身就是一个控制器
    @RequestMapping({"${server.error.path:${error.path:/error}}"})//处理error请求
    public class BasicErrorController extends AbstractErrorController {
    
    /*
    * 以下两个方法:
    * 根据请求信息不同,返回结果不同
    */
    @RequestMapping(produces = {"text/html"})
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
       HttpStatus status = this.getStatus(request);
       Map model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));//返回一个HTML
       response.setStatus(status.value());
       ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
       return modelAndView == null?new ModelAndView("error", model):modelAndView;
    }
    
    //返回map转为json数据
    @RequestMapping
    @ResponseBody  //将map集合输出为json数据
    public ResponseEntity> error(HttpServletRequest request) {
       Map body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
       HttpStatus status = this.getStatus(request);
       return new ResponseEntity(body, status);
    }
    
    }
自定义错误处理机制

​ 默认情况下,spring boot默认的处理机制已经是可以满足我们一般的开发目标,若有特定需求,可以自定义处理方案

定义浏览器返回的错误处理资源

​ 在资源目录下,创建resources/error目录,根据状态码命名错误页面,自定义错误页面的内容

定义非浏览器(APP)返回的错误处理

  1. 自定义异常类UserNotExistException.java
/**
 * 自定义异常
 */
public class UserNotExistException extends RuntimeException {
   private static final long serialVersionUID = -6112780192479692859L;
   private String id;
   public UserNotExistException(String id) {
      super("user not exist");
      this.id = id;
   }
   public String getId() {return id; }
   public void setId(String id) {this.id = id;}
}
  1. 增加服务
@GetMapping("/{id:\\d+}")
public User getInfo(@PathVariable(name = "id") String id) {
    throw new UserNotExistException("1");//测试异常处理
}
  1. 使用postman发送请求,返回json信息
{
    "timestamp": 1507034846064,
    "status": 500,
    "error": "Internal Server Error",
    "exception": "vic.tjs.Exception.UserNotExistException",
    "message": "user not exist",
    "path": "/user/1"
}
  1. 创建异常处理控制器
/**
 * @ControllerAdvice:处理其他控制器抛出的异常,不处理http请求,只负责处理控制器抛出的异常
 * @ExceptionHandler:当任何一个控制器,抛出指定的异常,都会执行该注解下的方法进行处理
 */
@ControllerAdvice
public class ControllerExceptionHandler {

   @ExceptionHandler(UserNotExistException.class)
   @ResponseBody //返回的map集合转成json
   @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//返回的htto状态码
   public Map handleUserNotExistException(UserNotExistException ex) {
      Map result = new HashMap<>();
      result.put("id", ex.getId());
      result.put("message", ex.getMessage());
      return result;
   }
}
  1. 使用postman发送请求,返回json信息
{
    "id": "1",
    "message": "user not exist"
}

RESTful API的拦截(Filter、Interceptor与Aspect)

Filter过滤器:记录所有服务的处理时间
组件自定义配置
/**
 * 自定义过滤器,拦截所有服务,并且计算服务时长
 */
@Component //声明为spring管理的组件才能识别此类为过滤器
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");
   }
}

访问服务,console输出

time filter start
进入getInfo服务
time filter 耗时:240
time filter finish
time filter start
time filter 耗时:10
time filter finish
Spring boot 声明配置

取消组件

//@Component //声明为spring管理的组件才能识别此类为过滤器
public class TimeFilter implements Filter {}

配置类配置

/**
 * 配置类
 */
@Configuration
public class WebConfig{
   @Bean
   public FilterRegistrationBean timeFilter() {
      FilterRegistrationBean registrationBean = new FilterRegistrationBean();
      TimeFilter timeFilter = new TimeFilter();
      registrationBean.setFilter(timeFilter); 
      List urls = new ArrayList<>();
      urls.add("/*");
      registrationBean.setUrlPatterns(urls); 
      return registrationBean; 
   }
}
Filter 应用缺点

​ 由于Filter是在J2EE规范定义的,未知sping相关的任何东西,只能获取requestresponse请求,无法获取请求的controller信息和哪个方法处理请求的

Interceptor 拦截器
声明拦截器
/**
 * 服务时间记录拦截器
 */
@Component
public class TimeInterceptor implements HandlerInterceptor {
   /**
    * 服务方法执行前 调用
    * @param handler 服务控制器
    * @return
    * @throws Exception
    */
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {
      System.out.println("preHandle");
      //获得服务的控制器的信息
    System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
      System.out.println(((HandlerMethod)handler).getMethod().getName());
      request.setAttribute("startTime", new Date().getTime());
      return true; //是否放行
   }
   /**
    * 服务方法执行完后 调用
    * 若服务方法抛出异常,则此方法不会被调用
    * @param handler
    * @param modelAndView
    * @throws Exception
    */
   @Override
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
         ModelAndView modelAndView) throws Exception {
      System.out.println("postHandle");
      Long start = (Long) request.getAttribute("startTime");
      System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));
   }
   /**
    * 不管服务方法是正常完成还是抛出异常,此方法都会调用
    * @param handler
    * @param exception
    * @throws Exception
    */
   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception)
         throws Exception {
      System.out.println("afterCompletion");
      Long start = (Long) request.getAttribute("startTime");
      System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));
      System.out.println("exception is "+ exception);
   }
}
配置拦截器
/**
 * 配置类
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
   @Autowired
   private TimeInterceptor timeInterceptor; //注入拦截器
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(timeInterceptor);
   }
……
}
正常运行控制台信息
time filter start                                -- doFilter
preHandle                                        -- preHandle
vic.tjs.web.controller.UserController            -- preHandle
query                                            -- preHandle
postHandle                                       -- postHandle
time interceptor 耗时:167                        -- postHandle
afterCompletion                                  -- afterCompletion
time interceptor 耗时:167
exception is null
time filter 耗时:170                             -- doFilter
time filter finish
抛出异常控制台信息

设置异常点

@GetMapping("/{id:\\d+}")
public User getInfo(@PathVariable(name = "id") String id) {
    throw new UserNotExistException("1");//测试异常处理
}

异常输出

time filter start
preHandle
vic.tjs.web.controller.UserController
getInfo
                                               -- 没有执行postHandle
afterCompletion
time interceptor 耗时:30
exception is null                   
time filter 耗时:40
time filter finish

疑问?exception为null?

解释:exception为空是因为之前配置中把异常处理了,自定义的异常控制处理器 比 拦截器中的afterCompletion 先执行,把异常处理完后才调用拦截器,因此不会获得异常信息

@ControllerAdvice
public class ControllerExceptionHandler {
   @ExceptionHandler(UserNotExistException.class)
   public Map handleUserNotExistException(UserNotExistException ex) {}

解决:

  1. 改造异常抛出点
public User getInfo(@PathVariable(name = "id") String id) {
        throw new RuntimeException("user not exist"); }
  1. 抛出异常
time filter start
preHandle
vic.tjs.web.controller.UserController
getInfo
afterCompletion
time interceptor 耗时:0
exception is java.lang.RuntimeException: user not exist
………
//拦截器会拦截所有的控制器
//以下的spring 异常处理控制器也被拦截了
preHandle
org.springframework.boot.autoconfigure.web.BasicErrorController
error
postHandle
time interceptor 耗时:0
afterCompletion
time interceptor 耗时:0
exception is null
拦截器局限性

​ 拦截器Object handle中的能获取请求的控制器的信息,但无法获取请求的参数的值

DispatcherServlet.java分发请求源码分析

public class DispatcherServlet extends FrameworkServlet {
protected void doService(HttpServletRequest request, HttpServletResponse response){
    this.doDispatch(request, response);
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response){
   //此方法是调用拦截器中的preHandle
if(!mappedHandler.applyPreHandle(processedRequest, response)) {
           return;
      }
//此处真正的调用控制器,方法参数的拼装在此处进行,所以拦截器无法获取参数信息
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
      if(asyncManager.isConcurrentHandlingStarted()) {
         return;
      }
}
}
Aspect (AOP) 切片

持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇_第2张图片

添加依赖

pom.xml中添加aop支持

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
dependency>
声明切片
@Aspect  //定义一个切片类
@Component
public class TimeAspect {
   /**
    * Around:什么时候执行
    * execution:在哪些符合条件的方法上执行
    * @param pjp 当前被拦截的方法信息对象
    * @return
    */
   @Around("execution(* vic.tjs.web.controller.UserController.*(..))")
   public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
      System.out.println("time aspect start");
      //获取被拦截的控制器的参数        
      Object[] args = pjp.getArgs();
      for (Object arg : args) {
         System.out.println("arg is "+arg);
      }
      long start = new Date().getTime();
      Object object = pjp.proceed(); //很像Filter中的doFilter,得到的结果就是控制器的返回值
      System.out.println("time aspect 耗时:"+ (new Date().getTime() - start));
      System.out.println("time aspect end");
      return object;
   }
}

控制台输出

time filter start #过滤器
preHandle # 拦截器
vic.tjs.web.controller.UserController$$EnhancerBySpringCGLIB$$678cdc94 #拦截器
getInfo #拦截器
time aspect start # 切片
arg is 1 #切片
进入getInfo服务 #服务
time aspect 耗时:3 #切片
time aspect end # 切片
postHandle  # 拦截器
time interceptor 耗时:83 # 拦截器
afterCompletion #拦截器
time interceptor 耗时:83
exception is null
time filter 耗时:93 #过滤器
time filter finish #过滤器
切片局限性

​ 切片不能像Filter和Interceptor能获取http请求与响应

总结 && 执行顺序

​ 总结:这三种拦截方式各有各好处,应根据需求选取其中一种

​ 执行顺序:

持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇_第3张图片

使用RESTful方式处理文件服务

文件上传
  1. 添加常用io包
<dependency>
  <groupId>commons-iogroupId>
  <artifactId>commons-ioartifactId>
dependency>
  1. 创建测试类,模拟上传文件
/**
 * 模拟客户端实现文件上传
 * @throws Exception
 */
@Test
public void whenUploadSuccess() throws Exception {
    String result = mockMvc.perform(MockMvcRequestBuilders.fileUpload("/file")
            .file(new MockMultipartFile("file", "test.txt", "multipart/form-data", "hello upload".getBytes("UTF-8"))))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andReturn().getResponse().getContentAsString();
    System.out.println(result);
}
  1. 创建服务
/**
 * 文件上传处理控制器
 */
@RestController
@RequestMapping("/file")
public class FileController {
//上传到本地
   private String folder = "D:\\idea_location\\vicsecuritydemo\\src\\main\\java\\vic\\tjs\\web\\controller
   @PostMapping
   public FileInfo upload(MultipartFile file) throws Exception {
      //上传文件的信息
      System.out.println(file.getName());
      System.out.println(file.getOriginalFilename());
      System.out.println(file.getSize());
      File localFile = new File(folder, new Date().getTime() + ".txt");
      file.transferTo(localFile);
      return new FileInfo(localFile.getAbsolutePath());
   }
}

注:这里的FileInfo只是一个自定义的简单实体类,用来记录文件位置

文件下载
  1. 创建服务
@GetMapping("/{id}")
public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws Exception {
   /**
    * jdk 7 语法,将流声明在try()中,调用完后可以自动关闭对应的流,简化开发
    */
   try (InputStream inputStream = new FileInputStream(new File(folder, id + ".txt"));
         OutputStream outputStream = response.getOutputStream();) {
      response.setContentType("application/x-download");
      response.addHeader("Content-Disposition", "attachment;filename=test.txt");
      IOUtils.copy(inputStream, outputStream);
      outputStream.flush();
   }
}

异步处理RESTful服务

持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇_第4张图片

使用Callable异步处理RESTful服务

副线程由主线程唤起

@RestController
public class AsyncController {
private Logger logger = LoggerFactory.getLogger(getClass());
@RequestMapping("/order")
public Callable order(){
   logger.info("主线程开始");
        Callable result = new Callable() {
            @Override
            public String call() throws Exception {
                logger.info("副线程开始");
                Thread.sleep(5000);
                logger.info("副线程返回");
                return "success";
            }
        };
        logger.info("执行其他事情 模拟  1 ");
        //Thread.sleep(1000);
        logger.info("执行其他事情 模拟  2 ");
        //Thread.sleep(1000);
        logger.info("主线程结束");
        //Thread.sleep(1000);
        return result;
}
}

补充:Callable与Runnable区别

  1. Callable中的call方法有返回值,而Runnable中的run方法无返回值;
  2. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

测试:开两个浏览器客户端同时发起请求,得到的结果是:

2018-03-16 20:45:06.860  INFO 8276 --- [p-nio-80-exec-1] vic.tjs.web.async.AsyncController        : 主线程开始
2018-03-16 20:45:06.860  INFO 8276 --- [p-nio-80-exec-1] vic.tjs.web.async.AsyncController        : 执行其他事情 模拟  1 
2018-03-16 20:45:06.860  INFO 8276 --- [p-nio-80-exec-1] vic.tjs.web.async.AsyncController        : 执行其他事情 模拟  2 
2018-03-16 20:45:06.860  INFO 8276 --- [p-nio-80-exec-1] vic.tjs.web.async.AsyncController        : 主线程结束
2018-03-16 20:45:06.860  INFO 8276 --- [      MvcAsync4] vic.tjs.web.async.AsyncController        : 副线程开始
2018-03-16 20:45:07.848  INFO 8276 --- [p-nio-80-exec-2] vic.tjs.web.async.AsyncController        : 主线程开始
2018-03-16 20:45:07.848  INFO 8276 --- [p-nio-80-exec-2] vic.tjs.web.async.AsyncController        : 执行其他事情 模拟  1 
2018-03-16 20:45:07.848  INFO 8276 --- [p-nio-80-exec-2] vic.tjs.web.async.AsyncController        : 执行其他事情 模拟  2 
2018-03-16 20:45:07.848  INFO 8276 --- [p-nio-80-exec-2] vic.tjs.web.async.AsyncController        : 主线程结束
2018-03-16 20:45:07.850  INFO 8276 --- [      MvcAsync5] vic.tjs.web.async.AsyncController        : 副线程开始
2018-03-16 20:45:11.860  INFO 8276 --- [      MvcAsync4] vic.tjs.web.async.AsyncController        : 副线程返回
2018-03-16 20:45:12.852  INFO 8276 --- [      MvcAsync5] vic.tjs.web.async.AsyncController        : 副线程返回

疑问:副线程的执行都是在return时返回结果?测试一下将服务中的的返回结果改为return null;得到如下结果

2018-03-16 20:51:01.144  INFO 7668 --- [p-nio-80-exec-2] vic.tjs.web.async.AsyncController        : 主线程开始
2018-03-16 20:51:01.144  INFO 7668 --- [p-nio-80-exec-2] vic.tjs.web.async.AsyncController        : 执行其他事情 模拟  1 
2018-03-16 20:51:01.144  INFO 7668 --- [p-nio-80-exec-2] vic.tjs.web.async.AsyncController        : 执行其他事情 模拟  2 
2018-03-16 20:51:01.144  INFO 7668 --- [p-nio-80-exec-2] vic.tjs.web.async.AsyncController        : 主线程结束

从上面的结果,可以清楚的看到,Callable线程就是在return时启动执行的。也就是说,主线程并未结束,而是唤起了副线程,并且副线程执行返回的结果还是交给主线程,并由主线程将结果返回,从这一点上,让我对异步这个概念混淆不清了。那跟单线程就没什么区别了。

备注:有时间深入了解一下这个东西,毕竟知识有限

使用DefferredResult异步处理RESTful服务

持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇_第5张图片

  1. 模拟下单队列
/**
 * 模拟下单队列
 */
@Component
public class MockQueue {
   private String placeOrder;
   private String completeOrder;
   private Logger logger = LoggerFactory.getLogger(getClass());
   public String getPlaceOrder() { return placeOrder; }

   public void setPlaceOrder(String placeOrder) throws Exception {
      new Thread(() -> {
         logger.info("接到下单请求, " + placeOrder);
         try {
            Thread.sleep(1000);
         } catch (Exception e) {
            e.printStackTrace();
         }
         this.completeOrder = placeOrder;
         logger.info("下单请求处理完毕," + placeOrder);
      }).start();
   }
   public String getCompleteOrder() {return completeOrder; }
   public void setCompleteOrder(String completeOrder) {this.completeOrder = completeOrder; }
}
  1. 在线程间传递DefferredResult
@Component
public class DeferredResultHolder {
//key:订单号,value:订单处理结果
   private Map> map = new HashMap>();
   public Map> getMap() {
      return map;
   }
   public void setMap(Map> map) {
      this.map = map;
   }
}
  1. 生成服务
@RestController
public class AsyncController {
   @Autowired
   private MockQueue mockQueue;
   @Autowired
   private DeferredResultHolder deferredResultHolder;

   private Logger logger = LoggerFactory.getLogger(getClass());

   @RequestMapping("/order")
   public DeferredResult order() throws Exception {
      logger.info("主线程开始");
      //随机生成订单号
      String orderNumber = RandomStringUtils.randomNumeric(8);
      mockQueue.setPlaceOrder(orderNumber);//设置订单号

      DeferredResult result = new DeferredResult<>();
      deferredResultHolder.getMap().put(orderNumber, result);

      return result;
   }
}
  1. 设置监听器
/**
 * 监听下单,返回Result
 */
@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");//当DeferredResult.setResult被执行,就意味着异步执行完毕需要向浏览器返回向信息
               mockQueue.setCompleteOrder(null);

            }else{
               try {
                  Thread.sleep(100);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         }
      }).start();
   }
}
  1. 调用服务,后台输出信息
2017-10-04 14:00:44.512  INFO 6976 --- [nio-8080-exec-1] vic.tjs.web.async.AsyncController        : 主线程开始
2017-10-04 14:00:44.516  INFO 6976 --- [      Thread-10] vic.tjs.web.async.MockQueue              : 接到下单请求, 75192809
2017-10-04 14:00:45.517  INFO 6976 --- [      Thread-10] vic.tjs.web.async.MockQueue              : 下单请求处理完毕,75192809
2017-10-04 14:00:45.597  INFO 6976 --- [       Thread-7] vic.tjs.web.async.QueueListener          : 返回订单处理结果:75192809
  1. 页面输出信息
place order success
异步服务的相关配置
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
   /**
    * 配置异步支持
    * 需要支持异步的拦截器都需要配在这里,支持异步的拦截器和addInterceptors不一样
    * @param configurer
    */
   @Override
   public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
      //super.configureAsyncSupport(configurer);
      configurer.registerCallableInterceptors(...); //支持Callable的拦截器
      configurer.registerDeferredResultInterceptors(..);//支持DeferredResult的拦截器
      configurer.setDefaultTimeout(..);//异步请求的默认超时时间
      configurer.setTaskExecutor(..);//设置可重用的线程池
   }
}

Swagger自动生成文档

​ 目前很多项目都是前后端分离,并且前后端开发并行工作,后端人员手动编写文档,效率低而且难度大,并且在修改代码的同时,也需要修改文档。使用文档自动生成可以解决以上这些缺点,并且很好的使前端开发人员容易的调用所有的RESTful服务

  1. pom.xml中添加依赖

<dependency>
  <groupId>io.springfoxgroupId>
  <artifactId>springfox-swagger2artifactId>
  <version>2.7.0version>
dependency>

<dependency>
  <groupId>io.springfoxgroupId>
  <artifactId>springfox-swagger-uiartifactId>
  <version>2.7.0version>
dependency
  1. 启动器中开启注解
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@EnableSwagger2
public class StartApplication{}
  1. 访问文档地址/swagger-ui.html
  2. 页面效果

持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇_第6张图片

持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇_第7张图片

  1. 自定义文档显示

@ApiOperation注解声明在方法中

@ApiOperation(value = "创建用户")
public User create(@Valid @RequestBody User user,BindingResult errors){}

@ApiParam注解声明在方法参数中

public void delete(@ApiParam(value = "用户id") @PathVariable String id){}

@ApiModelProperty注解声明在对象属性中

public class User {
    @ApiModelProperty(value = "用户名")
    private String username;
}

持续更新-使用 Maven Module 搭建spring boot项目(整合Spring Security、Spring Social、spring OAuth)第一篇_第8张图片

WireMock

​ 一般开发项目都会分模块进行,比如都会把前端和后端分开,在前端和后端里面也通常是分模块开发的。当开发进度不一致时,可以对依赖接口构建Mock Service,模拟不同输入/数据/场景,这样不至于影响本模块的开发进度。

  1. 进入WireMock官网,进行服务器下载

http://wiremock.org/docs/running-standalone/

  1. 将下载后的wiremock-standalone-2.14.0.jar运行,可以参考官方文档进行参数设置

    该文件目录下执行命令: java -jar wiremock-standalone-2.8.0.jar  - -port 8020
    
    # 其他参数 - - verbose 开启日志
    
  2. 项目中使用

<dependency>
  <groupId>com.github.tomakehurstgroupId>
  <artifactId>wiremockartifactId>
dependency>
<!— wiremock需要依赖http工具类 -->
<dependency>
  <groupId>org.apache.httpcomponentsgroupId>
  <artifactId>httpmimeartifactId>
dependency>
  1. 搭建服务
/**
 * WireMock 服务器搭建
 */
public class MockServer {

   /**
    * 使用静态引入WireMock,直接调用方法
    */
   public static void main(String[] args) throws IOException {
      configureFor(8020);
      removeAllMappings();

      mock("/order/1", "01");
      mock("/order/2", "02");
   }

   private static void mock(String url, String file) throws IOException {
      ClassPathResource resource = new ClassPathResource("vic/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)));
   }

}
  1. 资源创建

在资源目录下创建文件:vic/response/01.txt

{
   "id":1,
   "type":"C"
   "data" : {
        "name" : "victorys",
        "age" : 22,
        "sex" : 0
        "interest" : {
            "0" : "basketball",
            "1" : "swingming"
        }
   }
}

通过访问/order/1或得返回结果

你可能感兴趣的:(Spring,Security)