SpringSecurity学习总结-2 第三章 使用SpringMVC开发Restful API

17 篇文章 0 订阅

 第三章 使用SpringMVC开发Restful API

1、编写Restful API的测试用例

package security.demo;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
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;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {

    /**
     * 伪造web请求
     */

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    /**
     * 每个测试用例执行前执行的方法
     */
    @Before
    public void setup(){
        //伪造一个web请求
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    /**
     * 发起一个web请求的测试
     */
    @Test
    public void whenQuerySuccess() throws Exception {
        //发一个模拟的请求,然后对请求返回的内容进行判断
        mockMvc.perform(MockMvcRequestBuilders.get("/userModel")//发一个get类型的请求
                .param("username","哈哈")//请求的参数
                .param("age","18")//请求的参数
                .param("size","15")//分页的参数
                .param("page","3")//分页的参数
                .param("sort","age,desc")//分页排序的参数
                .contentType(MediaType.APPLICATION_JSON_UTF8))//发送内容是JSON格式的
                .andExpect(MockMvcResultMatchers.status().isOk())//期望返回的状态码是200
                .andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));//期望返回的JSON内容的长度是3
    }

}

2、使用注解声明Restful API

(1):RestController表明此Controller提供的是RestAPI。

(2):RequestMapping及其变体,用来映射http请求url到java的方法上。

(3):RequestParam映射请求中的参数到java方法的参数上。

(4):PageableDefault指定分页参数默认值。

3、在Restful API中传递参数

4、用户详情服务(3-3节内容)

(1):@PathVariable映射url中的参数到java方法的参数上。

(2):在url声明中使用正则表达式。

    /**
     * @PathVariable的使用 加上正则表达式(表示参数只能是数字)
     * @return
     */
    @GetMapping("/user/{id:\\d+}")
    public User getUserInfo(@PathVariable(value = "id") String id){
        User user = new User();
        user.setUsername("tom");
        return user;
    }

(3):JsonView控制方法返回的json内容。

使用步骤:

     ①在实体类中使用接口来声明多个视图。

     ②在值对象的get方法上指定视图。(即用来指定该字段可以显示到哪个视图上。)

①和②的完整示例

package security.demo.dto;

import com.fasterxml.jackson.annotation.JsonView;
import lombok.Data;

//@Data
public class User {

    private String username;

    private String password;

    /**
     * 声明简单视图接口
     */
    public interface UserSimpleView{};

    /**
     * 声明详情视图接口
     * 因为UserDetailView extends UserSimpleView
     * 所以UserDetailView会显示UserSimpleView中显示的所有字段
     */
    public interface UserDetailView extends UserSimpleView{};

