SpringMVC系列:HandlerMapping初始化

https://blog.csdn.net/csdn_20150804/article/details/103943435

文章目录

基于springboot2.1.4; springmvc执行,核心逻辑是在DispatchServlet中,其中的若干属性,初始值都是null,如下:

    private MultipartResolver multipartResolver;
    private LocaleResolver localeResolver;
    private ThemeResolver themeResolver;
    //主要分析这个
    private List<HandlerMapping> handlerMappings;
    private List<HandlerAdapter> handlerAdapters;
    private List<HandlerExceptionResolver> handlerExceptionResolvers;
    private RequestToViewNameTranslator viewNameTranslator;
    private FlashMapManager flashMapManager;
    private List<ViewResolver> viewResolvers;
12345678910

上篇文章中,DispatchServlet在执行相关逻辑时,都是直接使用了上边这些属性,本文分析下这些属性是何时在哪里被初始化的,并以handlerMappings为例,着重分析。

DispatchServlet类是个servlet,包含init方法,tomcat容器在启动时,会默认执行init方法,而DispatchServlet的父类HttpServletBean实现了Servlet接口的init方法。因此我们从HttpServletBean#init() 方法开始。

1.HttpServletBean#init()

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();
}
123456789101112131415161718192021222324

上面方法,其中关键方法是最后一行 initServletBean();这是个空方法,实现在其子类FrameworkServlet中实现,FrameworkServlet是DispatchServlet的直接父类。继续看下initServletBean的实现,

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 {
        //初始化springmvc web容器
        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");
    }
}
123456789101112131415161718192021222324252627282930

其中initWebApplicationContext()表示初始化web容器,进入,

protected WebApplicationContext initWebApplicationContext() {
    //得到根容器,也就是springmvc的父容器,spring容器ApplicationContext
    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) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!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;
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253

从上面方法可以看出,web容器实际上是spring容器的子容器,web容器内部包含了父容器的引用,然后再包含自己特有的一些内容,这些内容在onRefresh(wac);方法中初始化,继续进入onRefresh方法,

2.DispatcherServlet#onRefresh(ApplicationContext context)

protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}
123

上述方法在DispatcherServlet类中实现的,继续进入initStrategies(context)

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    //初始化HandlerMappings
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}
123456789101112

这里使用了策略模式,其中initHandlerMappings(context); 就是用来初始化handlerMappings属性的。

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;
​
        if (this.detectAllHandlerMappings) {//默认是true,扫描所有
            // 从ApplicationContext中,找类型是HandlerMapping的bean
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                //初始化handlerMappings属性
                this.handlerMappings = new ArrayList<>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                AnnotationAwareOrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }
​
        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
        //如果容器中一个handlerMapping都没有,则给一个默认的,就是去DispatcherServlet.properties配置文件中读取
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isTraceEnabled()) {
                logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                        "': using default strategies from DispatcherServlet.properties");
            }
        }
    }
1234567891011121314151617181920212223242526272829303132333435

上述方法中,handlerMappings是从上下文容器中直接get出来的,如果get不到,会使用默认的配置:其实从上述方法中的log信息中可以看出,有个叫DispatcherServlet.properties的配置文件。 打开看下:

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
​
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
​
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
​
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
​
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
​
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
​
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
​
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
1234567891011121314151617181920

从配置文件中我们可以发现,所有初始化所需要的内容都在这里了,其中包括HandlerMapping,共有两个。

但是,如果容器中有,就不会使用默认的,那么容器中是何时放进去的呢, 我们就直接分析springboot中的初始化逻辑。

3.WebMvcAutoConfiguration初始化配置

我们知道springboot在启动的时候,默认会加载spring.factories文件中的配置类,在spring-boot-autoconfigure.jar包下的spring.factories文件中,有如下内容: org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,

就是这个WebMvcAutoConfiguration,初始化了所有webmvc的配置,在这里面可以找到此配置类中还有个EnableWebMvcConfiguration内部类,可以发现他继承了WebMvcConfigurationSupport,所有HandlerMappings都能在这俩配置类中找到(代码比较多,就不贴出来了)。

