avatar

springMVC核心三

关于视图,我认为mvc框架中的提供完整视图从处理机制,和使用Rest开发基本是冲突,关于View
部分,如模板处理,不做过多分析.

视图解析器

简单来说分为三类,ViewResolverComposite为典型的Composite,一般为DispatcherServlet持有

  • 接口定义
    ViewResolver
    1
    View resolveViewName(String viewName, Locale locale) throws Exception;

    BeanNameViewResolver

    Context中获取View
  • 代码逻辑
    BeanNameViewResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public View resolveViewName(String viewName, Locale locale) throws BeansException {
    ApplicationContext context = obtainApplicationContext();
    if (!context.containsBean(viewName)) {
    // Allow for ViewResolver chaining...
    return null;
    }
    if (!context.isTypeMatch(viewName, View.class)) {
    if (logger.isDebugEnabled()) {
    logger.debug("Found bean named '" + viewName + "' but it does not implement View");
    }
    // Since we're looking into the general ApplicationContext here,
    // let's accept this as a non-match and allow for chaining as well...
    return null;
    }
    return context.getBean(viewName, View.class);
    }

    ContentNegotiating

    使用了ContentNegotiationManager,该类用来通过Request判断MiedaType
  • 代码逻辑
    ContentNegotiatingViewResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
      public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
    implements ViewResolver, Ordered, InitializingBean {

    private ContentNegotiationManager contentNegotiationManager;
    //FactoryBean
    private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean();
    //内部含有的Resolver
    private List<ViewResolver> viewResolvers;
    //WebApplicationObjectSupport提供的,调用点实际上是ApplicationContextAwareProcessor#postProcessBeforeInitialization
    //即populate之后调用
    protected void initServletContext(ServletContext servletContext) {
    Collection<ViewResolver> matchingBeans =
    BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
    if (this.viewResolvers == null) { //若本ViewResolver并没有定义viewResovler,则将beanFacory其他解析器添加
    this.viewResolvers = new ArrayList<>(matchingBeans.size());
    for (ViewResolver viewResolver : matchingBeans) {
    if (this != viewResolver) {
    this.viewResolvers.add(viewResolver);
    }
    }
    }
    else {
    //若执行到这里viewResolvers.!=null
    //说明有某些处理器,如InstantiationAwareBeanPostProcessor可能添加ViewResolver到这里
    //并且不是通过pvs添加,这样直接添加的是不属于beanFactory管理的
    for (int i = 0; i < this.viewResolvers.size(); i++) {
    ViewResolver vr = this.viewResolvers.get(i);
    if (matchingBeans.contains(vr)) { //明显就判断了一下,已经含有的不能是beanFactory中的
    continue;
    }
    String name = vr.getClass().getName() + i;
    //将并非是ioc容器创造出来的解析器调用init
    obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
    }

    }
    AnnotationAwareOrderComparator.sort(this.viewResolvers);
    this.cnmFactoryBean.setServletContext(servletContext);
    }
    //创造内部的ContextNegotiationMannager
    public void afterPropertiesSet() {
    if (this.contentNegotiationManager == null) {
    this.contentNegotiationManager = this.cnmFactoryBean.build();
    }
    if (this.viewResolvers == null || this.viewResolvers.isEmpty()) {
    logger.warn("No ViewResolvers configured");
    }
    }

    //视图解析
    public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
    if (requestedMediaTypes != null) {
    //遍历内部所有解析器,获取候选view
    List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
    //选择View#getContextTpe和判断出来的MiedaType符合的视图
    View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
    if (bestView != null) {
    return bestView;
    }
    }

    String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
    " given " + requestedMediaTypes.toString() : "";

    if (this.useNotAcceptableStatusCode) {
    if (logger.isDebugEnabled()) {
    logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
    }
    return NOT_ACCEPTABLE_VIEW;
    }
    else {
    logger.debug("View remains unresolved" + mediaTypeInfo);
    return null;
    }
    }
    }
  • 总结
    • 改类本质也是委托模式,包含List<ViewResolver>来处理
    • 目的是为了选出View#getContexTypeMiedaType符合的视图
    • 该类可以通过mvc:content-negotiation进行配置,参考ViewResolversBeanDefinitionParser实现

      AbstractCachingViewResolver

      简而言之,此类是带有缓存的视图解析器,总体逻辑简单,由子类实现loadView()来创建视图
  • 代码逻辑
    AbstractCachingViewResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
        public static final int DEFAULT_CACHE_LIMIT = 1024;

    //Fast access cache for Views, returning already cached instances without a global lock.
    //容量为1024的并发map
    //key实际上是 viewname+Local.toString(当前请求的Local字符串化,如zh,en)
    private final Map<Object, View> viewAccessCache = new ConcurrentHashMap<>(DEFAULT_CACHE_LIMIT);
    //Map from view key to View instance, synchronized for View creation.
    //创建缓存时加锁,这里有趣的是重写removeEldestEntry
    //简单来说在{@link LinkedHashMap}中说明该函数是当插入新元素时,可以选择将eldest元素是否删除,
    //典型的就是当使用map作为含有limit的时候,如这里1024,超过该值就选择删除最早的元素
    private final Map<Object, View> viewCreationCache =
    new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
    if (size() > getCacheLimit()) {
    viewAccessCache.remove(eldest.getKey());//主动删除viewAccessCache缓存
    //返回true后this的eldest会被LinkHashMap删除
    return true;
    }
    else {
    return false;
    }
    }
    };
    //构建视图逻辑
    public View resolveViewName(String viewName, Locale locale) throws Exception {
    if (!isCache()) {
    return createView(viewName, locale);
    }
    else {
    Object cacheKey = getCacheKey(viewName, locale);
    View view = this.viewAccessCache.get(cacheKey);
    if (view == null) {
    synchronized (this.viewCreationCache) {
    //简单来看,当创建的时候将降维单线程
    //1.可以防止当CacheKey相同时,发生不必要的重复创建工作,但是这点可以通过sych(cacheKey.intern())实现
    //2.我认为是子类实现的createView未必是线程安全的,因此这么实现锁
    view = this.viewCreationCache.get(cacheKey);
    if (view == null) {
    // Ask the subclass to create the View object.
    view = createView(viewName, locale);
    if (view == null && this.cacheUnresolved) {
    view = UNRESOLVED_VIEW;
    }
    if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
    this.viewAccessCache.put(cacheKey, view);
    this.viewCreationCache.put(cacheKey, view);
    }
    }
    }
    }
    else {
    if (logger.isTraceEnabled()) {
    logger.trace(formatKey(cacheKey) + "served from cache");
    }
    }
    return (view != UNRESOLVED_VIEW ? view : null);
    }
    }

    protected View createView(String viewName, Locale locale) throws Exception {
    return loadView(viewName, locale);
    }
    protected abstract View loadView(String viewName, Locale locale) throws Exception;
  • 总结
    • 附带缓存,并且实现了线程安全

      ResourceBundleViewResolver

      该类实现上使用了ResourceBundle来对于不同Local根据不同的in18配置文件,返回不同的View
  • 代码逻辑
    ResourceBundleViewResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    //properties默认名字
    public static final String DEFAULT_BASENAME = "views";

    protected View loadView(String viewName, Locale locale) throws Exception {
    BeanFactory factory = initFactory(locale);//创造不同的bf
    try {
    return factory.getBean(viewName, View.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
    // Allow for ViewResolver chaining...
    return null;
    }
    }

    protected synchronized BeanFactory initFactory(Locale locale) throws BeansException {
    // Try to find cached factory for Locale:
    // Have we already encountered that Locale before?
    if (isCache()) {
    BeanFactory cachedFactory = this.localeCache.get(locale);
    if (cachedFactory != null) {
    return cachedFactory;
    }
    }

    // Build list of ResourceBundle references for Locale.
    List<ResourceBundle> bundles = new LinkedList<>();
    for (String basename : this.basenames) {
    ResourceBundle bundle = getBundle(basename, locale);
    bundles.add(bundle);
    }

    // Try to find cached factory for ResourceBundle list:
    // even if Locale was different, same bundles might have been found.
    if (isCache()) {
    BeanFactory cachedFactory = this.bundleCache.get(bundles);
    if (cachedFactory != null) {
    this.localeCache.put(locale, cachedFactory);
    return cachedFactory;
    }
    }

    // Create child ApplicationContext for views.
    //通过上边获取到的ResourceBundle去读取不同的properties文件,并加载进行GAC
    GenericWebApplicationContext factory = new GenericWebApplicationContext();
    factory.setParent(getApplicationContext());
    factory.setServletContext(getServletContext());

    // Load bean definitions from resource bundle.
    //properties配置读取器
    PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(factory);
    reader.setDefaultParentBean(this.defaultParentView);
    for (ResourceBundle bundle : bundles) {
    reader.registerBeanDefinitions(bundle);
    }

    factory.refresh();

    // Cache factory for both Locale and ResourceBundle list.
    if (isCache()) {
    this.localeCache.put(locale, factory);
    this.bundleCache.put(bundles, factory);
    }

    return factory;
    }
  • 总结
    • 实现简单,具体如何配置参考后文
    • properties必须是views.properties这样的命名
    • 例子
      1
      2
      testViewName.(class)=org.springframework.web.servlet.view.InternalResourceView
      testViewName.url=/WEB-INF/index.jsp
    • 关于properties作为配置文件,参考PropertiesBeanDefinitionReader

      XmlViewResolver

      ResourceBundleViewResolver实现一致,不过是从xml中读取
  • 代码逻辑
    XmlViewResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
       
    public static final String DEFAULT_LOCATION = "/WEB-INF/views.xml";

    protected View loadView(String viewName, Locale locale) throws BeansException {
    BeanFactory factory = initFactory();
    try {
    return factory.getBean(viewName, View.class);
    }
    catch (NoSuchBeanDefinitionException ex) {
    // Allow for ViewResolver chaining...
    return null;
    }
    }

    protected synchronized BeanFactory initFactory() throws BeansException {
    if (this.cachedFactory != null) {
    return this.cachedFactory;
    }

    ApplicationContext applicationContext = obtainApplicationContext();

    Resource actualLocation = this.location;
    if (actualLocation == null) {
    actualLocation = applicationContext.getResource(DEFAULT_LOCATION);
    }

    // Create child ApplicationContext for views.
    //读取xml并创建gac
    GenericWebApplicationContext factory = new GenericWebApplicationContext();
    factory.setParent(applicationContext);
    factory.setServletContext(getServletContext());

    // Load XML resource with context-aware entity resolver.
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    reader.setEnvironment(applicationContext.getEnvironment());
    reader.setEntityResolver(new ResourceEntityResolver(applicationContext));
    reader.loadBeanDefinitions(actualLocation);

    factory.refresh();

    if (isCache()) {
    this.cachedFactory = factory;
    }
    return factory;
    }
    - 总结
    - 上述两种视图解析器的使用,用例子说明了gbc中的bean定义来源是可以多样化的

    UrlBasedViewResolver

  • 代码逻辑
    UrlBasedViewResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    public static final String REDIRECT_URL_PREFIX = "redirect:";
    public static final String FORWARD_URL_PREFIX = "forward:";
    //返回的视图对象
    private Class<?> viewClass;
    private String prefix = "";
    private String suffix = "";

    //重写父类,为了处理重定向和转发视图
    protected View createView(String viewName, Locale locale) throws Exception {
    // If this resolver is not supposed to handle the given view,
    // return null to pass on to the next resolver in the chain.
    if (!canHandle(viewName, locale)) {
    return null;
    }
    //处理RedirectView
    // Check for special "redirect:" prefix.
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
    String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
    RedirectView view = new RedirectView(redirectUrl,
    isRedirectContextRelative(), isRedirectHttp10Compatible());
    String[] hosts = getRedirectHosts();
    if (hosts != null) {
    view.setHosts(hosts);
    }
    return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
    }
    //创建Internal视图
    // Check for special "forward:" prefix.
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
    String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
    InternalResourceView view = new InternalResourceView(forwardUrl);
    return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
    }

    // Else fall back to superclass implementation: calling loadView.
    return super.createView(viewName, locale);
    }

    //由不同的子类决定不同的视图
    protected Class<?> requiredViewClass() {
    return AbstractUrlBasedView.class;
    }
    protected View loadView(String viewName, Locale locale) throws Exception {
    AbstractUrlBasedView view = buildView(viewName);
    View result = applyLifecycleMethods(viewName, view);
    return (view.checkResource(locale) ? result : null);
    }

    //初始化
    protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
    ApplicationContext context = getApplicationContext();
    if (context != null) {
    Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view, viewName);
    if (initialized instanceof View) {
    return (View) initialized;
    }
    }
    return view;
    }
    //构建视图对象,并没有通过bf构建,而是反射
    protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    Class<?> viewClass = getViewClass();
    Assert.state(viewClass != null, "No view class");

    //子类决定类型,为何不使用泛型来实现
    AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
    view.setUrl(getPrefix() + viewName + getSuffix());
    view.setAttributesMap(getAttributesMap());

    String contentType = getContentType();
    if (contentType != null) {
    view.setContentType(contentType);
    }

    String requestContextAttribute = getRequestContextAttribute();
    if (requestContextAttribute != null) {
    view.setRequestContextAttribute(requestContextAttribute);
    }

    Boolean exposePathVariables = getExposePathVariables();
    if (exposePathVariables != null) {
    view.setExposePathVariables(exposePathVariables);
    }
    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    if (exposeContextBeansAsAttributes != null) {
    view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    }
    String[] exposedContextBeanNames = getExposedContextBeanNames();
    if (exposedContextBeanNames != null) {
    view.setExposedContextBeanNames(exposedContextBeanNames);
    }

    return view;
    }
  • 总体
    • 支持forward:redirect:
    • 带有prefixsuffix
    • 由子类决定viewClass
    • 重写createView来处理rediect视图,以及forward视图
      子类
  • InternalResourceViewResolver:用来构建InternalResourceView
    InternalResourceViewResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public InternalResourceViewResolver() {
    Class<?> viewClass = requiredViewClass();
    if (InternalResourceView.class == viewClass && jstlPresent) {
    viewClass = JstlView.class;
    }
    setViewClass(viewClass);
    }

    protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    InternalResourceView view = (InternalResourceView) super.buildView(viewName);
    if (this.alwaysInclude != null) {
    view.setAlwaysInclude(this.alwaysInclude);
    }
    view.setPreventDispatchLoop(true);
    return view;
    }
  • TilesViewResolver:构建TilesView
  • ScriptTemplateViewResolver:构建ScriptTemplateView
  • XsltViewResolver:构建XsltView

    视图

    接口定义
    View
    1
    2
    3
    4
    5
    6
    7
    8
    public interface View {
    default String getContentType() {
    return null;
    }

    void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
    throws Exception;
    }

    AbstactView

  • 代码逻辑
    AbstactView
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {


    public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
    HttpServletResponse response) throws Exception {

    if (logger.isDebugEnabled()) {
    logger.debug("View " + formatViewName() +
    ", model " + (model != null ? model : Collections.emptyMap()) +
    (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
    }

    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);//处理响应头
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
    }
    //合并属性
    protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
    HttpServletRequest request, HttpServletResponse response) {

    //View.PATH_VARIABLES map
    @SuppressWarnings("unchecked")
    Map<String, Object> pathVars = (this.exposePathVariables ?
    (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

    // Consolidate static and dynamic model attributes.
    //staticAttributes map
    int size = this.staticAttributes.size();
    size += (model != null ? model.size() : 0);
    size += (pathVars != null ? pathVars.size() : 0);

    Map<String, Object> mergedModel = new LinkedHashMap<>(size);
    mergedModel.putAll(this.staticAttributes);
    if (pathVars != null) {
    mergedModel.putAll(pathVars);
    }
    //model map
    if (model != null) {
    mergedModel.putAll(model);
    }

    // Expose RequestContext?
    if (this.requestContextAttribute != null) {
    mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
    }

    return mergedModel;
    }

    protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
    if (generatesDownloadContent()) {
    response.setHeader("Pragma", "private");
    response.setHeader("Cache-Control", "private, must-revalidate");
    }
    }

    //子类实现
    protected abstract void renderMergedOutputModel(
    Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
    }

    AbstractJackson2View

    代码简单,通过jackson api进行application/jsonapplication/xml转换处理
  • MappingJackson2JsonView
    处理上文中合并的modelMap,将value转换成json
  • MappingJackson2XmlView
    处理上文中合并的modelMap,将value转换成xml

    AbstractUrlBasedView

    该部分子类和UrlBasedViewResolver子类相对应
    InternalResourceView
  • 代码逻辑
    InternalResourceView
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class InternalResourceView extends AbstractUrlBasedView {
    protected void renderMergedOutputModel(
    Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    // Expose the model object as request attributes.
    //model属性添加到res中
    exposeModelAsRequestAttributes(model, request);

    // Expose helpers as request attributes, if any.
    exposeHelpers(request);

    // Determine the path for the request dispatcher
    // url = prefix +viewName+ suffix
    String dispatcherPath = prepareForRendering(request, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
    throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
    "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(request, response)) {
    response.setContentType(getContentType());
    if (logger.isDebugEnabled()) {
    logger.debug("Including [" + getUrl() + "]");
    }
    rd.include(request, response);
    }

    else {
    // Note: The forwarded resource is supposed to determine the content type itself.
    if (logger.isDebugEnabled()) {
    logger.debug("Forwarding to [" + getUrl() + "]");
    }
    rd.forward(request, response);
    }
    }
    }
  • 总结
    • prefix+viewname+’suffix’访问jsp或servlet,以及其他页面
    • 通过RequestDispatcher进行includeforward处理
      RedirectView
  • 代码逻辑
    RedirectView
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    public class RedirectView extends AbstractUrlBasedView implements SmartView {
    //匹配重定义url中是否有占位符url
    private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
    //
    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
    HttpServletResponse response) throws IOException {

    String targetUrl = createTargetUrl(model,request);
    //调用一个 RequestDataValueProcessor当作触发器,默认 没有实现
    targetUrl = updateTargetUrl(targetUrl, model, request, response);

    // Save flash attributes
    //将outFlash数据放置到重定向url中
    RequestContextUtils.saveOutputFlashMap(targetUrl, request, response);

    // Redirect
    //response重定向设置
    sendRedirect(request, response, targetUrl, this.http10Compatible);
    }


    protected final String createTargetUrl(Map<String, Object> model, HttpServletRequest request)
    throws UnsupportedEncodingException {

    // Prepare target URL.
    StringBuilder targetUrl = new StringBuilder();
    String url = getUrl();
    Assert.state(url != null, "'url' not set");

    if (this.contextRelative && getUrl().startsWith("/")) {
    // Do not apply context path to relative URLs.
    targetUrl.append(getContextPath(request));
    }
    targetUrl.append(getUrl());

    String enc = this.encodingScheme;
    if (enc == null) {
    enc = request.getCharacterEncoding();
    }
    if (enc == null) {
    enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
    }
    //替换重定向中的temp url
    if (this.expandUriTemplateVariables && StringUtils.hasText(targetUrl)) {
    //获取当前的temp url
    Map<String, String> variables = getCurrentRequestUriVariables(request);
    targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, enc);
    }
    if (isPropagateQueryProperties()) {
    appendCurrentQueryParams(targetUrl, request);
    }
    if (this.exposeModelAttributes) {
    appendQueryProperties(targetUrl, model, enc);
    }

    return targetUrl.toString();
    }

    private String getContextPath(HttpServletRequest request) {
    String contextPath = request.getContextPath();
    while (contextPath.startsWith("//")) {
    contextPath = contextPath.substring(1);
    }
    return contextPath;
    }

    //一段一段替换
    protected StringBuilder replaceUriTemplateVariables(
    String targetUrl, Map<String, Object> model, Map<String, String> currentUriVariables, String encodingScheme)
    throws UnsupportedEncodingException {

    StringBuilder result = new StringBuilder();
    Matcher matcher = URI_TEMPLATE_VARIABLE_PATTERN.matcher(targetUrl);
    int endLastMatch = 0;
    while (matcher.find()) {
    String name = matcher.group(1);
    Object value = (model.containsKey(name) ? model.remove(name) : currentUriVariables.get(name));
    if (value == null) {
    throw new IllegalArgumentException("Model has no value for key '" + name + "'");
    }
    result.append(targetUrl.substring(endLastMatch, matcher.start()));
    result.append(UriUtils.encodePathSegment(value.toString(), encodingScheme));
    endLastMatch = matcher.end();
    }
    result.append(targetUrl.substring(endLastMatch));
    return result;
    }

    private Map<String, String> getCurrentRequestUriVariables(HttpServletRequest request) {
    String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; //temp url是在mapping中处理的
    Map<String, String> uriVars = (Map<String, String>) request.getAttribute(name);
    return (uriVars != null) ? uriVars : Collections.<String, String> emptyMap();
    }
    }
  • 总结

    • 支持temp url
      • 例子
        1
        2
        3
        4
        5
        @GetMapping("redirect/{red}/test/{red2}")
        public String redirectView(RedirectAttributes attributes){
        attributes.addAttribute("reKey","reValue");
        return "redirect:/redirect/{red}/test/{red2}/xx";
        }
    • 会将OutFlashMap的数据,拼接到重定向url中,并且缓存到FlashMapManager

      剩下的视图不做过多分析,基本基于rest是不会使用的

      异常处理器

      DispatcherServlet调用逻辑

      DispatcherServlet
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
      @Nullable Object handler, Exception ex) throws Exception {

      // Success and error responses may use different content types
      request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

      // Check registered HandlerExceptionResolvers...
      ModelAndView exMv = null;
      //[1!] 处理器调用
      if (this.handlerExceptionResolvers != null) {
      for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
      exMv = resolver.resolveException(request, response, handler, ex);
      if (exMv != null) {
      break;
      }
      }
      }
      //[1]
      //[2]处理
      if (exMv != null) {
      if (exMv.isEmpty()) {
      request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
      return null;
      }
      // We might still need view name translation for a plain error model...
      if (!exMv.hasView()) {
      String defaultViewName = getDefaultViewName(request);
      if (defaultViewName != null) {
      exMv.setViewName(defaultViewName);
      }
      }
      if (logger.isTraceEnabled()) {
      logger.trace("Using resolved error view: " + exMv, ex);
      }
      else if (logger.isDebugEnabled()) {
      logger.debug("Using resolved error view: " + exMv);
      }
      WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
      return exMv;
      }
      //[2!]
      //[3] 若处理器没有正确返回视图,则抛出异常
      throw ex;
      }

      HandlerExceptionResolver体系

      HandlerExceptionResolverComposite

      典型的Composite,DispatcherServlet默认并不是这个,在使用注解配置mvc的WebMvcConfigurationSupport#handlerExceptionResolver中才
      使用了这个来配置

      AbstractHandlerExceptionResolver

  • 代码逻辑
    AbstractHandlerExceptionResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
    //Handler实例,如Controller,HandlerMethod,等
    private Set<?> mappedHandlers;
    private static final String HEADER_CACHE_CONTROL = "Cache-Control";
    @Nullable
    //Handler类对象
    private Class<?>[] mappedHandlerClasses;

    public ModelAndView resolveException(
    HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    if (shouldApplyTo(request, handler)) {//判断是否进行异常处理
    prepareResponse(ex, response); //设置一个Cache-Control
    ModelAndView result = doResolveException(request, response, handler, ex);//子类实现
    if (result != null) {
    // Print debug message when warn logger is not enabled.
    if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
    logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
    }
    // Explicitly configured warn logger in logException method.
    logException(ex, request);
    }
    return result;
    }
    else {
    return null;
    }
    }

    protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
    if (handler != null) {//包含实例
    if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
    return true;
    }
    if (this.mappedHandlerClasses != null) {//包含类
    for (Class<?> handlerClass : this.mappedHandlerClasses) {
    if (handlerClass.isInstance(handler)) {
    return true;
    }
    }
    }
    }
    // Else only apply if there are no explicit handler mappings.
    //若不去设置,这里一般都是这条件,全通过
    return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
    }
    }
  • 总结
    • 提供了对于Handler的过滤能力,spring默认实现并没有使用
      SimpleMappingExceptionResolver
  • 代码逻辑
    SimpleMappingExceptionResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {
    // key为异常名,value为视图名
    private Properties exceptionMappings;
    //不处理的异常
    @Nullable
    private Class<?>[] excludedExceptions;
    //默认错误视图页面
    @Nullable
    private String defaultErrorView;

    @Nullable
    private Integer defaultStatusCode;

    private Map<String, Integer> statusCodes = new HashMap<>();

    @Nullable
    private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;

    protected ModelAndView doResolveException(
    HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    // Expose ModelAndView for chosen error view.
    String viewName = determineViewName(ex, request);
    if (viewName != null) {
    // Apply HTTP status code for error views, if specified.
    // Only apply it if we're processing a top-level request.
    Integer statusCode = determineStatusCode(request, viewName);
    if (statusCode != null) {
    applyStatusCodeIfPossible(request, response, statusCode);
    }
    return getModelAndView(viewName, ex, request); //创建ModelAndView,并返回
    }
    else {
    return null;
    }
    }

    protected String determineViewName(Exception ex, HttpServletRequest request) {
    String viewName = null;
    if (this.excludedExceptions != null) {
    for (Class<?> excludedEx : this.excludedExceptions) { //排除的跳过
    if (excludedEx.equals(ex.getClass())) {
    return null;
    }
    }
    }
    // Check for specific exception mappings.
    if (this.exceptionMappings != null) { //若定义了properties,则进行匹配
    viewName = findMatchingViewName(this.exceptionMappings, ex);
    }
    // Return default error view else, if defined.
    if (viewName == null && this.defaultErrorView != null) { //未定义或未匹配到,则返回默认值
    if (logger.isDebugEnabled()) {
    logger.debug("Resolving to default view '" + this.defaultErrorView + "'");
    }
    viewName = this.defaultErrorView;
    }
    return viewName;
    }

    //---------------匹配逻辑----------------
    //简单来说就是递归确定ex以及其父类有无能够和propeties中有匹配的情况,匹配就返回viewname
    protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
    String viewName = null;
    String dominantMapping = null;
    int deepest = Integer.MAX_VALUE;
    for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
    String exceptionMapping = (String) names.nextElement();
    int depth = getDepth(exceptionMapping, ex); //递归
    if (depth >= 0 && (depth < deepest || (depth == deepest &&
    dominantMapping != null && exceptionMapping.length() > dominantMapping.length()))) {
    deepest = depth;
    dominantMapping = exceptionMapping;
    viewName = exceptionMappings.getProperty(exceptionMapping);
    }
    }
    if (viewName != null && logger.isDebugEnabled()) {
    logger.debug("Resolving to view '" + viewName + "' based on mapping [" + dominantMapping + "]");
    }
    return viewName;
    }
    protected int getDepth(String exceptionMapping, Exception ex) {
    return getDepth(exceptionMapping, ex.getClass(), 0);
    }

    private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
    if (exceptionClass.getName().contains(exceptionMapping)) {
    // Found it!
    return depth;
    }
    // If we've gone as far as we can go and haven't found it...
    if (exceptionClass == Throwable.class) {
    return -1;
    }
    return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
    }
    }

    //------------状态码----------------
    protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
    if (this.statusCodes.containsKey(viewName)) {
    return this.statusCodes.get(viewName);
    }
    return this.defaultStatusCode;
    }
    DefaultHandlerExceptionResolver
    该实现将spring提供的异常一一对应,返回对应的状态码,MVCContainer为空
  • 代码逻辑
    DefaultHandlerExceptionResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
    protected ModelAndView doResolveException(
    HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    try {
    if (ex instanceof HttpRequestMethodNotSupportedException) {
    return handleHttpRequestMethodNotSupported(
    (HttpRequestMethodNotSupportedException) ex, request, response, handler);
    }
    else if (ex instanceof HttpMediaTypeNotSupportedException) {
    return handleHttpMediaTypeNotSupported(
    (HttpMediaTypeNotSupportedException) ex, request, response, handler);
    }
    else if (ex instanceof HttpMediaTypeNotAcceptableException) {
    return handleHttpMediaTypeNotAcceptable(
    (HttpMediaTypeNotAcceptableException) ex, request, response, handler);
    }
    else if (ex instanceof MissingPathVariableException) {
    return handleMissingPathVariable(
    (MissingPathVariableException) ex, request, response, handler);
    }
    else if (ex instanceof MissingServletRequestParameterException) {
    return handleMissingServletRequestParameter(
    (MissingServletRequestParameterException) ex, request, response, handler);
    }
    else if (ex instanceof ServletRequestBindingException) {
    return handleServletRequestBindingException(
    (ServletRequestBindingException) ex, request, response, handler);
    }
    else if (ex instanceof ConversionNotSupportedException) {
    return handleConversionNotSupported(
    (ConversionNotSupportedException) ex, request, response, handler);
    }
    else if (ex instanceof TypeMismatchException) {
    return handleTypeMismatch(
    (TypeMismatchException) ex, request, response, handler);
    }
    else if (ex instanceof HttpMessageNotReadableException) {
    return handleHttpMessageNotReadable(
    (HttpMessageNotReadableException) ex, request, response, handler);
    }
    else if (ex instanceof HttpMessageNotWritableException) {
    return handleHttpMessageNotWritable(
    (HttpMessageNotWritableException) ex, request, response, handler);
    }
    else if (ex instanceof MethodArgumentNotValidException) {
    return handleMethodArgumentNotValidException(
    (MethodArgumentNotValidException) ex, request, response, handler);
    }
    else if (ex instanceof MissingServletRequestPartException) {
    return handleMissingServletRequestPartException(
    (MissingServletRequestPartException) ex, request, response, handler);
    }
    else if (ex instanceof BindException) {
    return handleBindException((BindException) ex, request, response, handler);
    }
    else if (ex instanceof NoHandlerFoundException) {
    return handleNoHandlerFoundException(
    (NoHandlerFoundException) ex, request, response, handler);
    }
    else if (ex instanceof AsyncRequestTimeoutException) {
    return handleAsyncRequestTimeoutException(
    (AsyncRequestTimeoutException) ex, request, response, handler);
    }
    }
    catch (Exception handlerEx) {
    if (logger.isWarnEnabled()) {
    logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
    }
    }
    return null;
    }
    //随便看一个
    protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
    HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {

    String[] supportedMethods = ex.getSupportedMethods();
    if (supportedMethods != null) {
    response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
    }
    //405状态码
    response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
    return new ModelAndView();
    }

    }
  • 总结
    • 不同情况对应不同状态码,默认情况DispatcherServlet会使用这个
      ResponseStatusExceptionResolver
  • 代码逻辑
    ResponseStatusExceptionResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {
    //含有消息源
    private MessageSource messageSource;
    @Override
    public void setMessageSource(MessageSource messageSource) {
    this.messageSource = messageSource;
    }

    protected ModelAndView doResolveException(
    HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    try {
    if (ex instanceof ResponseStatusException) { //若抛出status异常
    return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
    }

    ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
    if (status != null) {//若异常被@SessionStatus标记
    return resolveResponseStatus(status, request, response, handler, ex);
    }

    if (ex.getCause() instanceof Exception) {
    return doResolveException(request, response, handler, (Exception) ex.getCause());
    }
    }
    catch (Exception resolveEx) {
    if (logger.isWarnEnabled()) {
    logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
    }
    }
    return null;
    }
    //code和reason可以从StatusExe或@ResponseStatus中获取
    protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
    throws IOException {
    //response处理
    if (!StringUtils.hasLength(reason)) {
    response.sendError(statusCode);
    }
    else {
    String resolvedReason = (this.messageSource != null ?
    this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
    reason);
    response.sendError(statusCode, resolvedReason);
    }
    return new ModelAndView();
    }
    }
  • 总结
    • 用于ResponseStatusException和被@ResponseStaues标记的注解
    • DispatcherServlet默认异常处理器之一
      ExceptionHandlerExceptionResolver
      最常见的异常处理器
  • 代码逻辑
    ExceptionHandlerExceptionResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {
    //重写了applyto逻辑,保证了该处理器处理的handler一定是MethodHandler类型
    protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
    if (handler == null) {
    return super.shouldApplyTo(request, null);
    }
    else if (handler instanceof HandlerMethod) {
    HandlerMethod handlerMethod = (HandlerMethod) handler;
    handler = handlerMethod.getBean();
    return super.shouldApplyTo(request, handler);
    }
    else {
    return false;
    }
    }
    protected final ModelAndView doResolveException(
    HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
    }
    protected abstract ModelAndView doResolveHandlerMethodException(
    HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);
    }
    //子类
    public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
    implements ApplicationContextAware, InitializingBean {
    //内部组件和RequestMappingAdaptor很相似
    private List<HandlerMethodArgumentResolver> customArgumentResolvers;

    @Nullable
    private HandlerMethodArgumentResolverComposite argumentResolvers;

    @Nullable
    private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

    @Nullable
    private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

    private List<HttpMessageConverter<?>> messageConverters;

    private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

    private final List<Object> responseBodyAdvice = new ArrayList<>();

    private ApplicationContext applicationContext;

    private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
    new ConcurrentHashMap<>(64);
    //初始化确定
    private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
    new LinkedHashMap<>();
    //---------------------始化-------------------------
    public void afterPropertiesSet() {
    // Do this first, it may add ResponseBodyAdvice beans
    initExceptionHandlerAdviceCache();

    if (this.argumentResolvers == null) {
    List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
    this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
    List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
    this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
    }

    private void initExceptionHandlerAdviceCache() {
    if (getApplicationContext() == null) {
    return;
    }
    //获取@Controlleradvice类,并转换为ControllerAdviceBean
    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);
    }
    //resolver用来存方法@ExceptionHandler函数,并提供匹配函数
    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");
    }
    }
    }
    }
    //-------------------处理异常----------------------
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
    HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
    //[1]根据异常处理函数创建一个ServletIncocableHandlerMethod
    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
    if (exceptionHandlerMethod == null) {
    return null;
    }
    //[1!]
    //[2]设置入参和回参处理器
    if (this.argumentResolvers != null) {
    exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
    exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }
    //[2!]

    //[3]调用invoke,这里又用到了invoke(arg...)可变参数,将exception,cause,handlerMethod传递过去
    //另个用到这种是InintFactory传递dataBinder的时候
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();

    try {
    if (logger.isDebugEnabled()) {
    logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
    }
    Throwable cause = exception.getCause();
    if (cause != null) {
    // Expose cause as provided argument as well
    exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
    }
    else {
    // Otherwise, just the given exception as-is
    exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
    }
    }
    catch (Throwable invocationEx) {
    // Any other than the original exception (or its cause) is unintended here,
    // probably an accident (e.g. failed assertion or the like).
    if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
    logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
    }
    // Continue with default processing of the original exception...
    return null;
    }

    if (mavContainer.isRequestHandled()) {//如果异常处理器结束了处理,则返回一个空白,基本发生于rest情况,即出现@ResponseBody注解
    return new ModelAndView();
    }
    else {//返回完整视图容器
    ModelMap model = mavContainer.getModel();
    HttpStatus status = mavContainer.getStatus();
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
    mav.setViewName(mavContainer.getViewName());
    if (!mavContainer.isViewReference()) {
    mav.setView((View) mavContainer.getView());
    }
    if (model instanceof RedirectAttributes) {
    Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
    RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
    }
    return mav;
    }
    }
    //--------------根据handlerMethod创建SIHM---------------------
    protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
    @Nullable HandlerMethod handlerMethod, Exception exception) {

    Class<?> handlerType = null;

    if (handlerMethod != null) {//从含有本handlerMethod中寻找,并创建
    // Local exception handler methods on the controller class itself.
    // To be invoked through the proxy, even in case of an interface-based proxy.
    handlerType = handlerMethod.getBeanType();
    ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
    if (resolver == null) {
    resolver = new ExceptionHandlerMethodResolver(handlerType);
    this.exceptionHandlerCache.put(handlerType, resolver);
    }
    Method method = resolver.resolveMethod(exception);
    if (method != null) {
    return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
    }
    // For advice applicability check below (involving base packages, assignable types
    // and annotation presence), use target class instead of interface-based proxy.
    if (Proxy.isProxyClass(handlerType)) {
    handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
    }
    }
    //从全局advice中匹配并创建
    for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
    ControllerAdviceBean advice = entry.getKey();
    if (advice.isApplicableToBeanType(handlerType)) {
    ExceptionHandlerMethodResolver resolver = entry.getValue();
    Method method = resolver.resolveMethod(exception);
    if (method != null) {
    return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
    }
    }
    }

    return null;
    }
  • 总结
    • @ExecptionHandler的调用实际被转换成了ServletInvokeHandlerMethod
    • @ExecptionHandler可以使用在Controller,或者@ControllerAdvice
    • 参数可以为excption,cause,handlerMethod
    • 入参和回参则决定于该处理器中的入参和回参处理器
      ExceptionHandlerMethodResolver
      用来解析@ExceptionHandler
  • 代码
    ExceptionHandlerMethodResolver
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    public class ExceptionHandlerMethodResolver {
    //======================属性===========================
    public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
    AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);

    private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);

    private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
    //====================构造初始化==========================
    public ExceptionHandlerMethodResolver(Class<?> handlerType) {
    //handlerType中获取@ExceptionHandler函数
    for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
    for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {//判断该函数与其匹配的异常
    addExceptionMapping(exceptionType, method);
    }
    }
    }

    //--------------异常匹配-----------------
    private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
    List<Class<? extends Throwable>> result = new ArrayList<>();
    detectAnnotationExceptionMappings(method, result);
    if (result.isEmpty()) {//说明@ExceptionHandler中没有value值
    for (Class<?> paramType : method.getParameterTypes()) { //选择method形参作为异常匹配
    if (Throwable.class.isAssignableFrom(paramType)) {
    result.add((Class<? extends Throwable>) paramType);
    }
    }
    }
    if (result.isEmpty()) {
    throw new IllegalStateException("No exception types mapped to " + method);
    }
    return result;
    }
    //获取方法中@ExceptionHandler#value作为异常值
    private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
    ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
    Assert.state(ann != null, "No ExceptionHandler annotation");
    result.addAll(Arrays.asList(ann.value()));
    }
    //-------------缓存--------------------
    private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
    Method oldMethod = this.mappedMethods.put(exceptionType, method);
    if (oldMethod != null && !oldMethod.equals(method)) {
    throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
    exceptionType + "]: {" + oldMethod + ", " + method + "}");
    }
    }

    //===========================解析====================================
    public Method resolveMethod(Exception exception) {
    return resolveMethodByThrowable(exception);
    }
    public Method resolveMethodByThrowable(Throwable exception) {
    Method method = resolveMethodByExceptionType(exception.getClass());
    if (method == null) {
    Throwable cause = exception.getCause();
    if (cause != null) {
    method = resolveMethodByExceptionType(cause.getClass());
    }
    }
    return method;
    }
    public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
    Method method = this.exceptionLookupCache.get(exceptionType);
    if (method == null) {
    method = getMappedMethod(exceptionType);
    this.exceptionLookupCache.put(exceptionType, method);
    }
    return method;
    }
    //由于初始化的逻辑会导致,多个excption对应一个method,因此此处做了一个排序
    private 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()) {
    //排序,简单来说就看匹配的excpetion和发生的异常是否类型更加接近
    matches.sort(new ExceptionDepthComparator(exceptionType));
    return this.mappedMethods.get(matches.get(0));
    }
    else {
    return null;
    }
    }
    }
文章作者: fancylight
文章链接: https://www.fancylight.top/2020/03/26/springMVC%E6%A0%B8%E5%BF%83%E4%B8%89/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 博客
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论