    public User() {

    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    /**
     * 设定用户名只在简单视图显示
     * @return
     */
    @JsonView(UserSimpleView.class)
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 设定密码只在详情视图显示
     * @return
     */
    @JsonView(UserDetailView.class)
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

     ③在Controller的方法上指定视图。(即用来指定哪些字段可以显示到返回的视图中。)

package security.demo.web.controller;

import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.web.bind.annotation.*;
import security.demo.dto.User;
import security.demo.dto.UserQueryCondition;

import java.util.ArrayList;
import java.util.List;

/**
 * @author chenlc
 */
@RestController
public class UserController {

    /**
     * 使用RequestParam接收参数
     * 使用了简单视图,只返回用户名
     * @param nickname
     * @return
     */
    @JsonView(User.UserSimpleView.class)
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public List<User> query(@RequestParam(value = "username",required = false,defaultValue = "tom") String nickname){
        List<User> users = new ArrayList<>();
        users.add(new User("AA","123"));
        users.add(new User("BB","456"));
        users.add(new User("CC","789"));
        System.out.println("nickname: "+nickname);
        return users;
    }

    /**
     * 使用实体类接收参数
     * @param user
     * @param pageable 用来接收分页参数
     * @return
     */
    @RequestMapping(value = "/userModel",method = RequestMethod.GET)
    public List<User> queryByModel(UserQueryCondition user,@PageableDefault(page = 2,size = 17,sort = "username,asc") Pageable pageable){
        List<User> users = new ArrayList<>();
        users.add(new User());
        users.add(new User());
        users.add(new User());
        System.out.println("user: "+user);

        System.out.println("pageable: "+pageable.getPageNumber());
        System.out.println("pageable: "+pageable.getPageSize());
        System.out.println("pageable: "+pageable.getSort());
        return users;
    }

//    /**
//     * @PathVariable的使用
//     * @return
//     */
//    @GetMapping("/user/{id}")
//    public User getUserInfo(@PathVariable(value = "id") String id){
//        User user = new User();
//        user.setUsername("tom");
//        return user;
//    }

    /**
     * @PathVariable的使用 加上正则表达式(表示参数只能是数字)
     * 使用了详情视图,会返回用户名和密码
     * @return
     */
    @JsonView(User.UserDetailView.class)
    @GetMapping("/user/{id:\\d+}")
    public User getUserInfo(@PathVariable(value = "id") String id){
        User user = new User();
        user.setUsername("tom");
        user.setPassword("000");
        return user;
    }
}

5、处理创建请求(3-4节内容)

(1)@RequestBody映射请求体到java方法的参数上。

当使用post方法发送请求体数据时,Controller中接收方法的参数上要加@RequestBody来映射请求体到参数上(参数一般是实体类的对象)。

(2)日期类型参数的处理。

前后端使用时间戳来传递时间。

(3)@Valid注解验证请求参数的合法性,BindingResult收集校验结果。

@Valid加在方法的参数上,用来校验前端传来的参数与后台的数据要求是否一致。

BindingResult作为方法的参数,用来收集校验的结果。

6、开发用户信息的修改和删除服务(3-5节内容)

(1)常用的校验注解使用

@NotNull 值不能为空

@Null 值必须为空

@Pattern(regex=) 字符串必须匹配正则表达式

@Email 字符串必须是Email地址

@Size 集合中元素数量必须在min和max之间

等等

(2)自定义校验的提示消息

在加注解时,给message字段设置自定义的校验提示消息,而不是使用默认的提示校验提示信息。例如:@NotBlank(message = "密码不能为空")

package security.demo.dto;

import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import security.demo.validator.MyConstraint;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Past;
import java.util.Date;

//@Data
public class User {

    /**
     * id
     */
    @ApiModelProperty("用户id")
    private String id;

    /**
     * 用户名不能为空
     */
    @MyConstraint(message = "用户名不能为空")
    @ApiModelProperty("用户名")
    private String username;

    /**
     * 密码不能为空
     */
    @NotBlank(message = "密码不能为空")
    @ApiModelProperty("用户密码")
    private String password;

    /**
     * 必须是一个过去的时间
     */
    @Past(message = "生日必须是一个过去的时间")
    @ApiModelProperty("用户生日")
    private Date birthDay;

    /**
     * 声明简单视图接口
     */
    public interface UserSimpleView{};

    /**
     * 声明详情视图接口
     * 因为UserDetailView extends UserSimpleView
     * 所以UserDetailView会显示UserSimpleView中显示的所有字段
     */
    public interface UserDetailView extends UserSimpleView{};

    public User() {

    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public User(String id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public User(String id, String username, String password, Date birthDay) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.birthDay = birthDay;
    }

    /**
     * 设定id只在简单视图显示
     * @return
     */
    @JsonView(UserSimpleView.class)
    public String getId() {
        return id;
    }

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

    /**
     * 设定用户名只在简单视图显示
     * @return
     */
    @JsonView(UserSimpleView.class)
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 设定密码只在详情视图显示
     * @return
     */
    @JsonView(UserDetailView.class)
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * 设定生日只在简单视图显示
     * @return
     */
    @JsonView(UserSimpleView.class)
    public Date getBirthDay() {
        return birthDay;
    }

    public void setBirthDay(Date birthDay) {
        this.birthDay = birthDay;
    }

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", birthDay=" + birthDay +
                '}';
    }
}

(3)自定义校验注解

①、新建自定义的校验注解

package security.demo.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)//表明这个注解具有校验作用,使用的校验逻辑由validatedBy指定的校验类来执行
public @interface MyConstraint {
    /**
     * 校验需要的提示信息
     * @return
     */
    String message();

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

②、新建自定义的校验逻辑类

package security.demo.validator;


import org.springframework.beans.factory.annotation.Autowired;
import security.demo.service.HelloService;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 自定义的校验类需要实现ConstraintValidator接口
 * MyConstraint:是需要验证的注解
 * String:需要验证的字段类型,这样该注解就只能用在String类型的参数上,如果设置成Object,则任何类型都能使用该注解
 */
public class MyConstraintValidator implements ConstraintValidator<MyConstraint,Object> {

    @Autowired
    private HelloService helloService;

    /**
     * 校验类初始化时执行
     * @param constraintAnnotation
     */
    @Override
    public void initialize(MyConstraint constraintAnnotation) {
        System.out.println("my custom validator init");
    }

