一、拦截器
1.1 拦截器的作用
在对于数据库进行增删查改的时候,如果当前页面不检查用户是否登录,然后就能操作成功是不合理的,解决方法有两个:
- 对于已经写好的每个接口都加上一个判断,从Session中获取用户信息,看UserInfo是不是空的,如果是空的,后端就将返回值设为一个错误的状态,然后前端根据这个状态强制跳转到用户登录界面。
这个状态最好是一个类,里面装上:
-
- 状态码(code)
- 状态信息(message)
- 返回的数据(data)
-
可以使用拦截器,对于所有需要用户登录的界面进行拦截。代码如下:
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession();UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_INFO);if (userInfo == null || userInfo.getId() <= 0) {log.info("用户未登录!");response.setStatus(401);return false;}return true;}
}
/*** 拦截器的配置信息*/
//@Configuration
public class WebConfig implements WebMvcConfigurer {@AutowiredLoginInterceptor loginInterceptor;private List<String> excludePaths = Arrays.asList("/user/login","/**/*.js","/**/*.css","/**/*.png","/**/*.html");@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns(excludePaths);}
}
1.2 使用拦截器的注意事项
-
拦截器的代码很短,主要把握好两个部分:
-
- 配置拦截器(招保安)
- 定义拦截器(招到以后安排哪部分的安保工作)
添加前置、后置、视图渲染的拦截工作
- 拦截器要加@component注解注册为Bean,方便Spring扫描得到
1.3 @DispatcherServlet源码分析
1. 继承关系
-
DispatcherServlet
继承FrameworkServlet
-
FrameworkServlet
继承HttpServletBean
-
HttpServletBean
继承HttpServlet
-
HttpServlet
继承GenericServlet
-
enericServlet
实现Servlet
(是一个接口) -
Servlet
的声明周期有三部分: -
- init()
- service()
- destroy()
2. DispatcherServlet
是从日志中发现了DispatcherServlet
这个类。
3. initStrategies
protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}
- Multipart是和文件相关的,Resolver是解析器
- 本地初始化解析器
- 主体初始化解析器
- 处理器初始化映射
- 处理器初始化适配器
- 处理器初始化
4. doDispatch
// 主要代码
// 获得处理器
mappedHandler = getHandler(processedRequest);
// 获得处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 执行拦截器(预处理阶段),如果返回false就立即停止(被拦截)
if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;
}
// 使用处理器适配器对于处理器进行调度,返回ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 拦截器的后处理
mappedHandler.applyPostHandle(processedRequest, response, mv);// 对于所有资源进行整理
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
4.1. doDispatch-applyPreHandle
/*** Apply preHandle methods of registered interceptors.* @return {@code true} if the execution chain should proceed with the* next interceptor or the handler itself. Else, DispatcherServlet assumes* that this interceptor has already dealt with the response itself.*/boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for (int i = 0; i < this.interceptorList.size(); i++) {HandlerInterceptor interceptor = this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}return true;}
这是一个拦截器链,然后遍历每一个拦截器,对于各个拦截器进行拦截的前置处理。
4.2. doDispatch-applyPostHandle
/*** Apply postHandle methods of registered interceptors.*/void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {for (int i = this.interceptorList.size() - 1; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);interceptor.postHandle(request, response, this.handler, mv);}}
遍历拦截器链上的每一个拦截器,对于其进行后置拦截处理
二、统一数据返回格式
2.1 快速入门
对于每个接口,如果都是一样的返回格式,那么对于前后端交流是非常友好的。
实现代码:
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {return Result.success(body);}
}
-
supports方法是决定要不要使用统一返回格式:
-
- true——使用
- false——不使用
-
beforeBodyWrite方法:在写入响应正文前需要做的事情。
-
- 此处就是对于所有的返回正文进行了一次封装,Result就是一个常见的返回类,有code,msg,data等信息
2.2 存在问题
当返回String的时候,会报500的错误。
原因?源代码整起
先根据错误堆栈信息打个断点试试
子类:
追根溯源,
就是因为write方法会传进去一个Result类型的参数给addDefaultHeaders,
而addDefaultHeaders方法是被子类重写过的,
所以会调用子类重写后的方法,(父类中接收这个的参数是一个泛型,对应到这个情况中正好是Result类型)
重写的方法中定死了这个参数是一个String类型,所以会发生类型转换异常。
所以需要给String做一个单独的处理!
处理方法就是使用SpringBoot内置的Jackson对于信息进行序列化,防止源码中的这种错误。
代码实现:
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 对于String类型的响应正文进行序列化if (body instanceof String) {return objectMapper.writeValueAsString(body);}return Result.success(body);}
}
三、统一异常处理
统一异常也是一个面向切面编程思想的实现,其对于所有发生异常的接口进行捕获。
主要使用到的注解有三个:
- @ResponseBody
类注解,因为返回的仍然是数据,是响应正文。
- @ControllerAdvice
类注解,统一功能返回都是用这个注解。
- @ExceptionHandler
方法注解。
实现代码:
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {@ExceptionHandlerpublic Object handler(Exception e) {return Result.failed(e.getMessage());}@ExceptionHandlerpublic Object handler(ArithmeticException e) {return Result.failed(e.getMessage());}@ExceptionHandlerpublic Object handler(NullPointerException e) {return Result.failed(e.getMessage());}
}
3.1 @ControlelrAdvice
为什么能够对代码做到不侵入?源码整起
查看@ControlelrAdvice注解的源代码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {// ......
}
@ControlelrAdvice派生于@Compoent,所以可以不加五大注解也能够被Spring找到。
再看DispatcherServlet的源码,能够找到异常处理器的初始化,工作过程:
protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}
其中重点关注initHandlerAdapters(context)
、initHandlerExceptionResolvers(context)
这两个方法。
private void initHandlerAdapters(ApplicationContext context) {this.handlerAdapters = null;if (this.detectAllHandlerAdapters) {// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.Map<String, HandlerAdapter> matchingBeans =BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerAdapters = new ArrayList<>(matchingBeans.values());// We keep HandlerAdapters in sorted order.AnnotationAwareOrderComparator.sort(this.handlerAdapters);}}else {try {HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);this.handlerAdapters = Collections.singletonList(ha);}catch (NoSuchBeanDefinitionException ex) {// Ignore, we'll add a default HandlerAdapter later.}}// Ensure we have at least some HandlerAdapters, by registering// default HandlerAdapters if no other adapters are found.if (this.handlerAdapters == null) {this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());// We keep HandlerExceptionResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}else {try {HandlerExceptionResolver her =context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);this.handlerExceptionResolvers = Collections.singletonList(her);}catch (NoSuchBeanDefinitionException ex) {// Ignore, no HandlerExceptionResolver is fine too.}}// Ensure we have at least some HandlerExceptionResolvers, by registering// default HandlerExceptionResolvers if no other resolvers are found.if (this.handlerExceptionResolvers == null) {this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}}
可以看到这两个方法是极其相似的,都是从Bean工厂中提取Bean,如果没有提取到,然后又使用其他手段去获得Bean。
从Bean工厂获得Bean的类型各不相同,分别是:
- HandlerAdapter.class
- HandlerExceptionResolver.class
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();initMessageConverters();// ...}private void initControllerAdviceCache() {// 如果这个Bean是空,直接返回if (getApplicationContext() == null) {return;}// 获得所有被@ControllerAdvice标注的BeanList<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();//...}}
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beansinitExceptionHandlerAdviceCache();initMessageConverters();}// 如果这个Bean是空,直接返回private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}// 获得所有被@ControllerAdvice标注的BeanList<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}// 获得异常处理方法的处理器ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}// ...}
}
public class ExceptionHandlerMethodResolver {@Nullableprivate Method getMappedMethod(Class<? extends Throwable> exceptionType) {List<Class<? extends Throwable>> matches = new ArrayList<>();for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {if (mappedException.isAssignableFrom(exceptionType)) {matches.add(mappedException);}}if (!matches.isEmpty()) {if (matches.size() > 1) {// 如果匹配到多个异常会进行排序,取第一个matches.sort(new ExceptionDepthComparator(exceptionType));}return this.mappedMethods.get(matches.get(0));}else {return NO_MATCHING_EXCEPTION_HANDLER_METHOD;}}
}
他们两个都实现了InitializingBean接口,所以都有afterPropertiesSet这个方法,能够执行一些额外的初始化。
public interface InitializingBean {void afterPropertiesSet() throws Exception;
}
这个方法是在bean的依赖属性被注入之后(此时Spring Application Context已经有了一些Bean),执行一些额外的初始化