4.RequestMappingHandlerMapping初始化

这里看下一个比较重要的HandlerMapping,RequestMappingHandlerMapping 的初始化代码,RequestMappingHandlerMapping专门用来处理@Controller修饰的bean,这种方式也是我们开发中最常用的。

@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
	RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
	mapping.setOrder(0);
	mapping.setInterceptors(getInterceptors());
	mapping.setContentNegotiationManager(mvcContentNegotiationManager());
	mapping.setCorsConfigurations(getCorsConfigurations());

	PathMatchConfigurer configurer = getPathMatchConfigurer();

	Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
	if (useSuffixPatternMatch != null) {
		mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
	}
	Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
	if (useRegisteredSuffixPatternMatch != null) {
		mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
	}
	Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
	if (useTrailingSlashMatch != null) {
		mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
	}

	UrlPathHelper pathHelper = configurer.getUrlPathHelper();
	if (pathHelper != null) {
		mapping.setUrlPathHelper(pathHelper);
	}
	PathMatcher pathMatcher = configurer.getPathMatcher();
	if (pathMatcher != null) {
		mapping.setPathMatcher(pathMatcher);
	}

	return mapping;
}

protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
	return new RequestMappingHandlerMapping();
}
1234567891011121314151617181920212223242526272829303132333435363738

上面这段代码,是告诉spring容器实例化一个RequestMappingHandlerMapping,并且设置了一些属性值。

而RequestMappingHandlerMapping类还实现了接口InitializingBean,所以在实例化后,会执行afterPropertiesSet()方法:

public void afterPropertiesSet() {
	//初始化RequestMappingInfo的config
	this.config = new RequestMappingInfo.BuilderConfiguration();
	this.config.setUrlPathHelper(getUrlPathHelper());
	this.config.setPathMatcher(getPathMatcher());
	this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
	this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
	this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
	this.config.setContentNegotiationManager(getContentNegotiationManager());
	//调用父类方法
	super.afterPropertiesSet();
}
123456789101112

打开父类方法:

public void afterPropertiesSet() {
	initHandlerMethods();
}
protected void initHandlerMethods() {
	//从spring容器中拿到所有bean,然后遍历
	for (String beanName : getCandidateBeanNames()) {
		if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
			//扫描所有handler方法,就是controller层的方法
			processCandidateBean(beanName);
		}
	}
	//记录个日志
	handlerMethodsInitialized(getHandlerMethods());
}
1234567891011121314

上述方法中,spring拿到容器中的诉苦有bean,然后遍历处理,让我们看下如何处理的,

protected void processCandidateBean(String beanName) {
	Class<?> beanType = null;
	try {
		beanType = obtainApplicationContext().getType(beanName);
	}
	catch (Throwable ex) {
		// An unresolvable bean type, probably from a lazy bean - let's ignore it.
		if (logger.isTraceEnabled()) {
			logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
		}
	}
	if (beanType != null && isHandler(beanType)) {
		//探测寻找Handler方法
		detectHandlerMethods(beanName);
	}
}

1234567891011121314151617

上述代码逻辑就是判断一个bean是不是Handler(就是所谓的controller),如果是,那么就继续扫描Handler中的方法,并注册;其中判断isHandler(beanType)用来判断是否是handler。 因为我们这里分析的是RequestMappingHandlerMapping,所以isHandler方法在此类中实现:

protected boolean isHandler(Class<?> beanType) {
	return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
			AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
1234

就是判断bean上是否有@Controller注解或者@RequestMapping注解。

下面继续看下spring如何探测handler方法然后如何注册的,

protected void detectHandlerMethods(Object handler) {
    //handler对象就是我们定义的一个Controller
	Class<?> handlerType = (handler instanceof String ?
			obtainApplicationContext().getType((String) handler) : handler.getClass());

	if (handlerType != null) {
		Class<?> userType = ClassUtils.getUserClass(handlerType);
		//扫描到Controller中的所有方法,key是方法名,value是路径名
		Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
				(MethodIntrospector.MetadataLookup<T>) method -> {
					try {
						return getMappingForMethod(method, userType);
					}
					catch (Throwable ex) {
						throw new IllegalStateException("Invalid mapping on handler class [" +
								userType.getName() + "]: " + method, ex);
					}
				});
		if (logger.isTraceEnabled()) {
			logger.trace(formatMappings(userType, methods));
		}
		//
		methods.forEach((method, mapping) -> {
			//如果有代理,这里要找到被代理后的方法
			Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
			//注册Method
			registerHandlerMethod(handler, invocableMethod, mapping);
		});
	}
}
123456789101112131415161718192021222324252627282930