    /**
     * @param o 需要校验的值
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        helloService.greeting("tom");
        System.out.println("需要校验的值:"+o);
//        return true;
        return false;
    }
}

③、将自定义的校验注解加到需要校验的字段上。

    /**
     * 用户名不能为空
     */
    @MyConstraint(message = "用户名不能为空")
    private String username;

7、Restful API错误处理机制(3-6节内容)

(1)SpringBoot默认的错误处理机制

①: 浏览器端发出的请求,springboot如果处理时有错误,会由BasicErrorController的errorHtml()方法来处理,然后返回页面;

②: 非浏览器发出的请求(如PostMan等),springboot如果处理时有错误,会由BasicErrorController的error()方法来处理,然后返回JSON格式的错误信息;

(2)自定义异常处理

①: 浏览器端发出的请求,出现异常时返回的页面是在src/main/resources/templates/error/404.html配置,状态码是多少的就配置以该状态码为名称的页面,并在页面中设置返回的信息。

②: 非浏览器发出的请求(如PostMan等),springboot如果处理时有异常的情况处理方法:

步骤一:创建一个异常处理类

package security.demo.exception;

import java.io.Serializable;

/**
 * 自定义的处理用户不存在的异常处理
 */
public class UserNotExistException extends RuntimeException implements Serializable {

    private static final long serialVersionUID = -4253671095865255861L;

    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;
    }
}

步骤二、创建一个异常的拦截处理器

package security.demo.web.controller;

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 security.demo.exception.UserNotExistException;

import java.util.HashMap;
import java.util.Map;

/**
 * ControllerAdvice:处理Controller抛出的异常
 */
@ControllerAdvice
public class ControllerExceptionHandler {

    /**
     * 处理抛出来的UserNotExistException类型的异常
     * @param exception
     * @return
     */
    @ExceptionHandler(UserNotExistException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String,Object> handlerUserNotExistException(UserNotExistException exception){
        Map<String,Object> result = new HashMap<>();
        result.put("id",exception.getId());
        result.put("message",exception.getMessage());

        return result;
    }

}

只要springboot方法里抛出UserNotExistException类型的异常就会由ControllerExceptionHandler配置的handlerUserNotExistException()方法来处理。

8、Restful API的拦截(以拦截记录时间为例)(3-7、8节内容)

(1)过滤器(Filter) 

优点: filter里面是能够获取到(HttpServletRequest request)和响应(HttpServletResponse response),从request中也能获取到传入的参数信息;

缺点:无法知道是哪一个Controller类中的哪个方法被执行。

Filter随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。

1.启动服务器时加载过滤器的实例,并调用init()方法来初始化实例;

2.每一次请求时都只调用方法doFilter()进行处理;

3.停止服务器时调用destroy()方法,销毁实例。

方式一:实现Filter类(有@Component注解

package security.demo.web.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;
import java.util.Date;

/**
 * @Component 这个注解的目的是将TimeFilter交给容器来管理。
 * 时间过滤器
 */
@Component
public class TimeFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("时间过滤器初始化!");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("时间过滤器开始执行过滤!");
//       long startTime = new Date().getTime();
       long startTime = System.currentTimeMillis();

        //执行下一个过滤器
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("TimeFilter的耗时: "+(System.currentTimeMillis() - startTime));
        System.out.println("时间过滤器结束执行过滤!");
    }

    @Override
    public void destroy() {
        System.out.println("销毁时间过滤器!");
    }
}

方式二:实现Filter((无@Component注解

①先定义一个没有@Component注解的过滤器

package security.demo.web.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;

/**
 * 没有@Component注解的过滤器
 */
public class OtherTimeFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("OtherTimeFilter初始化!");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("OtherTimeFilter开始执行过滤!");
       long startTime = System.currentTimeMillis();

        //执行下一个过滤器
        filterChain.doFilter(servletRequest,servletResponse);
        System.out.println("OtherTimeFilter的耗时: "+(System.currentTimeMillis() - startTime));
        System.out.println("OtherTimeFilter结束执行过滤!");
    }

    @Override
    public void destroy() {
        System.out.println("销毁OtherTimeFilter!");
    }
}

②使用Filter配置类来管理没有@Component注解的过滤器

package security.demo.web.config;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import security.demo.web.filter.OtherTimeFilter;

/**
 * Filter:无法获取处理请求的方法信息
 * springboot中filter的配置和顺序执行:https://www.cnblogs.com/douJiangYouTiao888/p/9183095.html
 * 配置类
 */
@Configuration
public class WebFilterConfig {

