- 三哥
内容来自【自学星球】
欢迎大家来了解我的星球,和星主(也就是我)一起学习 Java ,深入 Java 体系中的所有技术。我给自己定的时间是一年,无论结果如何,必定能给星球中的各位带来点东西。
想要了解更多,欢迎访问👉:自学星球
--------------SSM系列源码文章及视频导航--------------
创作不易,望三连支持!
SSM源码解析视频
👉点我
Spring
SpringMVC
MyBatis
---------------------【End】--------------------
遵循源码解读的惯例,先来搭建一个简单的使用案例。
SpringMVC的案例可以分为两种:
对于这两种形式的使用都非常重要,所以后面我都会对其进行源码分析,那先来看看使用流程。
1、创建一个 web 环境的项目
2、引入 pom 依赖
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.0.2.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
dependency>
3、编写SpringMVC配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.j3code.studyspring.mvc.xml" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
context:component-scan>
<mvc:annotation-driven>mvc:annotation-driven>
beans>
4、编写 web.xml 文件
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Applicationdisplay-name>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springweb.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
5、编写测试 Controller
@RestController
@RequestMapping("/my")
public class MyController {
@GetMapping("/test01")
public String test() {
return "Hello World J3";
}
}
6、配置 Tomcat 之后,就可以启动测试了
注解版本和 XML 版本的 1、2 步骤一样。
3、编写注解配置文件
@Configuration
@ComponentScan("cn.j3code.studyspring.mvc.annotation")
public class MySpringMvcConfig {
}
4、编写web初始化类
public class WebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 启动 SpringMVC 容器
AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();
// 注册 MVC 配置类
app.register(MySpringMvcConfig.class);
// 创建 DispatcherServlet
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(app));
// 填写映射路径
dispatcher.addMapping("/");
// 填写优先级
dispatcher.setLoadOnStartup(1);
}
}
5、web.xml配置文件置空或者删除
6、编写测试 Controller
@RestController
@RequestMapping("/my/annotation")
public class MyAnnotationController {
@GetMapping("/test01")
public String test() {
return "Hello World J3";
}
}
7、配置 Tomcat 之后,就可以启动测试了
使用案例搭建完毕之后,那就要来分析分析其是如何启动的了,依然是分两种情况分析:
在 XML 方式启动 SpringMVC 框架时,我们在 web.xml 文件中配置了一个类 DispatcherServlet
。显然,这个类就是我们分析 SpringMVC 框架启动流程的入口。
先来看看 DispatcherServlet 类的继承结构图:
由图我们可知 DispatcherServlet
是一个 Servlet ,那么在 Tomcat 启动的时候会来调用其 init 方法进行 Servlet 的初始化,所以这就是我们分析的入口。
org.springframework.web.servlet.HttpServletBean#init
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// Set bean properties from init parameters.
// 获取在web.xml配置的初始化参数,并将其设置到DispatcherServlet中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// bw 就是 DispatcherServlet
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.
// 调用子类FrameworkServlet进行初始化
// 模板方法,此方法在HttpServletBean本身是空的,但是因为调用方法的对象是DispatcherServlet
// 所以优先在DispatcherServlet找,找不到再去父类找,最后在FrameworkServlet找
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
该方法做了两件事情,配置初始化参数和初始化 Servlet ,下面我们来看看是如何初始化的。
org.springframework.web.servlet.FrameworkServlet#initServletBean
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
// 初始化WebApplicationContext,并调用子类(DispatcherServlet)的onRefresh(wac)方法
this.webApplicationContext = initWebApplicationContext();
// 空方法
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
看 initWebApplicationContext 方法名字也知道这是初始化 web 应用上下文的,所以我们进去看看。
org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
// 获取root WebApplicationContext,即web.xml中配置的listener(ContextLoaderListener)
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 判断容器是否由编程式传入(即是否已经存在了容器实例),存在的话直接赋值给wac,给springMVC容器设置父容器
// 最后调用刷新函数configureAndRefreshWebApplicationContext(wac),作用是把Spring MVC配置文件的配置信息加载到容器中去
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
// context上下文在构造是注入
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
// context没有被refreshed,提供一些诸如设置父context、设置应用context id等服务
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);
}
}
}
// 在ServletContext中寻找是否有Spring MVC容器,初次运行是没有的,Spring MVC初始化完毕ServletContext就有了Spring MVC容器
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();
}
// 当wac既没有没被编程式注册到容器中的,也没在ServletContext找得到,此时就要新建一个Spring MVC容器
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 如果没有WebApplicationContext则创建
wac = createWebApplicationContext(rootContext);
}
// 到这里Spring MVC容器已经创建完毕,接着真正调用DispatcherServlet的初始化方法onRefresh(wac)
// 此处仍是模板模式的应用
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.
onRefresh(wac);
}
// 将Spring MVC容器存放到ServletContext中去,方便下次取出来
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
这个方法主要干了些啥:
上述方法的 1 到 4 都是围绕 WebApplicationContext 所展开,如果有 WebApplicationContext 则进行配置和刷新如果没有则进行创建然后再配置刷新,所以我们就只分析一个比较全的方法 createWebApplicationContext 就可以了。
源码如下:
org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.web.context.WebApplicationContext)
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
在进入:
org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 实例化容器
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
// 设置容器环境
wac.setEnvironment(getEnvironment());
// 设置父容器
wac.setParent(parent);
// 加载Spring MVC的配置信息,如:bean注入、注解、扫描等等
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 刷新容器,根据Spring MVC配置文件完成初始化操作
configureAndRefreshWebApplicationContext(wac);
return wac;
}
该方法创建了 web 容器紧接着最关键的一步就是通过 configureAndRefreshWebApplicationContext 方法来刷新容器,下面来看看该方法源码。
org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// 用可以获取到的信息,获取一个更有意义的上下文
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
// 生成默认 id
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
// 设置 servletContext 属性
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
// 初始化属性源, 确保 servlet属性源到位并能够在任何 refresh 之前的后期处理和初始化中使用
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
// 空方法
postProcessWebApplicationContext(wac);
// 应用 ApplicationContextInitializer 接口中的 initialize 方法
applyInitializers(wac);
//刷新容器
wac.refresh();
}
这个方法为 web 容器设置了很多必要属性,并且在刷新容器之前还应用了 ApplicationContextInitializer 接口中的 initialize 方法,最后才开始进行容器的刷新。
现在我们回归到 initWebApplicationContext 方法,只剩下了 onRefresh(wac) 没有分析,那来看看其源码吧!
org.springframework.web.servlet.DispatcherServlet#onRefresh
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
org.springframework.web.servlet.DispatcherServlet#initStrategies
protected void initStrategies(ApplicationContext context) {
// 1、初始化文件上传处理器
initMultipartResolver(context);
// 2、初始化国际化配置
initLocaleResolver(context);
// 3、初始化主题处理器
initThemeResolver(context);
// 4、初始化HanlderMapping
initHandlerMappings(context);
// 5、初始化HandlerAdapter
// HandlerAdapter用来调用具体的方法对用户发来的请求来进行处理
initHandlerAdapters(context);
// 6、初始化异常处理器,
// HandlerExceptionResolver是用来对请求处理过程中产生的异常进行处理
initHandlerExceptionResolvers(context);
// 7、RequestToViewNameTranslator用于在视图路径为空的时候,自动解析请求
// 去获取ViewName
initRequestToViewNameTranslator(context);
// 8、初始化视图处理器
// ViewResolvers将逻辑视图转成view对象
initViewResolvers(context);
// 9、FlashMapManager用于存储、获取以及管理FlashMap实例
initFlashMapManager(context);
}
该方法是初始化 SpringMVC 中的九大组件,这些组件初始化完毕时也就是意味着 SpringMVC 启动成功。
注解启动方式我们主要看案例中的 WebInitializer 类。
public class WebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 启动 SpringMVC 容器
AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();
// 注册 MVC 配置类
app.register(MySpringMvcConfig.class);
// 创建 DispatcherServlet
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(app));
// 填写映射路径
dispatcher.addMapping("/");
// 填写优先级
dispatcher.setLoadOnStartup(1);
}
}
该类实现了 WebApplicationInitializer 接口中的 onStartup 方法,并且直接创建出了 web 容器和 DispatcherServlet 然后添加到 ServletContext 中。那有 DispatcherServlet 就好办了,肯定会走它的 init 方法,这流程就和 xml 的一致了,就不重复分析了。
最后一个问题来了,谁会过来调用 WebInitializer 类呢!
要明白这点,就不得不来看下面这个类了:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
// webAppInitializerClasses 就是servlet3.0规范中为我们收集的 WebApplicationInitializer 接口的实现类的class
// 从webAppInitializerClasses中筛选并实例化出合格的相应的类
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
// 这行代码说明我们在实现WebApplicationInitializer可以通过继承Ordered, PriorityOrdered来自定义执行顺序
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
// 迭代每个initializer实现的方法
initializer.onStartup(servletContext);
}
}
}
这个类是干啥的?
HandlesTypes 注解用于声明 onStartup 方法参数中感兴趣的 class ,例如 SpringServletContainerInitializer 类,HandlesTypes 注解中的值为 WebApplicationInitializer.class ,则 onStartup 参数中 webAppInitializerClasses 集合的值都是 WebApplicationInitializer.class 类型。
ServletContainerInitializer 是 Servlet 3.0 引入的接口,用于在 web 应用启动时动态添加 servlet 、 filter 和 listener 。
在 Tomcat 启动时,会读取 META-INF/services/javax.servlet.ServletContainerInitializer
文件中存放实现该接口的类,并调用 onStartup 方法,SpringServletContainerInitializer 的配置如图:
该机制也称为 SPI。
现在知道我们的 WebInitializer 类为啥会生效了吧!
这九大组件的初始化流程都非常类似,大致流程如下:
我们来看看它们共同调用的方法 getDefaultStrategies 。
public class DispatcherServlet extends FrameworkServlet {
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
// 创建文件资源对象,文件路径为 DispatcherServlet.properties
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
// 加载资源文件
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
// strategyInterface的class全限定名称
String key = strategyInterface.getName();
// 从默认properties中根据托底组件的class名称获取对应的托底组件实现类名称.
// 可以参考DispatcherServlet.properties的具体格式
String value = defaultStrategies.getProperty(key);
if (value != null) {
// 一个策略接口可能对应多个实现类.
// 策略接口和实现类的格式为:com.wb.spring.interface.Strategy=com.wb.spring.impl.A,com.wb.spring.impl.B
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
// 初始化策略实现类组价的list集合
List<T> strategies = new ArrayList<>(classNames.length);
// 循环初始化
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
// 创建策略接口实现类对应的Bean组件
// 通过AutowireCapableBeanFactory.createBean(clazz)实现
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException(
"Could not find DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]", ex);
}
catch (LinkageError err) {
throw new BeanInitializationException(
"Error loading DispatcherServlet's default strategy class [" + className +
"] for interface [" + key + "]: problem with class file or dependent class", err);
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
}
代码中的资源配置文件如下:
DispatcherServlet.properties
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
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
下面以 HandlerMapping 为例,说说 getDefaultStrategies 与 DispatcherServlet.properties 的加载关系:
默认情况下,SpringMVC 将加载当前系统中所有实现了 HandlerMapping 接口的bean。如果只期望 SpringMVC 加载指定的 HandlerMapping 时,可以修改 web.xml 中的 DispatcherServlet 的初始参数,将 detectAllHandlerMappings 的值设置为 false:
<init-param> <param-name>detectAllHandlerMappingsparam-name> <param-value>falseparam-value> init-param>
- 1
- 2
- 3
- 4
此时 Spring MVC 将查找名为 “handlerMapping” 的 bean ,并作为当前系统中唯一的 handlermapping 。如果没有定义 handlerMapping 的话,则 SpringMVC 将按照 org.Springframework.web.servlet.DispatcherServlet 所在目录下的 DispatcherServlet.properties 中所定义的 org.Springframework.web.servlet.HandlerMapping 的内容来加载默认的 handlerMapping 。
由第三节我们知道 SpringMVC 默认的 HandlerMapping 有两个:
在这里,我会分析 RequestMappingHandlerMapping ,因为平常我们使用最多的也就是通过 @RequestMapping 注解来标注映射 URL 。
先来看看 RequestMappingHandlerMapping 的类结构图:
该类我们关注两个点:
通过 Aware 接口可以获取到容器及容器中的 Bean ,通过 InitializingBean 接口,可以在该类调用构造器方法之后做些小动作(afterPropertiesSet 方法)。
那,我们来看看该类功能开始的入口把!
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet
public void afterPropertiesSet() {
// 各种配置设置
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());
// 调用AbstractHandlerMethodMapping afterPropertiesSet 方法
super.afterPropertiesSet();
}
this.config 表示生成请求映射对象的配置类,该类先对其进行了相关赋值,然后调用父类 afterPropertiesSet 方法。
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#afterPropertiesSet
public void afterPropertiesSet() {
initHandlerMethods();
}
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods
protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
// 从root容器以及子容器里,或者仅从子容器里获取所有的Bean
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
// 遍历容器里所有的Bean
for (String beanName : beanNames) {
// 忽略掉scopedTarget.打头的bean(session application request之类的作用域内的代理类)
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
// 获取Bean的Class类型
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
// 判断Class上是否有Controller注解或是RequestMapping注解
if (beanType != null && isHandler(beanType)) {
// 提取其url与controller映射关系
detectHandlerMethods(beanName);
}
}
}
// 空方法
handlerMethodsInitialized(getHandlerMethods());
}
先来看看判断方法 isHandler :
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler
protected boolean isHandler(Class<?> beanType) {
//判断类上是否存在Controller注解或是RequestMapping注解
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
接着来看看 detectHandlerMethods 方法:
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods
protected void detectHandlerMethods(final Object handler) {
// 如果handler是字符串,证明是一个beanName,则从IOC容器中获取其Class对象;否则直接获取Class对象
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 为了确保获取到的类是被代理的类
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 寻找方法上有@RequestMapping注解的Method实例
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.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
// 将获取到的Method对象依次注册到HandlerMapping中去
for (Map.Entry<Method, T> entry : methods.entrySet()) {
// 获取被AOP代理包装后的方法实例
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
}
这个方法有两个重点:
先来看看获取
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 创建方法上面的RequestMapping信息
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 创建类上面的RequestMapping信息
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 将两个信息合并
info = typeInfo.combine(info);
}
}
// 返回
return info;
}
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo(java.lang.reflect.AnnotatedElement)
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
// 如果该函数含有@RequestMapping注解,则根据其注解信息生成RequestMapping实例,
// 否则返回空
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo(org.springframework.web.bind.annotation.RequestMapping, org.springframework.web.servlet.mvc.condition.RequestCondition>)
protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {
// 这里用到了一个典型的建造者模式
RequestMappingInfo.Builder builder = RequestMappingInfo
// 这里对路径进行解析,在path中是支持SpEL表达式的,
// RequestMappingHandlerMapping实现了EmbeddedValueResolverAware这个接口
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}
让我们再回到 detectHandlerMethods 方法,当 selectMethods 方法执行完之后,Controller 方法实例和 RequestMappingInfo 的映射关系就建立起来了,并保存在 methods 这个 Map
接下来再来分析注册方法:
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register
//储存 MappingRegistration 所有的注册信息
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
//储存RequestMappingInfo 与 HandlerMethod
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
//储存路径与RequestMappingInfo
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
//储存@RequestMapping 注解的请求路径 与 HandlerMethod列表
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
//跨域配置
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
//读写锁
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 注册数据: mapping => RequestMappingInfo || handler => beanName || method => Method
* 1、根据 handle 和 method,创建 HandlerMethod,
* 2、效验 HandlerMethod 是否存在
* 3、储存 HandlerMethod
* 4、储存 RequestMappingInfo 跟 url
* 5、储存 @RequestMapping 注解 的路径跟所有的方法
* 6、存储 CorsConfiguration 信息(跨域)
* 7、储存 MappingRegistration 对象
*/
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
// 根据 handle 和 method,创建 HandlerMethod,
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//验证方法的唯一性,即先前是否已经注册过同样的映射
assertUniqueMethodMapping(handlerMethod, mapping);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);
}
// 注册RequestMappingInfo 和 HandlerMethod
this.mappingLookup.put(mapping, handlerMethod);
// 注册请求路径与对应的RequestMappingInfo
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
// 注册请求路径与HandlerMethod
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
// 注册HandlerMethod与跨域信息
this.corsLookup.put(handlerMethod, corsConfig);
}
// 创建及注册 MappingRegistation 信息
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
该方法主要干了写啥:
最终会将前面获取到的所有信息给包装起来,保存到 Map
成员变量中,后续就可以解析请求并选择合适的 Controller 方法来对请求进行处理。
到此就建立了 URL 请求和 Controller 方法的映射了。
getHandler 方法会返回一个 HandlerExecutionChain 对象,该对象包含用户请求的处理程序(handler)和拦截器(interceptors)。
public interface HandlerMapping {
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 获取handler的具体逻辑,留给子类实现
Object handler = getHandlerInternal(request);
if (handler == null) {
// 如果获取到的handler为null,则采用默认的handler,即属性defaultHandler
handler = getDefaultHandler();
}
if (handler == null) {
//如果连DefaultHandler也为空,则直接返回空
return null;
}
// Bean name or resolved handler?
// 如果handler是beanName,从容器里获取对应的bean
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 根据handler和request获取处理器链HandlerExecutionChain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
// CORS请求的处理
if (CorsUtils.isCorsRequest(request)) {
// 全局配置
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
// handler的单独配置
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
// handler的所有配置,全局配置+单独配置
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
// 根据cors配置更新HandlerExecutionChain
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
该类逻辑很简单,先获取具体的 handler ,然后通过 handler 和 request 获取我们需要的 HandlerExecutionChain 对象。
那先来看看获取 handler 相关方法。
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 获取 request 中的 url,用来匹配 handler
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock();
try {
// 根据路径寻找 Handler,并封装成 HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
// 根据 handlerMethod 中的 bean 来实例化 Handler,并添加进 HandlerMethod
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
该方法逻辑简单,先从 request 中获取请求 url ,然后通过 url 寻找对应的 HandlerMethod ,最后将找到的 handlerMethod 再次封装并返回出去。
那我们来看看,SpringMVC 是如何通过 url 寻找 handlerMethod 的。
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath,
HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 通过uri直接在注册的RequestMapping中获取对应的RequestMappingInfo列表,需要注意的是,
// 这里进行查找的方式只是通过url进行查找,但是具体哪些RequestMappingInfo是匹配的,还需要进一步过滤
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
// 对获取到的RequestMappingInfo进行进一步过滤,并且将过滤结果封装为一个Match列表
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果无法通过uri进行直接匹配,则对所有的注册的RequestMapping进行匹配,这里无法通过uri
// 匹配的情况主要有三种:
// ①在RequestMapping中定义的是PathVariable,如/user/detail/{id};
// ②在RequestMapping中定义了问号表达式,如/user/?etail;
// ③在RequestMapping中定义了*或**匹配,如/user/detail/**
addMatchingMappings(this.mappingRegistry.getMappings().keySet(),
matches, request);
}
if (!matches.isEmpty()) {
// 对匹配的结果进行排序,获取相似度最高的一个作为结果返回,这里对相似度的判断时,
// 会判断前两个是否相似度是一样的,如果是一样的,则直接抛出异常,如果不相同,
// 则直接返回最高的一个
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size()
+ " matching mapping(s) for [" + lookupPath + "] : " + matches);
}
// 获取匹配程度最高的一个匹配结果
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
// 如果匹配结果不止一个,首先会判断是否是跨域请求,如果是,
// 则返回PREFLIGHT_AMBIGUOUS_MATCH,如果不是,则会判断前两个匹配程度是否相同,
// 如果相同则抛出异常
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for"
+ " HTTP path '" + request.getRequestURL() + "': {" + m1
+ ", " + m2 + "}");
}
}
// 这里主要是对匹配结果的一个处理,主要包含对传入参数和返回的MediaType的处理
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
} else {
// 如果匹配结果是空的,则对所有注册的Mapping进行遍历,判断当前request具体是哪种情况导致
// 的无法匹配:①RequestMethod无法匹配;②Consumes无法匹配;③Produces无法匹配;
// ④Params无法匹配
return handleNoMatch(this.mappingRegistry.getMappings().keySet(),
lookupPath, request);
}
}
该方法该类写啥:
1、首先根据url在对应的匹配集directPathMatches内进行查找,如果查找不到则进行全局查找。
2、要求 bestMatch 的优先级必须大于 matches 剩余的所有的匹配条件,如果存在相同的,则抛出错误。
3、调用handleMatch进行匹配成功后的处理
4、调用handleNoMatch进行匹配失败后的处理
再来看看通过 handler 和 request 获取我们需要的 HandlerExecutionChain 对象。
org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandlerExecutionChain
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 判断handler是不是执行器链,如果不是则创建一个执行器链
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// 获取请求url
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
//包装拦截器
/**
*
*
*
*
*/
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
// MappedInterceptor根据url判断是否匹配
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
// 其它类型的Interceptor对所有请求都有效
chain.addInterceptor(interceptor);
}
}
return chain;
}
该方法将 handler 封装成一个 HandlerExecutionChain 对象,然后遍历所有的拦截器,判断 url 是否符合拦截器的规则,符合则添加到 HandlerExecutionChain 对象中,最后返回该对象。
好了,今天的内容到这里就结束了,我是 【J3】关注我,我们下期见
。
由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。
如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。
感谢您的阅读,十分欢迎并感谢您的关注。