上述代码逻辑,首先拿到Controller中的所有方法以及方法上的路径,如果方法有被代理,要使用被代理后的方法;然后遍历进行注册; 下面看下注册的逻辑:

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
	this.mappingRegistry.register(mapping, handler, method);
}
123

其中,MappingRegistry是AbstractHandlerMethodMapping的内部类,里面维护了一堆map,

class MappingRegistry {

	private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

	private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();

	private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();

	private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

	private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

	private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	...
}
123456789101112131415

注册就是往这些map中存东西:

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();
    try {
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        assertUniqueMethodMapping(handlerMethod, mapping);
        //存储请求路径(包括请求方式)和请求方法的映射关系
        this.mappingLookup.put(mapping, handlerMethod);
​
        List<String> directUrls = getDirectUrls(mapping);
        for (String url : directUrls) {
        //存储url和请RequestMapping注解中的信息映射
            this.urlLookup.add(url, mapping);
        }
​
        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }
​
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }
​
        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}
12345678910111213141516171819202122232425262728293031

说下参数: mapping:是方法上的路径信息(@RequestMpping注解中的信息),包括请求方式GET/POST等; handler:就是Controller对象; method:Controller中的方法; 注册的过程就是MappingRegistry存请求路径处理方法等相关的映射关系,而这些东西最终会在DispatcherServlet处理前端请求时使用到,也就是上篇文章中,我们分析的内容。

本文先分析到这里,其他初始化内容也是和HandlerMapping一样的,具体代码就不再展示了,直接在配置类中搜索相关内容即可。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring MVC 的生命周期可以大致分为以下几个阶段: 1. Servlet 容器加载 Spring MVC:在 Web 应用启动时,Servlet 容器会加载 Spring MVC 的配置,并初始化 DispatcherServlet。 2. 初始化 DispatcherServlet:DispatcherServlet 会读取 Web 应用中的配置文件,如 applicationContext.xml,通过配置文件中的信息来初始化 Spring 容器。 3. Spring 容器初始化:在 Spring 容器初始化阶段,会完成所有 Bean 的创建和依赖注入等操作。 4. 初始化 HandlerMappingHandlerMapping 是用来映射请求 URL 到具体的处理器(Controller)的组件。在初始化阶段,DispatcherServlet 会根据配置文件中的信息,初始化对应的 HandlerMapping 实现类。 5. 初始化 HandlerAdapter:HandlerAdapter 是用来执行具体的请求处理器(Controller)的组件。在初始化阶段,DispatcherServlet 会根据配置文件中的信息,初始化对应的 HandlerAdapter 实现类。 6. 处理请求:当有请求到达时,DispatcherServlet 会根据 HandlerMapping 的映射规则,找到对应的处理器(Controller)。 7. 执行处理器(Controller)的方法:DispatcherServlet 会通过 HandlerAdapter 调用处理器(Controller)中的方法,并传递请求参数。 8. 视图解析和渲染:处理器(Controller)返回一个逻辑视图名,DispatcherServlet 会使用 ViewResolver 解析该视图名,并找到对应的视图(View)。 9. 渲染视图:DispatcherServlet 将模型数据传递给视图(View),视图会根据模型数据生成最终的响应结果。 10. 返回响应:DispatcherServlet 将最终的响应结果返回给客户端。 11. 销毁 Spring 容器:当 Web 应用关闭时,Servlet 容器会销毁 Spring MVC 的配置,同时销毁 Spring 容器。 以上是 Spring MVC 的主要生命周期,每个阶段都有相应的回调方法和事件监听器可以进行扩展和定制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值