    /**
     * FilterRegistrationBean是用来注册Filter的类
     * @return
     */
    @Bean
    public FilterRegistrationBean otherTimeFilter(){

        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        //将自定义的没有@Component注解的过滤器注册进来
        registrationBean.setFilter(new OtherTimeFilter());
        //设置过滤顺序
        registrationBean.setOrder(2);
        //设置过滤器的名字
        registrationBean.setName("OtherTimeFilter");
        //设置需要过滤的地址
        registrationBean.addUrlPatterns("/*");
        return registrationBean;

    }


}

方式一和方式二的区别:

方式二没有使用@Component注解,而是使用配置类来注册过滤器,这样的好处正如在过滤器注册时的设置一样清晰:

①:可以设置过滤器在过滤器链的位置(顺序);

②:可以设置过滤器在过滤器链中的名称;

③:可以设置过滤器拦截的规则;

而使用@Component注解的过滤器则无法更好的实现上述细节。

(2)拦截器(Interceptor)

优点:  可获请求(HttpServletRequest request)和响应(HttpServletResponse response)对象,也可获取方法所在类的类名及方法名信息


        MethodParameter[] methodParameters = ((HandlerMethod)handler).getMethodParameters();
        for (MethodParameter methodParameter : methodParameters) {
            String parameterName = methodParameter.getParameterName();
            // 只能获取参数的名称,不能获取到参数的值
            //System.out.println("parameterName: " + parameterName);
        }

缺点:从handler对象中只能获取参数的名称,不能获取到参数的值,所以无法获取处理请求的方法里的形参信息,(从request中可以间接能获取到传入的参数信息)

它比filter的执行优先级低。

步骤一:需要先实现HandlerInterceptor

package security.demo.web.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Interceptor:可获取处理请求的方法信息
 */
@Component
public class TimeInterceptor implements HandlerInterceptor {

    /**
     * 处理请求的方法执行前运行此方法
     * @param request
     * @param response
     * @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",System.currentTimeMillis());

        return true;
    }

    /**
     * 处理请求的方法执行后运行此方法
     * @param request
     * @param response
     * @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 startTime = (Long)request.getAttribute("startTime");
        System.out.println("Interceptor执行耗时: "+ (System.currentTimeMillis() - startTime));

    }

    /**
     * 处理请求的方法执行完毕后运行此方法
     * @param request
     * @param response
     * @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 startTime = (Long)request.getAttribute("startTime");
        System.out.println("Interceptor执行耗时: "+ (System.currentTimeMillis() - startTime));

        System.out.println("exception is :"+exception);
    }
}

步骤二:新建一个Web的配置类继承WebMvcConfigurationSupport

package security.demo.web.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import security.demo.web.filter.OtherTimeFilter;
import security.demo.web.interceptor.TimeInterceptor;

/**
 * Filter:无法获取处理请求的方法信息
 * springboot中filter的配置和顺序执行:https://www.cnblogs.com/douJiangYouTiao888/p/9183095.html
 * 配置类:配置过滤器和拦截器,如果同时配置了拦截器和过滤器,二者都会起作用
 */
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    @Autowired
    private TimeInterceptor timeInterceptor;

    /**
     * FilterRegistrationBean是用来注册Filter的类
     * @return
     */
    @Bean
    public FilterRegistrationBean otherTimeFilter(){

        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        //将自定义的没有@Component注解的过滤器注册进来
        registrationBean.setFilter(new OtherTimeFilter());
        //设置过滤顺序
        registrationBean.setOrder(2);
        //设置过滤器的名字
        registrationBean.setName("OtherTimeFilter");
        //设置需要过滤的地址
        registrationBean.addUrlPatterns("/*");
        return registrationBean;

    }

