SpringMVC进阶-01
1.拦截器
public class LoginInterceptor implements HandlerInterceptor {
/**
* 拦截器 拦截没有登录的用户
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (request.getSession().getAttribute("user") == null) {
response.sendRedirect("/admin");
return false;
}
return true;
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin")
.excludePathPatterns("/admin/login");
}
}
2.异常处理
@Slf4j
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView handlerException(HttpServletRequest request, Exception e) throws Exception {
log.error("request url {}, error info {}", request.getRequestURL(), e);
// 如果捕获到的异常上有ResponseStatus注解,说明是页面找不到的异常,这里直接抛出,
// 交给springboot去处理,来返回error/404.html。
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
throw e;
}
ModelAndView mv = new ModelAndView();
mv.addObject("url", request.getRequestURL());
mv.addObject("exception", e);
mv.setViewName("error/error.html");
return mv;
}
}
3.方法访问日志记录
@Slf4j
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.my.blog.controller.*.*(..))")
public void log() {}
@Before(value = "log()")
public void doBefore(JoinPoint joinPoint) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
String ip = request.getRemoteAddr();
// 方法名 = 类全名 + 方法名
String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
RequestInfo requestInfo = new RequestInfo(url, ip, methodName, args);
}
@After(value = "log()")
public void doAfter() {
}
@AfterReturning(value = "log()", returning = "obj")
public void afterReturning(Object obj) {
log.info("response result {}", obj);
}
@ToString
@AllArgsConstructor
static class RequestInfo {
private final String url;
private final String ip;
private final String methodName;
/**
* 方法参数
*/
private final Object[] args;
}
}
4.Controller参数接受
@Slf4j
@RestController
public class UserController {
@Resource
private UserService userService;
/**
* localhost:8080/user06
* [{"id":1,"name":"tom"}, {"id":2, "name":"bob"}]
* json数组的封装
* @param users
* @return
*/
@GetMapping(value = "/user06")
public List<User> getUser06(@RequestBody List<User> users) {
// user [User(id=1, name=tom, age=null), User(id=2, name=bob, age=null)]
log.info("user {}", users);
return users;
}
/**
* localhost:8080/user04
* {"id":1}
*
* 会将{"id":1}看做是一个整体,然后转换为User对象
* @param user
* @return
*/
@GetMapping(value = "/user05")
public User getUser05(@RequestBody User user) {
// user User(id=1, name=null, age=null)
log.info("user {}", user);
return user;
}
/**
* 接受json数据
* localhost:8080/user04
* {"id":"1"}
* 会将{"id":"1"}看做是一个整体赋给id
* @param id
* @return
*/
@GetMapping(value = "/user04")
public User getUser04(@RequestBody String id) {
// id {"id":"1"}
log.info("id {}", id);
return userService.getUser(Integer.parseInt(id));
}
/**
* localhost:8080/user03
* body form-data参数 id=1,name=tom
* @param user
* @return
*/
@GetMapping("/user03")
public User getUser03(User user) {
log.info("user {}", user);
return user;
}
/**
* localhost:8080/user02
* body form-data参数 id=1,2,3
* 同样需要使用@RequestParam注解,否则报错
* @param ids
* @return
*/
@GetMapping("/user02")
public List<User> getUser02(@RequestParam List<Integer> ids) {
log.info("list is {}", ids);
List<User> users = new ArrayList<>();
ids.forEach(v -> users.add(userService.getUser(v)));
return users;
}
/**
* localhost:8080/user01
* body form-data参数 id=1
* @param id
* @return
*/
@GetMapping("/user01")
public User getUser01(Integer id) {
log.info("user id {}", id);
return userService.getUser(id);
}
/**
* localhost:8080/get/user?id=1&name=tom&age=100
* @param user
* @return
*/
@GetMapping("/get/user")
public User getUser(User user) {
log.info("user {}", user);
return user;
}
/**
* localhost:8080/users?ids=1,2,3
* 可以接受输出参数,但是必须要使用 @RequestParam注解,
* 否则报错 No primary or default constructor found for interface java.util.List]
* @param ids
* @return
*/
@GetMapping("/users")
public List<User> getUsers(@RequestParam(name = "ids") List<Integer> ids) {
log.info("list is {}", ids);
List<User> users = new ArrayList<>();
ids.forEach(v -> users.add(userService.getUser(v)));
return users;
}
/**
* localhost:8080/user?id=1&username=tom.
* id必须传入,否则页面出现404,服务器端出现警告。
* Required request parameter 'id' for method parameter type Integer is not present
* @param id
* @param name
* @return
*/
@GetMapping("/user")
public User getUser(@RequestParam(name = "id", required = true) Integer id,
@RequestParam(name = "username", required = false) String name) {
log.info("user id {}, name {}", id, name);
return userService.getUser(id);
}
/**
* localhost:8080/user/1
* @param id
* @return
*/
@GetMapping("/user/{id}")
public User getUser(@PathVariable("id") Integer id) {
log.info("user id {}", id);
return userService.getUser(id);
}
}
5.耗时请求异步处理
- SpringMVC实现耗时请求异步处理,同时客户端和服务端保持连接不断开。
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor01())
.addPathPatterns("/test/01")
.order(-100);
}
}
@Slf4j
public class TestInterceptor01 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("TestInterceptor01 preHandle {}", Thread.currentThread().getName());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("TestInterceptor01 postHandle {}", Thread.currentThread().getName());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("TestInterceptor01 afterCompletion {}", Thread.currentThread().getName());
}
}
@Slf4j
@RestController
public class TestController {
@GetMapping("/test/01")
public Callable<String> test01() {
log.info("test 01 {}", Thread.currentThread().getName());
return () -> {
log.info("异步处理任务开始 {}", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(5);
log.info("异步处理任务结束 {}", Thread.currentThread().getName());
return "异步处理任务完成";
};
}
}
- 异步任务的执行流程。1)现有SpringMVC线程执行Interceptor.preHandler()和Controller中的方法。2)然由开启的线程异步执行耗时操作。3)耗时操作执行完成,在由SpringMVC线程重新执行Interceptor.preHandler() -> postHandler() -> afterCompletion()。
TestInterceptor01 preHandle http-nio-8080-exec-9
test 01 http-nio-8080-exec-9
异步处理任务开始 task-3
步处理任务结束 task-3
TestInterceptor01 preHandle http-nio-8080-exec-7
TestInterceptor01 postHandle http-nio-8080-exec-7
TestInterceptor01 afterCompletion http-nio-8080-exec-7
- 耗时操作有线程池去执行。
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor01())
.addPathPatterns("/test/01")
.order(-100);
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
// 核心线程数
threadPoolTaskExecutor.setCorePoolSize(5);
// 允许核心线程数执行超时
threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
// 最大线程数
threadPoolTaskExecutor.setMaxPoolSize(5);
// 配置队列大小
threadPoolTaskExecutor.setQueueCapacity(50);
// 配置线程池前缀
threadPoolTaskExecutor.setThreadNamePrefix("async-");
threadPoolTaskExecutor.initialize();
configurer.setTaskExecutor(threadPoolTaskExecutor);
}
}
6.AsyncHandlerInterceptor
- AsyncHandlerInterceptor是对HandlerInterceptor接口在异步场景下的一个补充。在异步场景中HandlerInterceptor接口中的postHandle()、afterCompletion()方法只会在异步线程执行完成才去执行。
- AsyncHandlerInterceptor提供afterConcurrentHandlingStarted()方法,在异步线程执行之前调用,可以用来释放开启异步线程的线程的资源,如线程的MDC、当前线程缓存的用户数据。
- AsyncHandlerInterceptor代码实例。
@Configuration
public class SpringMVCConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor02())
.addPathPatterns("/test/02")
.order(1);
}
}
@Slf4j
public class TestInterceptor02 implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("TestInterceptor02 preHandle {}", Thread.currentThread().getName());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("TestInterceptor02 postHandle {}", Thread.currentThread().getName());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("TestInterceptor02 afterCompletion {}", Thread.currentThread().getName());
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("TestInterceptor02 afterConcurrentHandlingStarted {}", Thread.currentThread().getName());
}
}
@Slf4j
@RestController
public class TestController {
@GetMapping("/test/02")
public Callable<String> test02() {
log.info("test 02 {}", Thread.currentThread().getName());
return () -> {
log.info("异步处理任务开始 {}", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(5);
log.info("异步处理任务结束 {}", Thread.currentThread().getName());
return "异步处理任务完成";
};
}
}
- AsyncHandlerInterceptor执行流程。
TestInterceptor02 preHandle http-nio-8080-exec-9
test 02 http-nio-8080-exec-9
# 可以在afterConcurrentHandlingStarted()中
# 释放http-nio-8080-exec-9线程ThreadLocal中缓存的数据。
TestInterceptor02 afterConcurrentHandlingStarted http-nio-8080-exec-9
异步处理任务开始 async-service-1
异步处理任务结束 async-service-1
TestInterceptor02 preHandle http-nio-8080-exec-10
TestInterceptor02 postHandle http-nio-8080-exec-10
TestInterceptor02 afterCompletion http-nio-8080-exec-10