目录
[1. 定义拦截器](#1. 定义拦截器)
[2. 注册配置拦截器](#2. 注册配置拦截器)
[3. 拦截路径](#3. 拦截路径)
[4. DispatcherServlet 源码分析](#4. DispatcherServlet 源码分析)
[1. 初始化](#1. 初始化)
[2. 处理请求](#2. 处理请求)
[5. 适配器模式](#5. 适配器模式)
[1. 配置](#1. 配置)
[2. 源码分析](#2. 源码分析)
[1. 配置](#1. 配置)
[2. 源码分析](#2. 源码分析)
前言
本文详细介绍了Spring框架中的拦截器、统一数据返回格式和统一异常处理的实现原理。拦截器通过实现HandlerInterceptor接口,在请求处理前后执行特定逻辑;统一数据返回格式通过ResponseBodyAdvice接口实现响应数据的标准化处理;统一异常处理则利用@ControllerAdvice和@ExceptionHandler注解来捕获和处理异常。文章还分析了DispatcherServlet的源码实现,包括初始化流程和请求处理机制,并探讨了适配器模式在Spring中的应用场景。这些核心功能共同构成了Spring MVC框架的基础架构,为开发者提供了灵活、高效的Web开发支持。
一、拦截器
拦截器是 Spring 框架提供的核心功能之一,主要作用是拦截用户请求,在指定方法前后,根据业务需要执行预先设定的代码;
比如在用户访问时,判断用户是否登录,如果登录了就可以正常访问,没登录就拦截请求,返回登录页面,让用户进行登录;
1. 定义拦截器
定义拦截器,实现 HandlerInterceptor 接口,重写里面的三个方法;
preHandle() 用户访问时,目标方法执行前,执行的逻辑,可以是校验用户是否登录;返回 true 继续执行后续的操作,返回 false 拦截请,中断后续的操作;
postHandle() 用户访问时,目标方法执行后,执行的逻辑;
afterCompletion() 视图渲染完毕后执行的逻辑;
java
import com.example.demo.constant.Constant;
import com.example.demo.model.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 校验用户是否登录
HttpSession session = request.getSession();
UserInfo userInfo = (UserInfo) session.getAttribute(Constant.SESSION_KEY_USERINFO);
// 2. 校验失败,拦截
if(userInfo == null){
response.setStatus(401);
return false;
}
// 3. 校验成功,放行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
2. 注册配置拦截器
需要通类实例化一个拦截器,也可以将拦截器类交给 Spring 进行管理,再通过 IoC 的方式进行注入;
实现 WebMvcConfigure 接口,重写 addInterceptor() 方法;
注册拦截器后,根据业务需求,配置拦截器拦截的目录或者放行的目录;
java
import com.example.demo.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/css/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/pic/**")
.excludePathPatterns("/**/*.html")
.excludePathPatterns("/test/**");
}
}
3. 拦截路径
addPathPatterns() 指定要拦截哪些请求;
excludePathPatterns() 指定不拦截哪些请求;
拦截路径:
/* :一级路径;
/** :任意级路径;
/book/* :/book 下的一级路径;
/book/** book 下的任意路径;
三层架构的执行逻辑是 controller 调用 service,service 调用 mapper,有了拦截器之后,拦截器先预处理,再执行 controller 后面的逻辑;
4. DispatcherServlet 源码分析
Tomcat 项目启动后会看到 DispatcherServlet 相关的日志;
DispatcherServlet 是一个核心类,用于控制程序的执行顺序;

所有的请求都会先进入到 DispatcherServlet 执行 doDispach 调度方法,如果有拦截器,先执行拦截器的 preHandle() 方法,返回 true 后继续执行 controller 中的方法,执行完毕后,再回来执行 postHandle() 和 afterCompletion(),返回给 DispatcherServlet;
Servlet 的生命周期:
- init():初始化;
- service:执行服务;
- destroy():销毁;
1. 初始化
DispatcherServlet 的初始化方法 init() 在其父类 HttpServletBean 中实现的;
java
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
initServletBean() 是在 FrameworkServlet 类中实现的,主要作用是建立 WebApplicationContext 容器,并加载 SpringMVC 配置文件定义的 Bean 到该容器中,最后将该容器添加到 ServletContext 中:
java
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
初始化 web 容器的过程中,会通过 onRefresh() 来初始化 SpringMVC 的容器:
java
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
java
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
上述过程,方法都是在父类中定义的,但是具体实现都是在子类中完成的;这种在父类中定义,子类中实现的方式叫做模板方法模式;
2. 处理请求
DispatcherServlet 接收到请求后,执行 doDispatcher 调度方法,将请求传给 Controller;
java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new ServletException("Handler dispatch failed: " + err, err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new ServletException("Handler processing failed: " + err, err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
asyncManager.setMultipartRequestParsed(multipartRequestParsed);
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
cleanupMultipart(processedRequest);
}
}
}
}
HandlerAdapter 在 SpringMVC 中使用了适配器模式,可以看到执行 controller 中的方法前会执行拦截器的 preHandle(),执行后会执行 postHandle() 和 afterCompletion() 方法;
5. 适配器模式
适配器模式就是将某个接口转化为客户期望的接口,让原本不兼容的接口可以兼容;
以如下代码为例:
java
public class Executor {
public void process(){
System.out.println("我是 executor,正在执行服务...");
}
}
public class Adapter {
private Executor executor;
public void process(Executor executor){
this.executor = executor;
System.out.print("我是 adapter,正在执行:");
executor.process();
}
}
public class Custom {
public static void main(String[] args) {
Adapter adapter = new Adapter();
System.out.print("我是客户端,正在执行:");
adapter.process(new Executor());
}
}
一般在出现接口不兼容的情况下才会使用适配器模式,适配器模式一般出现在老项目中,对代码进行扩展改造,避免影响原有的逻辑;
二、统一数据返回格式
1. 配置
统一数据返回格式,需要添加一个返回结果的配置类,实现 ResponseBodyAdvice 接口,并加上 @ControllerAdvice 注解;
supports() 方法:判断是否要执行 beforeBodyWrite() 方法,true 为执行,false 为不执行;可以对指定的类或者方法对 response 进行处理;
beforeBodyWrite():指定对 response 进行处理;
java
import com.example.demo.model.ReturnResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class ResultConfig implements ResponseBodyAdvice {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 判断是否要执行下面的 beforeBodyWrite() 方法
// 需要统一结果返回,就要返回 true
// 不需要统一结果返回,就返回 false
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 返回前设置返回数据的格式,配合上面的 supports 方法进行统一结果返回
if(body instanceof ReturnResult<?>){
return body;
}
if(body instanceof String){
try {
return objectMapper.writeValueAsString(ReturnResult.success(body));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
return ReturnResult.success(body);
}
}
注意,如果要返回的数据格式是 String 类型的,不能直接返回,需要使用 ObjectMapper 对象将字符串转化为格式化数据,才能返回,否则会出现报错;
2. 源码分析
将原本的数据格式转换为统一数据返回格式这个步骤是在 service 的阶段完成的;
需要用到 HttpMessageConverter 实现数据格式的转换;
过程:
DispatcherServlet 的 doService() 方法调用 doDispatch() 方法;
java
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
...
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
doDispatch() 调用 handle() 方法调度服务,handle() 方法有多个子类实现,用的是 AbstractHandlerMethodAdapter 类中的 handle() 方法;
java
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
...
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
...
}
Spring 中既能支持 controller 方式实现的路由映射,也能支持 servlet 方式实现的映射,是因为使用了适配器模式, 能适配不同的接口;因此提供服务也是通过适配器实现了,所以会在源码中经常看到 Adapter;
RequestMappingHandlerAdapter 类实现了 AbstractHandlerMethodAdapter 的 handleInternal() 方法,再调用 invokeHandlerMethod() 方法;
java
@Override
@Nullable
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
调用 ServletInvocableHandlerMethod 类中的 invokeAndHandle() 方法,调用 handleReturnValue() 方法;
java
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
handleReturnValue() 实现了返回数据的格式转换,有多个类实现,使用的是 RequestResponseBodyMethodProcessor 类中的实现;
java
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
if (returnValue instanceof ProblemDetail detail) {
outputMessage.setStatusCode(HttpStatusCode.valueOf(detail.getStatus()));
if (detail.getInstance() == null) {
URI path = URI.create(inputMessage.getServletRequest().getRequestURI());
detail.setInstance(path);
}
invokeErrorResponseInterceptors(detail, null);
}
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
调用 AbstarctMessageConverter 类中的 writeWithMessageConverters() 方法,使用转换器实现;
java
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
...
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
ResolvableType targetResolvableType = null;
for (HttpMessageConverter converter : this.messageConverters) {
ConverterType converterTypeToUse = null;
if (converter instanceof GenericHttpMessageConverter genericConverter) {
if (genericConverter.canWrite(targetType, valueType, selectedMediaType)) {
converterTypeToUse = ConverterType.GENERIC;
}
}
else if (converter instanceof SmartHttpMessageConverter smartConverter) {
targetResolvableType = getNestedTypeIfNeeded(ResolvableType.forMethodParameter(returnType));
if (smartConverter.canWrite(targetResolvableType, valueType, selectedMediaType)) {
converterTypeToUse = ConverterType.SMART;
}
}
else if (converter.canWrite(valueType, selectedMediaType)){
converterTypeToUse = ConverterType.BASE;
}
if (converterTypeToUse != null) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
switch (converterTypeToUse) {
case BASE -> converter.write(body, selectedMediaType, outputMessage);
case GENERIC -> ((GenericHttpMessageConverter) converter).write(body, targetType, selectedMediaType, outputMessage);
case SMART -> ((SmartHttpMessageConverter) converter).write(body, targetResolvableType, selectedMediaType, outputMessage, null);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
...
}
上述代码中的 getAdvice() .beforeBodyWrite() 就是调用统一数据返回格式的配置;
三、统一异常处理
1. 配置
统一异常处理使用的 @ControllerAdvice 和 @ExceptionHandler 注解实现的;
如果返回的不是页面,而是数据,要加上 @ResponseBody 注解;
java
import com.example.demo.model.ReturnResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@Slf4j
@ResponseBody
@ControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler
public ReturnResult<Object> handler1(Exception e){
log.error("发生错误:e", e);
return ReturnResult.error("内部错误");
}
@ExceptionHandler
public ReturnResult<Object> handler2(ArithmeticException e){
log.error("发生错误:e", e);
return ReturnResult.error("算术错误");
}
@ExceptionHandler
public ReturnResult<Object> handler3(NullPointerException e){
log.error("发生错误:e", e);
return ReturnResult.error("空指针异常");
}
}
注意加上 @Slf4j 打印日志,否则出现异常的时候,没有错误信息;
2. 源码分析
@ControllerAdvice:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
/**
* Alias for {@link Component#value}.
* @since 6.1
*/
@AliasFor(annotation = Component.class, attribute = "value")
String name() default "";
...
}
@ControllerAdvice 是基于 @Component 实现的,也会被 Spring 管理;
对于 @ControllerAdvice 注解,Servlet 在初始化阶段会初始化适配器和异常解析器;
initHandlerAdapters():
初始化阶段就会取得所有的 HandlerAdapter 接口的 bean 并保存起来,其中有一个类型为 RequestMappingHandlerAdapter 的 bean;这个 bean 是 @RequestMapping 注解能起作用的关键,这个 bean 在启动过程中会获取所有被 @ControllerAdvice 注解标注的 bean 对象;
RequestMappingHandlerAdapter 类实现了 InitializingBean 接口,重写了 afterPropertiesSet() 方法,用于获取 被 @ControllerAdvice 注解标注的 bean 对象;
java
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
...
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
initMessageConverters();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
if (BEAN_VALIDATION_PRESENT) {
List<HandlerMethodArgumentResolver> resolvers = this.argumentResolvers.getResolvers();
this.methodValidator = HandlerMethodValidator.from(
this.webBindingInitializer, this.parameterNameDiscoverer,
methodParamPredicate(resolvers, ModelAttributeMethodProcessor.class),
methodParamPredicate(resolvers, RequestParamMethodArgumentResolver.class));
}
}
}
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
if (logger.isDebugEnabled()) {
int modelSize = this.modelAttributeAdviceCache.size();
int binderSize = this.initBinderAdviceCache.size();
int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
" @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
}
}
}
initHandlerExceptionResolvers():
初始化阶段就会取得所有的 HandlerExceptionResolver 接口的 bean 并保存起来,其中有一个类型为 ExceptionHandlerExceptionResolver 的 bean;这个 bean 在启动过程中会获取所有被 @ControllerAdvice 注解标注的 bean 对象;
ExceptionHandlerExceptionResolver 类实现了 InitializingBean 接口,重写了 afterPropertiesSet() 方法,用于获取被 @ControllerAdvice 注解标注的 bean 对象;
java
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {
...
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<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);
}
}
if (logger.isDebugEnabled()) {
int handlerSize = this.exceptionHandlerAdviceCache.size();
int adviceSize = this.responseBodyAdvice.size();
if (handlerSize == 0 && adviceSize == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " +
handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
}
}
}
...
}
当 Controller 抛出异常,DispatcherServlet 通过 ExceptionHandlerExceptionResolver 解析异常, ExceptionHandlerExceptionResolver 通过 ExceptionHandlerMethodResolver 解析异常,最终找到适用的 @ExceptionHandler 标注的方法;
java
public class ExceptionHandlerMethodResolver {
...
@Nullable
private ExceptionHandlerMappingInfo getMappedMethod(Class<? extends Throwable> exceptionType, MediaType mediaType) {
List<ExceptionMapping> matches = new ArrayList<>();
for (ExceptionMapping mappingInfo : this.mappedMethods.keySet()) {
if (mappingInfo.exceptionType().isAssignableFrom(exceptionType) && mappingInfo.mediaType().isCompatibleWith(mediaType)) {
matches.add(mappingInfo);
}
}
if (!matches.isEmpty()) {
if (matches.size() > 1) {
matches.sort(new ExceptionMapingComparator(exceptionType, mediaType));
}
return this.mappedMethods.get(matches.get(0));
}
else {
return NO_MATCHING_EXCEPTION_HANDLER;
}
}
...
}