    /**
     * 注册拦截器
     * @param registry
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeInterceptor);
    }
}

(3)切片(Aspect)(3-8节内容) (最常使用)

优点: 可以获取方法信息及方法里的参数信息。

缺点: 无法直接获取方法的请求及响应对象。

(通过下面代码可以间接获取方法的请求及响应对象)


ServletRequestAttributes attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attribute.getRequest();
HttpServletResponse response = attribute.getResponse();

常使用在日志,事务,请求参数安全验证等

package security.demo.web.aspect;

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 {

    /**
     * (* security.demo.web.controller.UserController.*(..)):
     * 第一个星号表示任何类型的返回值,第二个星号是UserController类里任何的方法
     * (..):表示方法里的任何参数
     * 整个的意思是作用在UserController的任何方法上,且不论方法的参数是什么,并且不管返回的是什么类型的方法上(即UserController的所有方法上)
     * ProceedingJoinPoint:ProceedingJoinPoint的对象包含了被拦截的方法的所有信息
     */
    @Around("execution(* security.demo.web.controller.UserController.*(..))")
    public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("time aspect start!");
        //获取被拦截的方法
         Object proceed = pjp.proceed();
         //获取方法的参数的数组
         Object[] args = pjp.getArgs();
         for(Object arg : args){
             System.out.println("参数是: "+arg);
         }
        System.out.println("time aspect end!");

        return proceed;
    }

}

三者在程序中的执行顺序:

①Filter  ②Interceptor  (③ControllerAdvice)  ④Aspect

执行后的返回顺序:

controller -> aspect -> controllerAdvice -> Interceptor -> Filter

无

9、文件的上传下载

下载功能需要加入IO的jar包

		<!-- 处理文件上传下载的IO依赖  -->
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.5</version>
		</dependency>

代码:

package security.demo.web.controller;

import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import security.demo.dto.FileInfo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.UUID;

@RestController
@RequestMapping("/file")
public class FileController {

    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping
    public FileInfo update(MultipartFile file){
        //上传时参数的名字
        System.out.println(file.getName());
        //原始的文件名
        String fileOriginalName = file.getOriginalFilename();
        //文件的后缀名
        String fileSuffixName = fileOriginalName.substring(fileOriginalName.lastIndexOf("."));
        //文件的大小
        System.out.println(file.getSize());
        String folder = "自己指定一个路径";

        File uploadPath = new File(folder);
        if(!uploadPath.exists()){
            uploadPath.mkdirs();
        }

        File saveFilePath = new File(folder, UUID.randomUUID().toString()+fileSuffixName);
        try {
            file.transferTo(saveFilePath);
        } catch (IOException e) {
            e.printStackTrace();
        }

        FileInfo fileInfo = new FileInfo(saveFilePath.getAbsolutePath());
        return fileInfo;
    }

    /**
     * 文件下载,因为是写在响应response中的,所以是无返回值的
     * @param request
     * @param response
     * @param id
     */
    @GetMapping("/{id}")
    public void download(HttpServletRequest request, HttpServletResponse response, @PathVariable("id") String id) throws IOException {

            //输入流
            InputStream inputStream = new FileInputStream(new File("文件存储路径","文件的名字(包含后缀)"));
            //输出流
            OutputStream outputStream = response.getOutputStream();
            //设置响应中的内容类型为下载类型
            response.setContentType("application/x-download");
            //设置下载时文件的名字
            response.addHeader("Content-Disposition","attachment;filename=(设定的文件名(包含后缀名))");
            IOUtils.copy(inputStream,outputStream);

            outputStream.flush();
            //关闭流
            inputStream.close();
            outputStream.close();
    }

}

10、异步处理REST服务

(1)使用Runnable异步处理Rest服务

在主线程中使用Callable来发起一个副线程来执行耗时的逻辑。(需要注意的是副线程必须要由主线程发起调用,即主副线程之间必须要由关联)

package security.demo.web.async;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Callable;

/**
 * @author chenlc
 */
@RestController
@Slf4j
public class AsyncController {

    /**
     * 模拟同步请求处理
     * @return
     * @throws InterruptedException
     */
    @GetMapping("/synchroOrder")
    public String synchroOrder() throws InterruptedException {
        log.info("主线程开始运行!");
        Thread.sleep(1000);
        log.info("主线程结束返回!");
        return "success";
    }

    /**
     * 模拟异步请求处理
     * 使用Runnable的Callable来异步处理Rest服务
     * 副线程必须要由主线程发起调用,即主副线程之间必须要由关联
     * @return
     * @throws InterruptedException
     */
    @GetMapping("/asyncOrder")
    public Callable<String> asyncOrder() throws InterruptedException {
        log.info("主线程开始运行!");

        Callable result = new Callable<String>(){
            @Override
            public String call() throws Exception {
                log.info("副线程开始运行!");
                Thread.sleep(1000);
                log.info("副线程结束返回!");
                return "success";
            }
        };
        log.info("主线程结束返回!");
        return result;
    }


}

(2)使用DeferredResult异步处理Rest服务

无

①:模拟一个消息队列和应用2

package security.demo.web.async;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 模拟一个消息队列
 */
@Component
@Slf4j
public class MockQueue {

