• 2019-2-24


    关键词:SpringMVC和SpringBoot常见功能、Spring Security

    一、《Spring Security开发安全的REST服务》视频笔记---part1、springmvc和SpringBoot部分

    1、Spring boot单元测试

    eg:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserControllerTest {
    
    	@Autowired
    	private WebApplicationContext wac;
    
    	private MockMvc mockMvc;
    
    	@Before
    	public void setup() {
    		mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    	}
    	
    	@Test
    	public void whenUploadSuccess() throws Exception {
    		String result = mockMvc.perform(fileUpload("/file")
    				.file(new MockMultipartFile("file", "test.txt", "multipart/form-data", "hello upload".getBytes("UTF-8"))))
    				.andExpect(status().isOk())
    				.andReturn().getResponse().getContentAsString();
    		System.out.println(result);
    	}
    	
    
    	@Test
    	public void whenQuerySuccess() throws Exception {
    		String result = mockMvc.perform(
    				get("/user").param("username", "jojo").param("age", "18").param("ageTo", "60").param("xxx", "yyy")
    						// .param("size", "15")
    						// .param("page", "3")
    						// .param("sort", "age,desc")
    						.contentType(MediaType.APPLICATION_JSON_UTF8))
    				.andExpect(status().isOk()).andExpect(jsonPath("$.length()").value(3))
    				.andReturn().getResponse().getContentAsString();
    		
    		System.out.println(result);
    	}
    
    	@Test
    	public void whenGetInfoSuccess() throws Exception {
    		String result = mockMvc.perform(get("/user/1")
    				.contentType(MediaType.APPLICATION_JSON_UTF8))
    				.andExpect(status().isOk())
    				.andExpect(jsonPath("$.username").value("tom"))
    				.andReturn().getResponse().getContentAsString();
    		
    		System.out.println(result);
    	}
    	
    	@Test
    	public void whenGetInfoFail() throws Exception {
    		mockMvc.perform(get("/user/a")
    				.contentType(MediaType.APPLICATION_JSON_UTF8))
    				.andExpect(status().is4xxClientError());
    	}
    	
    	@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(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
    				.content(content))
    				.andExpect(status().isOk())
    				.andExpect(jsonPath("$.id").value("1"))
    				.andReturn().getResponse().getContentAsString();
    		
    		System.out.println(reuslt);
    	}
    	
    	@Test
    	public void whenCreateFail() throws Exception {
    		
    		Date date = new Date();
    		System.out.println(date.getTime());
    		String content = "{"username":"tom","password":null,"birthday":"+date.getTime()+"}";
    		String reuslt = mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
    				.content(content))
    //				.andExpect(status().isOk())
    //				.andExpect(jsonPath("$.id").value("1"))
    				.andReturn().getResponse().getContentAsString();
    		
    		System.out.println(reuslt);
    	}
    	
    	@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(put("/user/1").contentType(MediaType.APPLICATION_JSON_UTF8)
    				.content(content))
    				.andExpect(status().isOk())
    				.andExpect(jsonPath("$.id").value("1"))
    				.andReturn().getResponse().getContentAsString();
    		
    		System.out.println(reuslt);
    	}
    	
    	@Test
    	public void whenDeleteSuccess() throws Exception {
    		mockMvc.perform(delete("/user/1")
    				.contentType(MediaType.APPLICATION_JSON_UTF8))
    				.andExpect(status().isOk());
    	}
    
    }
    

      

    2、请求url正则表达式

    eg:

    @PutMapping("/{id:\d+}")

    3、@JsonView注解

    使用步骤:

    (1)使用接口来声明多个视图

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

      

    上面声明了UserSimpleView和UserDetailView两个接口。

    (2)在值对象的get方法上指定视图

    例如上述代码中,有两处不同:

    @JsonView(UserSimpleView.class)
    	public String getUsername() {
    		return username;
    	}
    

      和

    @JsonView(UserDetailView.class)
    	public String getPassword() {
    		return password;
    	}
    

      而且由于UserSimpleView和UserDetailView是继承关系,所以显示密码的地方也会显示用户名。

    (3)在Controller方法上指定视图

    例如:

    @GetMapping
    	@JsonView(User.UserSimpleView.class)
    	@ApiOperation(value = "用户查询服务")
    	public List<User> query(UserQueryCondition condition,
    			@PageableDefault(page = 2, size = 17, sort = "username,asc") Pageable pageable) {
    
    		System.out.println(ReflectionToStringBuilder.toString(condition, ToStringStyle.MULTI_LINE_STYLE));
    
    		System.out.println(pageable.getPageSize());
    		System.out.println(pageable.getPageNumber());
    		System.out.println(pageable.getSort());
    
    		List<User> users = new ArrayList<>();
    		users.add(new User());
    		users.add(new User());
    		users.add(new User());
    		return users;
    	}
    

      和

            @GetMapping("/{id:\d+}")
    	@JsonView(User.UserDetailView.class)
    	public User getInfo(@ApiParam("用户id") @PathVariable String id) {
    //		throw new RuntimeException("user not exist");
    		System.out.println("进入getInfo服务");
    		User user = new User();
    		user.setUsername("tom");
    		return user;
    	}
    

      

    4、@RequestBody映射请求体到java方法的参数

    eg:

    后台接收:

    public User create(@RequestBody User user)

    前端发送json字符串:

    @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(post("/user").contentType(MediaType.APPLICATION_JSON_UTF8)
    .content(content))
    .andExpect(status().isOk())
    .andExpect(jsonPath("$.id").value("1"))
    .andReturn().getResponse().getContentAsString();

    System.out.println(reuslt);
    }

    5、日期类型参数的处理

    后台统一返回时间戳,前端根据自己的需求将时间戳转成特定格式的日期时间。

    eg:

    Date date = new Date();
    String content = "{"username":"tom","password":null,"birthday":"+date.getTime()+"}";

    6、@Valid注解和BindingResult验证请求参数的合法性并处理校验结果

    愚蠢的逐个校验(代码重构时不方便维护):

    if(StringUtils.isNotBlank(pwd)){
    
    }
    

      可以使用@NotBlank:

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

      

    但如果仅仅只加这个注解是不行的!!!还要在控制器方法中配合@Valid注解使用:

            @PostMapping
    	@ApiOperation(value = "创建用户")
    	public User create(@Valid @RequestBody User user) {
    
    		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;
    	}
    

      注意:如果没有加上BindingResult参数,且传进来的password为空,则该方法根本不会执行,而是直接报错!!!如果想要知道是发生了什么错误并且能进入方法进行处理,则:

            @PostMapping
    	@ApiOperation(value = "创建用户")
    	public User create(@Valid @RequestBody User user, BindingResult errors) {
        
                    if(errors.hasErrors()){
                        errors.getAllErrors().stream().forEach(err->System.out.println(err.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;
    	}    
    

      

    扩展:还有很多常用的验证注解在Hibernate Validator可以查看。

    7、自定义校验注解

    (1)创建注解(

    注意:

    @Constraint(validatedBy = MyConstraintValidator.class)

    @Target({ElementType.METHOD, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = MyConstraintValidator.class)
    public @interface MyConstraint {
    	
    	String message();
    
    	Class<?>[] groups() default { };
    
    	Class<? extends Payload>[] payload() default { };
    
    }
    

      

    (2)定义刚刚这个注解的校验逻辑由谁来执行(泛型参数中,第一个是注解名,第二个是注解适合用在什么类型的参数)

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

    (3)使用注解:

            @MyConstraint(message = "这是一个测试")
    	private String username;
    

      

    8、Spring Boot中默认的错误处理机制和自定义异常处理

    可以使用chrome的一个插件来模拟app的请求:Restlet Client

    SpringBoot默认的错误处理机制是:检测到是浏览器发出,则返回html;是app发出,则返回json字符串。源码在BasicErrorController

     浏览器自定义返回内容:如果想在指定错误发生(比如404)时返回指定html页面,可以在resources文件夹下再新建一个resources/error/404.html,再发生404时将显示这个页面。

    客户端自定义返回内容:

    ①控制器方法内部抛出一个异常(可以是一个自定义的继承RuntimeException的异常类)

    ②定义一个处理控制器异常的类:

    @ControllerAdvice
    public class ControllerExceptionHandler {
    
    	@ExceptionHandler(UserNotExistException.class)
    	@ResponseBody
    	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    	public Map<String, Object> handleUserNotExistException(UserNotExistException ex) {
    		Map<String, Object> result = new HashMap<>();
    		result.put("id", ex.getId());
    		result.put("message", ex.getMessage());
    		return result;
    	}
    
    }
    

      

    9、Restful API的拦截的3种机制:

    (1)过滤器(Filter)

    @Component
    public class TimeFilter implements Filter {
    
    	/* (non-Javadoc)
    	 * @see javax.servlet.Filter#destroy()
    	 */
    	@Override
    	public void destroy() {
    		System.out.println("time filter destroy");
    	}
    
    	/* (non-Javadoc)
    	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
    	 */
    	@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");
    	}
    
    	/* (non-Javadoc)
    	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
    	 */
    	@Override
    	public void init(FilterConfig arg0) throws ServletException {
    		System.out.println("time filter init");
    	}
    
    }
    

      假如需要将第三方的Filter加入到项目中(这些Filter不会有@Component注解),需要自己写配置类将该第三方Filter加入到Filter链中(该配置和在web.xml中配置filter标签效果一样,只不过SpringBoot不能用web.xml配置):

    @Configuration
    public class WebConfig {
    	
    	@Bean
    	public FilterRegistrationBean timeFilter() {
    		
    		FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    		
    		TimeFilter timeFilter = new TimeFilter();
    		registrationBean.setFilter(timeFilter);
    		
    		List<String> urls = new ArrayList<>();
    		urls.add("/*");
    		registrationBean.setUrlPatterns(urls);
    		
    		return registrationBean;
    		
    	}
    
    }
    

      这种用法的局限是,filter属于j2ee的东西而不是spring的东西,所以filter内无法知道request是由哪个Controller的哪个方法处理的,如果想知道则要用第2种机制(Spring框架本身提供的)。

    (2)拦截器(Interceptor)

    @Component
    public class TimeInterceptor implements HandlerInterceptor {
    
    	/* (non-Javadoc)
    	 * @see org.springframework.web.servlet.HandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
    	 */
    	@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;
    	}
    
    	/* (non-Javadoc)
    	 * @see org.springframework.web.servlet.HandlerInterceptor#postHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, org.springframework.web.servlet.ModelAndView)
    	 */
    	@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));
    
    	}
    
    	/* (non-Javadoc)
    	 * @see org.springframework.web.servlet.HandlerInterceptor#afterCompletion(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
    	 */
    	@Override
    	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
    			throws Exception {
    		System.out.println("afterCompletion");
    		Long start = (Long) request.getAttribute("startTime");
    		System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));
    		System.out.println("ex is "+ex);
    
    	}
    
    }
    

      注意两个地方:

    ①上面afterCompletion方法中的Exception 参数在控制器方法抛出UserNotExistException异常时是无法获取的,因为前面有一个@ControllerAdvice修饰的类里面@ExceptionHandler(UserNotExistException.class)修饰的方法会提前获取到这个异常;若是其它没有被提前处理的异常则可以获取。

    ②拦截器和过滤器不同的地方是,这里拦截器已经用了@component注解,但依然还是需要配置,否则无法生效:

    @Configuration
    public class WebConfig extends WebMvcConfigurerAdapter {
    	
    	@Autowired
    	private TimeInterceptor timeInterceptor;
    	
    	@Override
    	public void addInterceptors(InterceptorRegistry registry) {
    		registry.addInterceptor(timeInterceptor);
    	}
    
    
    }
    

      拦截器有一个局限,就是上面preHandle方法中的handler参数只能拿到控制器处理的类名和方法名,但无法拿到方法参数的值。原因见源码DispatcherServlet类的doService方法里面调用了一个doDispatch方法。若想拿到则要用第三个机制。

    (3)切片(Aspect)

    Spring AOP简介:

    切片(类):由“切入点”(注解)和“增强”(方法)组成。

    切入点:1、在哪些方法上起作用;2、在什么时候起作用

    增强:起作用时执行的业务逻辑。

    eg:

    @Aspect
    @Component
    public class TimeAspect {
    	
    	@Around("execution(* com.imooc.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();
    		
    		System.out.println("time aspect 耗时:"+ (new Date().getTime() - start));
    		
    		System.out.println("time aspect end");
    		
    		return object;
    	}
    
    }  

    总结1:过滤器、拦截器、切片的区别是:

    ①过滤器可以拿到原始的http请求和响应的信息,但是拿不到真正处理请求的那个方法的信息

    ②拦截器既可以拿到原始的http请求和响应的信息,也能拿到真正处理请求的那个方法的信息,但拿不到那个方法的参数的值

    ③切片能拿到那个方法的参数的值,但拿不到原始http请求响应对象。

    总结2:拦截顺序(从外到内)和抛出异常顺序(从内到外,并且多了一个ControllerAdvice)是:

    10、文件上传和下载

    现在很多应用前后端分离,前端都是SPA,所以不会刷新页面,也不会有表单提交,大部分情况上传文件都是异步完成,即提交表单只是提交一个文件路径,然后文件的上传都是另外单做的。

    public class FileInfo {
    	
    	public FileInfo(String path){
    		this.path = path;
    	}
    	
    	private String path;
    
    	public String getPath() {
    		return path;
    	}
    
    	public void setPath(String path) {
    		this.path = path;
    	}
    	
    }
    

      

    @RestController
    @RequestMapping("/file")
    public class FileController {
    
    	private String folder = "/Users/zhailiang/Documents/my/muke/inaction/java/workspace/github/imooc-security-demo/src/main/java/com/imooc/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());
    	}
    
    	@GetMapping("/{id}")
    	public void download(@PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    		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();
    		} 
    
    	}
    
    }
    

      

    11、异步处理REST服务

    异步处理的好处:主线程调用副线程后不需要等待,可以继续处理其它新的请求。

    本节有三块内容:

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

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

    (3)异步处理配置

    同步方法处理(需要1秒左右):

    @RestController
    public class AsyncController {
    	
    	private Logger logger = LoggerFactory.getLogger(getClass());
    	
    	@RequestMapping("/order")
    	public String order() throws Exception {
    		logger.info("主线程开始");
                    Thread.sleep(1000);
    		logger.info("主线程返回");
    		return "success";
    	}
    }
    

      

    异步处理(父线程即Tomcat线程立刻返回不需要等待),服务器吞吐量可以提升:

    @RestController
    public class AsyncController {
    	
    	private Logger logger = LoggerFactory.getLogger(getClass());
    	
    	@RequestMapping("/order")
    	public Callable<String> order() throws Exception {
    		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;
    	}
    }
    

      上面这种做法就属于(1)使用Runnable异步处理Rest服务,但这种方法有局限:副线程必须写在主线程内部。对于更复杂的企业级应用,需要使用DeferredResult异步处理Rest服务。

    以这个为例:

    在这个例子中,线程1负责发送,线程2负责监听,两者是隔离的,使用方法(1)无法实现,需要方法(2)。

    下面会有4段代码:

    用一个对象模拟上面的消息队列的代码、用一个Tomcat主线程接收请求的代码、监听处理结果并返回响应的线程2代码、用一个DeferredResultHolder将线程1处理完后得到的DeferredResult在线程2返回回去。

    模拟消息队列:

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

      DeferredResultHolder :

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

      上面的Map<String, DeferredResult<String>> map中的key是订单号,value是订单处理结果。

    接收请求的线程:

    @RestController
    public class AsyncController {
    	
    	@Autowired
    	private MockQueue mockQueue;
    	
    	@Autowired
    	private DeferredResultHolder deferredResultHolder;
    	
    	private Logger logger = LoggerFactory.getLogger(getClass());
    	
    	@RequestMapping("/order")
    	public DeferredResult<String> order() throws Exception {
    		logger.info("主线程开始");
    		
    		String orderNumber = RandomStringUtils.randomNumeric(8);
    		mockQueue.setPlaceOrder(orderNumber);
    		
    		DeferredResult<String> result = new DeferredResult<>();
    		deferredResultHolder.getMap().put(orderNumber, result);
    		
    		return 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");
    					mockQueue.setCompleteOrder(null);
    					
    				}else{
    					try {
    						Thread.sleep(100);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    
    			}
    		}).start();
    	}
    }
    

      上面这个方法里之所以单独启动一个线程运行,是因为这个监听器是监听容器启动的事件,若该方法一直循环则阻止容器正常启动。

    最后就是内容(3)异步处理配置。前面配置过一个WebConfig类来拦截处理同步的请求(filter或者Interceptor),但如果是要拦截上面的异步请求,则配置方法不同:

    @Configuration
    public class WebConfig extends WebMvcConfigurerAdapter {
    	
    	@Override
    	public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    		configurer.。。()
    	}
    
    }
    

      比如如果是要拦截内容(1)的那个Callable的请求,就需要这样注册异步处理的拦截器:

    configurer.registerCallableInterceptors(interceptors)
    

      

    12、与前端开发并行工作

    介绍两个工具:使用swagger自动生成html文档、使用WireMock(本身就是一个独立的服务器,可以接收前端的请求,然后模拟数据返回结果)快速伪造RESTful服务。

     了解三个swagger常用注解以及@EnableSwagger2即可。

    WireMock例子:注意要先去官网下载WireMock的可运行jar包然后跑起来(作为独立的服务器),接着引入相关pom,才能开始开发

    public class MockServer {
    
    	/**
    	 * @param args
    	 * @throws IOException
    	 */
    	public static void main(String[] args) throws IOException {
    		configureFor(8062);
    		removeAllMappings();
    
    		mock("/order/1", "01");
    		mock("/order/2", "02");
    	}
    
    	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(), "
    ");
    		stubFor(get(urlPathEqualTo(url)).willReturn(aResponse().withBody(content).withStatus(200)));
    	}
    
    }
    

     

    二、java8新增时间api:LocalDateTime类

  • 相关阅读:
    mysql主见自增,新增对象之后如何获取自增列
    mysql 常用语法
    加载maven中没有jar的命令
    设计的数据库中有关键字时,要 `` 符号不是单引号,和~一个键
    RequestMapping manager问题
    对页面所有TextBox进行操作
    正则简单检测字符有效性
    同时兼容IE和Firefox的事件(Event)
    各种浏览器的可见性
    页面加载Loading(.net)
  • 原文地址:https://www.cnblogs.com/z-y-x/p/10426974.html
Copyright © 2020-2023  润新知