    /**
     * 下单
     */
    private String placeOrder;

    /**
     * 完成订单
     */
    private String completeOrder;

    public String getPlaceOrder() {
        return placeOrder;
    }

    /**
     * 模拟一个处理下单请求的应用
     * @param placeOrder
     * @throws InterruptedException
     */
    public void setPlaceOrder(String placeOrder){
        new Thread(() ->{
            log.info("接到下单请求!");
            //模拟处理下单请求
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            //模拟完成订单请求
            this.completeOrder = placeOrder;
            log.info("下单请求处理完毕: " + placeOrder);
        }).start();
    }

    public String getCompleteOrder() {
        return completeOrder;
    }

    public void setCompleteOrder(String completeOrder) {
        this.completeOrder = completeOrder;
    }
}

②:新建一个DeferredResult的持有类

package security.demo.web.async;

import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.HashMap;
import java.util.Map;

/**
 * 用来在两个线程之间传递DeferredResult对象
 */
@Component
public class DeferredResultHolder {

    /**
     * String:作为订单号
     * DeferredResult<String>:作为该订单的处理结果对象
     */
    private Map<String, DeferredResult<String>> map = new HashMap<>();

    public Map<String, DeferredResult<String>> getMap() {
        return map;
    }

    public void setMap(Map<String, DeferredResult<String>> map) {
        this.map = map;
    }
}

③:创建消息队列的监听器

package security.demo.web.async;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import java.lang.annotation.Target;

/**
 * 监听器:监听MockQueue中是否放入数据
 */
@Component
@Slf4j
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private MockQueue mockQueue;

    @Autowired
    private DeferredResultHolder deferredResultHolder;

    /**
     * 当服务启动后就开始监听mockQueue
     * @param contextRefreshedEvent
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        new Thread(() ->{ //使用new Thread是为了防止主线程被占用
            //mockQueue中有值就处理,无值就睡1s
            while (true){
                if(StringUtils.isNotBlank(mockQueue.getCompleteOrder())){

                    String orderNumber = mockQueue.getCompleteOrder();
                    log.info("返回订单处理结果: "+orderNumber);
                    deferredResultHolder.getMap().get(orderNumber)
                            .setResult("placeOrderSuccess");//订单处理完成之后设置返回结果
                    mockQueue.setCompleteOrder(null);
                }else{
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

    }
}

④:编写处理异步请求的方法

    /**
     * 使用DeferredResult来处理异步请求
     * @return
     * @throws InterruptedException
     */
    @GetMapping("/deferredResultOrder")
    public DeferredResult<String> deferredResultOrder() throws InterruptedException {
        log.info("主线程开始运行!");

        //随机生成一个8位的订单号
        String orderNumber = RandomStringUtils.randomNumeric(8);

        mockQueue.setPlaceOrder(orderNumber);

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

        log.info("主线程结束返回!");
        return result;
    }

功能的梳理:

步骤一:AsyncController中处理异步请求的deferredResultOrder()方法接收到请求后,随机生成一个订单号,设置到消息队列和DeferredResult中。

步骤二:MockQueue消息队列的setPlaceOrder()方法收到下单请求后,模拟应用2来处理订单。

步骤三:步骤二执行完毕后,QueueListener监听器开始返回订单的处理结果,将订单的处理结果放到DeferredResultHolder的对象中,并清空消息队列。

步骤四:处理异步请求的方法返回给前端处理结果。

(3)异步处理配置

WebMvcConfigurationSupport的configureAsyncSupport()方法

    /**
     * 处理异步的配置
     * @param configurer
     */
    @Override
    protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        //1、注册Callable异步拦截器
        configurer.registerCallableInterceptors();
        //2、注册DeferredResult异步拦截器
        configurer.registerDeferredResultInterceptors();
    }

11、使用Swagger自动生成接口文档

步骤一:demo模块的pom.xml中加入swageer依赖

		<!-- swagger2 -->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>

		<!-- swagger-ui视图 -->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>

步骤二:在启动类上加入@EnableSwagger2

步骤三:在实体类的字段上加@ApiModelProperty("用户id")

步骤四:在Controller中的方法上加@ApiOperation("查询用户详情")

步骤五:重新启动springboot

12、使用WireMock快速伪造Restful服务

提供伪造的服务供前端使用,使用方法见链接:WireMock使用总结_Funnee的博客-CSDN博客_wiremock

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值