前言
本文是我粗略看了 SpringMVC 源码之后的一篇总结,对之前解析的一个提炼,为以后再刷源码提供一个参考思路,可能有错漏的地方,请大家指正交流。
我试着从源码
和一条 request 的请求执行路径
去解析 springmvc 的原理,并且稍微涉及一点点设计模式的相关概念。
前提
观看本文最好具备一定的 spring 源码基础,因为我可能会略过一些在我看来大家都知道的细节部分,因为追踪源码太复杂,会导致篇幅过长。
知识点
- springmvc 有 2 个spring容器,我们在学习 springioc 的时候可能稍微接触过父子容器的概念。
这里祭出 springmvc 的架构图,root 根容器包含了services、repository;而子容器才是视图和控制器
分析
Demo
还是从 demo 开始,我想大家比较早接触 spring 差不多应该是从 springmvc 开始的,那个时候我们搭建一个 springmvc 项目需要一些配置文件,配置 beans 和扫包,事务,等等组件。然后会修改一下web.xml
这个文件,我们一般会配置下ContextLoaderListener
和DispatcherServlet
部分。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
我们总是听说什么 SpringMVC 是基于 DispatcherServlet 的,DispatcherServlet
是核心巴拉巴拉的,但是为什么是核心呢?
还有为什么要在 web.xml 这里配置这些玩意?有什么作用呢?
本篇我会解答这些疑问
演示代码
我这里先不写传统的 springmvc 的项目配置,而是采用零配置文件的方式,配置一个 springmvc 的环境,然后用 tomcat 跑起来。我就依据这个项目来做源码分析,并解答上面的疑问。可能有些人觉得不可思议,springmvc 也可以做到零配置吗?不需要配置文件的不就是 springboot 了吗?
利用 servlet3.0 和 spi 机制,我们在 jdk6 和 tomcat7 之后,在 web 项目中不需要加入 web.xml 配置文件了。至于 spi 机制是什么,这里不过多细节讲解,我们在 demo 中捎带说下。我们看起来高端的 springboot 其实就是零配置的 springmvc 的更高级组合,它把一些常用的配置全部做了默认实施,所以我们可以拿来即用,但是底层其实还是同一套东西的。
废话不多说,上代码
项目结构(我是直接在 spring 源码项目中创建的测试工程,为了方便对源码进行注释):
jar包:
plugins {
id 'java'
}
apply plugin: 'war'
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
optional("javax.servlet:javax.servlet-api:4.0.1")
compile(project(":spring-webmvc"))
compile(project(":spring-context"))
}
我们的启动类,继承AbstractAnnotationConfigDispatcherServletInitializer
,
public class StarterClass extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
/**
* 获取 root 容器配置
*/
return new Class<?>[]{SpringRootApplicationContext.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
/**
* 获取 springmvc 容器配置
*/
return new Class<?>[]{SpringMvcApplicationContext.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
2 个配置文件代码,没有特别内容,主要是扫包,注意我这里标注的扫包位置和类的名称的区别
@Configuration
@ComponentScan(value = {"top.ybq87.controller"})
public class SpringMvcApplicationContext {
}
@Configuration
@ComponentScan(value = {"top.ybq87.service"})
public class SpringRootApplicationContext {
}
3 个 controller 作用的类
@Controller
public class HelloController {
@Autowired
private UserService userService;
@ResponseBody
@RequestMapping("/hello")
public Object hello(String name) {
return userService.say(name);
}
}
@Component("/myBeanNameController")
public class MyBeanNameController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
System.out.println(">>>>>MyBeanNameController");
return null;
}
}
@Component("/myHttpRequest")
public class MyHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
System.out.println(">>>>>> MyHttpRequestHandler >>>>>>");
}
}
然后是接口和实现类,很简单
public interface UserService {
String say(String name);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public String say(String name) {
return "UserServiceImpl >> " + name;
}
}
好了,测试代码已经完毕,我们使用 tomcat7+跑起项目来,注意我这里可是一个配置文件都没有写的。
访问成功。开工
容器初始化
我们从 StarterClass 这个文件开始分析,我这里继承了AbstractAnnotationConfigDispatcherServletInitializer
,可能大家再看其他的源码或者零配置教程文章的时候都是继承的WebApplicationInitializer
,包括官网都给的也是这个例子。因为前者经过了一些封装,我们不需要写那么复杂的配置,所以用起来更加简单,其实底层一样,不深究。
稍微涉及一点 tomcat 的启动原理,tomcat 容器启动的时候的加载逻辑:
org.apache.catalina.startup.ContextConfig#lifecycleEvent
org.apache.catalina.startup.ContextConfig#configureStart
org.apache.catalina.startup.ContextConfig#webConfig
org.apache.catalina.startup.ContextConfig#processServletContainerInitializers
org.apache.catalina.startup.WebappServiceLoader#load
查看 tomcat 源码:在 IDEA 编辑器双击 shift,贴入org.apache.catalina.startup.WebappServiceLoader,会自动找到这个类
这个方法我列几个比较重要的代码,不展开
// serviceType = ServletContainerInitializer,此参数由上级方法传入
// SERVICES = META-INF/services/
String configFile = SERVICES + serviceType.getName();
// 找到指定文件
WebResource[] resources = context.getResources().getClassLoaderResources("/" + configFile);
这个代码的意思就是从目录 META-INF/services/javax.servlet.ServletContainerInitializer
找到文件然后加载里面的 class。
不过我们回看自己的 demo 没有发现这个文件,但是使用 IDEA 的快捷搜索双击 shift,我们发现这个文件在spring-web
项目下。哦,原来我们在引入包的时候已经加载进来了。
这个文件里面只有一条数据org.springframework.web.SpringServletContainerInitializer
,看到了全限定类名,自然想到了反射去实例化,我们看看这个类的具体实现
SpringServletContainerInitializer
tomcat 启动时,会依据 SPI 机制找到项目下的这个类,然后调用它的 onStartup()
方法,但是 spring-web帮我们实现了这个机制,并且做了一个拓展。
/**
* HandlesTypes 注解含义:会扫描所有 实现了 value=WebApplicationInitializer 的接口实现类,组装为 set 参数,
* 传递给 SpringServletContainerInitializer#onStartup(java.util.Set, javax.servlet.ServletContext)
*/
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
// webAppInitializerClasses 这个参数我们知道什么意思了,不展开了。
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
/**
* 循环 WebApplicationInitializer 的实现类,然后调用这个类的 onStartup 方法
*/
for (Class<?> waiClass : webAppInitializerClasses) {
/**
* 不是接口,不是抽象类,而且实现了 WebApplicationInitializer 接口的
*/
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
/**
* 将通过判定的加入到 initializers,为了在后面依次调用它的 onStartup 方法
*/
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,实现了 order 接口或者使用了 @Order 注解的
*/
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
/**
* 调用它的 onStartup 方法,指向我们的 top.ybq87.StarterClass
* 但是我们自己没有实现 onStartup 方法,所以去看他的父类找
*/
initializer.onStartup(servletContext);
}
}
}
看到这里我们知道了,回去找我们的启动类的父级,我们找到了AbstractDispatcherServletInitializer#onStartup
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 调用的父类,实例化我们的 spring root 上下文,只是创建,没有初始化
super.onStartup(servletContext);
// 注册我们的 DispatcherServlet 创建我们 spring web 上下文对象
registerDispatcherServlet(servletContext);
}
第一行代码调用了 super,又去找了它的父级AbstractContextLoaderInitializer#onStartup
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
/**
* 1、新建我们的 父容器 RootApplicationContext
* 当前类没有发现实现方法,说明交给了子类 : AbstractAnnotationConfigDispatcherServletInitializer#createRootApplicationContext()
* 注意到这个方法之后,只是 new 了一个根容器,但是没有初始化【即没有调用 refresh()】
*/
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
/**
* 2、创建一个 ContextLoaderListener ,这个是不是超级熟悉,我们以前在使用配置文件启动 springmvc 的时候
* 经常在 web.xml 中配置的 ContextLoaderListener
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param>
*
* 创建一个监听器对象, 然后将监听器注册到 servlet 上下文,也就是注册到 tomcat
* 然后他持有一个 web 容器的引用【目前是空的】
* 我们回到 super.onStartup(servletContext)
*/
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
原来这里是用来创建 springmvc 中 2 个容器的根容器的,只不过我们看到的创建都是交给子类实现,目前还没有具体方法。我们先记下来。
分析第二行代码
/**
* 我们一般的配置信息
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
*/
protected void registerDispatcherServlet(ServletContext servletContext) {
// 获取 dispatcherservlet 的名称
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
/**
* 创建 WebApplicationContext 对象,交给子类实现
* AbstractAnnotationConfigDispatcherServletInitializer#createServletApplicationContext()
* 实例化 子容器
*/
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
/**
* 创建 DispatcherServlet 对象,所以 tomcat 会对 DispatcherServlet 进行生命周期管理
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
* 我们进去发现只是设置关联子容器,没有更多操作。
*/
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
/**
* 获取 ServletApplicationContextInitializers 对象,注册到 dispatcherServlet
*/
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
/**
* 将 DispatcherServlet 注册到 tomcat
*/
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
/**
* 眼熟吧,设置 dispatcherServlet 属性
*/
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
这里就是创建子容器的地方了,但是也是交给子类实现。
看了这些源码我想你可能还是有点懵,我这里提醒大家关注 2 个方法,也就是流出来给子类拓展的createRootApplicationContext()
和createServletApplicationContext()
AbstractAnnotationConfigDispatcherServletInitializer
上面 2 个方法都是 AbstractAnnotationConfigDispatcherServletInitializer
类的,我们看看实现。
createRootApplicationContext()
protected WebApplicationContext createRootApplicationContext() {
// 获取父容器的配置类,抽象方法,所以是交给子类实现,也就是 top.ybq87.StarterClass.getRootConfigClasses
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
// 创建我们的根容器
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// 把配置类加载到根容器中
context.register(configClasses);
return context;
}
else {
return null;
}
}
看到这里终于和我们的 demo 代码有关联了,我们的配置是这么写的,还记得开篇的那张图么。
@Configuration
@ComponentScan(value = {"top.ybq87.service"})
public class SpringRootApplicationContext {
}
但是我们看完整个代码,发现这里就真的只是实例化了一个AnnotationConfigWebApplicationContext
类,并初始化了一些参数(获取到根容器的配置文件位置)没有别的操作了。
慌!!
createServletApplicationContext()
再看看另外一个类,子容器的创建如何
@Override
protected WebApplicationContext createServletApplicationContext() {
// 和我们实例化父容器一样的操作
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
摔!居然和父容器一样的创建过程。
ContextLoaderListener
遇到困难不要怕,我们灵光一闪,记得有 2 个重要的方法被忽略了,
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
这一步在 tomcat 的上下文环境注册了一个 listener!
还记得JavaWeb 三大组件中 Listener 的相关特性么?
在 servlet 容器的创建时调用监听器的 void contextInitialized(ServletContextEvent sce)
我们去看看ContextLoaderListener的这个方法
@Override
public void contextInitialized(ServletContextEvent event) {
/**
* 初始化容器
*/
initWebApplicationContext(event.getServletContext());
}
原来如此,在 servlet 容器启动的时候,因为之前注册了一个根容器的监听器,在配置信息都加载完成之后,servlet 容器启动了调用监听的 init 方法,此时去真正初始化根容器。
跟踪到ContextLoader#initWebApplicationContext
,毫无疑问,这里进行了spring 容器的初始化。
DispatcherServlet
同理分析子容器的初始化,
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
/**
* 获取 ServletApplicationContextInitializers 对象,注册到 dispatcherServlet
*/
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
/**
* 将 DispatcherServlet 注册到 tomcat
*/
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
这段代码构建了一个FrameworkServlet
实例,然后注册到了 tomcat 的上下文。
servlet 组件的生命周期:构建时调用 init 方法,接收消息(一个 request调用一次)调用 service 方法,销毁调用 destory 方法
我们直接看FrameworkServlet
,发现它是个抽象类,看父类HttpServletBean#init
但是还是回到了子类的FrameworkServlet#initServletBean
。
try {
/**
* 初始化 web 容器
*/
this.webApplicationContext = initWebApplicationContext();
// 拓展点,交给开发人员在初始化之后做点事情
initFrameworkServlet();
}
FrameworkServlet#initWebApplicationContext
方法
protected WebApplicationContext initWebApplicationContext() {
/**
* 获取 AnnotationConfigServletWebServerApplicationContext 类型的web容器
* 即得到父容器 rootContext 是 WebApplicationContext 类型的,
* 记得之前在父容器的初始化时,将父容器注册到了 servlet应用的上下文么,所以这里可以得到父容器
*/
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);
}
/**
* 1、刷新 springmvc 的上下文,即初始化子容器,
* 2、触发一个 spring 的监听事件
*/
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
/**
* 在子容器初始化之后,触发了 spring 的 ApplicationListener 事件,调用
* FrameworkServlet.ContextRefreshListener#onApplicationEvent
* 回去修改 refreshEventReceived = true,这里再次判定一下,避免事件没有触发。
*/
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
/**
* 刷新容器,交给了子类实现,进入 DispatcherServlet
*/
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
// 事件推送
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
我们看注释,发现最关键的部分还是交给了子类实现onRefresh
/**
* 用于初始化我们 springmvc 的九大组件
*/
protected void initStrategies(ApplicationContext context) {
/**
* 初始化 web 上下文对象的 用于文件上传下载的解析器对象
* 处理 multipart-form-data 类型请求
*/
initMultipartResolver(context);
// 初始化 国际化资源
initLocaleResolver(context);
// 初始化 主题解析器
initThemeResolver(context);
/**
* 初始化 handlermappings,handlerMapper 存储的是 uri 对应的 Controller 处理类(或者具体方法)。
* 当 DispatcherServlet 接受到客户端的请求后,SpringMVC 通过 uri 在 handlermappings 定位到 Controller 处理类(或者具体方法)
*/
initHandlerMappings(context);
/**
* 初始化 handleradapters,我们拿到了 controller 就要调用它的对应的方法。adapter 负责方法调用。
*/
initHandlerAdapters(context);
// 初始化 异常处理器
initHandlerExceptionResolvers(context);
//
initRequestToViewNameTranslator(context);
/**
* view 解析器
*/
initViewResolvers(context);
initFlashMapManager(context);
}
到这里我们才真正的接触到了 springmvc 的核心部分。
DispatcherServlet
篇幅有限,只分析我们重点看的 2 个方法。
initHandlerMappings
先看注册 HandlerMapping 的方法,从容器找到 HeandlerMapping 的实现类,并初始化这些类。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
/**
* 是否允许查找所有的 handler。默认 true
*/
if (this.detectAllHandlerMappings) {
// 从容器查询所有的 HandlerMapping 的实现类
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
// 找到了
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
// 排序,spring 处理请求就是依据这个排序的结果进行,如果当前handlerMapping不可以处理则抛给下一个
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.
/**
* 确保有至少有一个 HandlerMapping,如果前面没有找到就使用默认的 HandlerMapping
*/
if (this.handlerMappings == null) {
/**
* 没有使用 @EnableWebMvc 注解,进入默认方法,得到了 3 个默认的 HandlerMapping
* BeanNameUrlHandlerMapping(实现接口的 controller)
* RequestMappingHandlerMapping(基于注解的 controller)
* RouterFunctionMapping(Spring5.0 的 WebFlux 加入的,先忽略)
*/
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
我们的项目没有使用到 @EnableWebMvc 注解,所以是加载的默认 HandlerMapping,我们关注的是其中的 2 个BeanNameUrlHandlerMapping
和RequestMappingHandlerMapping
。
在上面的代码中我们看到DispatcherServlet#getDefaultStrategies
这个方法的一些关键代码
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
createDefaultStrategy
这个方法的实现
protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
return context.getAutowireCapableBeanFactory().createBean(clazz);
}
这里我们似乎看到了在学习 springioc 源码的时候经常见到的 createBean,初始化 bean 的工作。也就是会去初始化RequestMappingHandlerMapping
和BeanNameUrlHandlerMapping
RequestMappingHandlerMapping
先看RequestMappingHandlerMapping
这个类,找一些我们熟悉的东西。
// 这个方法在 springioc 源码分析的时候应该看到过,在 bean 属性赋值之后调用。
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager());
/**
* 查看父类的方法
*/
super.afterPropertiesSet();
}
追踪到父类,眼熟啊
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* 该方法就是去把我们的 Controller 中的 RequestMapping 注解的路径 URI 和方法进行一一映射保存
* 比如我们的 demo 中的 HelloController 的 uri 映射就是:
* key = /hello, value = public Object hello(String name) 这个方法的包装类 HandlerMethod
*/
protected void initHandlerMethods() {
/**
* 去 web 容器中获取出所有组件的 beanNames 获取出来
*/
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 关键方法
processCandidateBean(beanName);
}
}
//
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
/**
* 通过 beanName 去我们的web容器中获取 beanType(class对象)
*/
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);
}
}
// isHandler 判定条件:是否有 Controller 或者 RequestMapping 注解
if (beanType != null && isHandler(beanType)) {
/**
* 通过 Class 对象判断是不是一个 controller 对象
* 判断类上面有没有 @Controller || @RequestMapping 注解
*/
detectHandlerMethods(beanName);
}
}
protected void detectHandlerMethods(Object handler) {
/**
* 判定 传入的 handler 是否是 beanName
* 1、是:通过 beanName 从web 容器中获取 beanName 对应的 bean 的 class 对象
* 2、不是:直接获取 handler 的 class 对象
* 从我们之前的代码看这里是 string 类型的 beanName
*/
Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
/**
* 获取目标的class对象,防止class对象被cglib增强的
*/
Class<?> userType = ClassUtils.getUserClass(handlerType);
/**
* 作用:把我们的Controller 中标注的 @RequestMapping 的方法对象做为key,配置的路径作为value
* 设置到Map对象中
*
* 在这里使用的拉姆达表示式,把getMappingForMethod(method,userType)的方法注入到了
* MethodIntrospector.MetadataLookup接口中的inspect方法中
* 那么真正的调用inspect()方法的时候就会调用getMappingForMethod方法
*/
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));
}
/**
* 循环我们的上一步解析的map,把method---path 的映射关系保存到
* MappingRegistry 对象中.
*/
methods.forEach((method, mapping) -> {
/**
* 解析map中的key(method)对象
* 获取method对象是不是一个可执行的 method 对象
*/
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
/**
* 把我们的映射关系保存到 MappingRegistry 中,一路追踪这个方法,
* AbstractHandlerMethodMapping.MappingRegistry#register
* 一个写锁方法
*/
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
简单看下registerHandlerMethod
方法调用AbstractHandlerMethodMapping.MappingRegistry#register
的代码
this.readWriteLock.writeLock().lock();
try {
/**
* 根据 controller 对象和被调用的 method 对象 来创建我们的 HandlerMethod
*/
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
/**
* 把我们的 url,和 handlerMethod 保存到 mappingLookup map 中
* mappingLookup<RequestMappingInfo,HandlerMethod>
*/
this.mappingLookup.put(mapping, handlerMethod);
// 从 RequestMapping 注解得到 value 的值,因为是数组
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
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);
}
// 映射表注册 MappingRegistration 对象
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
} finally {
this.readWriteLock.writeLock().unlock();
}
这里我们就知道了,原来是将我们的 controller 的具体方法和 uri 进行了映射。
BeanNameUrlHandlerMapping
再来看 BeanNameUrlHandlerMapping 这个类的初始化,直接告诉你追踪这个AbstractDetectingUrlHandlerMapping#detectHandlers
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
for (String beanName : beanNames) {
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
// 注册 handler
registerHandler(urls, beanName);
}
}
if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
}
}
我们追踪到这里只是简单的将 uri 和对应的 BeanName 映射,存储到了AbstractUrlHandlerMapping#handlerMap
这个 map 中。
好了经过一些列的判定,终于我们得到了 3 个 HandlerMapping 类型的实例,并且分别存储了 uri 到 method(类)的映射关系。
到目前为止我们大概知道了一个 uri 请求过来之后,应该可以找到对应的处理类(方法),但是怎么调用方法还没看到。
initHandlerAdapters
再看 HandlerAdapter 的实现。我简化下,补贴代码了,分析的方法和上面的差不多,
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
这个方法找到了 4 个适配器,我们忽略 webflux 的,应该是关心其中的 3 个:
- HttpRequestHandlerAdapter
- SimpleControllerHandlerAdapter
- RequestMappingHandlerAdapter
我们知道在 spring 中实现一个 controller 的方式有 2 大类(实现接口或者注解),3 种方式(实现 Controller 接口、实现 HttpRequestHandler 接口、使用@Controller 注解)与这里的适配器对应。
老铁们看到这里应该想起了适配器模式了,先不展开,留个扣子。
这里注册了 3 个适配器。
总结下 DispatcherServlet 的 init
分析到这里我们掌握的信息看,dispatcherservlet 在初始化的时候,分解得到了 uri 和方法的对应关系,然后还有注册了几个适配器。实际的应用是怎么生效的看下面。
一条请求的执行路径
我们以http://localhost:8080/hello
这个请求为例分析,Servlet 处理请求一般走 service,我们这里就对应的是DispatcherServlet#doDispatch
这个方法。
摘取一些重要逻辑代码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
/**
* 1、从我们当前的请求中推断出我们的 HandlerExecuteChain 处理器执行链对象
* 重点!!
* 经过这一步,通过 uri 获取到一个对应的 controller 的 method 方法
* (或者是一个类对象,因为注册 controller 的方式有 3 种,但是我们最常用的是用 @RequestMapping 注册 uri)
* 同时在 处理链加入了 适配的拦截器。还记得我们在分析 HandlerMapping 的时候的结果么按照 demo 中 HandlerMapping 的结构应该是:
* key = /hello, value = top.ybq87.controller.HelloController#hello
* key = /myBeanNameController, value = myBeanNameController
* key = /myHttpRequest, value = myHttpRequestHandler
* 后面 2 个的值是类,第一个是方法
*/
mappedHandler = getHandler(processedRequest);
...
/**
* 2、获取对应的 handlerAdapter 适配器,最终目的是要调用被适配的 handler 方法
*/
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
/**
* 3、调用 handle,执行请求
* 通过我们的适配器真正的调用我们的目标方法
* 因为 controller 的定义方式有多种,所以对应的方法调用方式需要做适配。
*/
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
标注的方法我们一个一个的分析
getHandler方法
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
/**
* 遍历所有的 mapping,得到对应的处理类,
*/
for (HandlerMapping mapping : this.handlerMappings) {
// 重点,我们系统一共有 3 个 HandlerMappings,其中一个是 webflux 忽略。
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
找到AbstractHandlerMapping#getHandler
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
/**
* 1、调用具体的实现去获取 handler(可以看成是 controller),具体类是 HandlerMethod
* AbstractUrlHandlerMapping#getHandlerInternal
* uri 得到 method 处理类
*/
Object handler = getHandlerInternal(request);
...
/**
* 通过 uri,去配置的拦截器 interceptor 看看是否有匹配的拦截器,加入处理链
*/
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
...
return executionChain;
}
getHandlerInternal
这里会调用子类的实现,具体对应的有 2 个:AbstractUrlHandlerMapping#getHandlerInternal
和RequestMappingInfoHandlerMapping#getHandlerInternal
。
因为我们分析的是/hello
这个请求,所以我们知道它会去找到RequestMappingInfoHandlerMapping#getHandlerInternal
。
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
/**
* 获取UrlPathHelper对象,用于来解析从们的request中解析出请求映射路径
*/
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
/**
* 通过我们从Request对象中解析出来的lookupPath 然后通过lookupPath获取HandlerMethod对象
*/
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
} finally {
this.mappingRegistry.releaseReadLock();
}
}
原来这里就通过 uri 得到了我们之前包装的 HandlerMethod 对象。
getHandler这个方法返回了 HandlerMethod 和一些默认的过滤器,(当然如果我们分析的是/myBeanNameController
这个请求,那么这里就不是 HandlerMethod 了,而是MyBeanNameController
这个类,老铁们能转过弯来么)
getHandlerAdapter方法
上一步得到了 HandlerMethod,我们知道了具体的方法,但是怎么调用?这里就用到了适配器模式,因为 Handler可能是一个 method 对象,也可能是一个 class,而且他们之间没有任何关联。作为 client 的 DispatcherServlet 想要调用这些 Target 端,就必须通过适配器。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
/**
* 依据 Handler 的类型【
* HandlerMethod(AbstractHandlerMethodAdapter)、
* 自定义 MyBeanNameController 的类型(SimpleControllerHandlerAdapter)、
* 自定义 MyHttpRequestHandler 类(HttpRequestHandlerAdapter)
* 】 判断找到合适的 adapter
* 1、AbstractHandlerMethodAdapter 中会去判定是否是 HandlerMethod 对象,
* 【那么什么情况下这个 handler 是一个 HandlerMethod?就是使用注解注册我们的 uri 的时候,会追踪到具体的 method,
* 所以在 HandlerMapping 中存储的是 HandlerMethod 对象】
* 2、HttpRequestHandler 实现了HttpRequestHandler接口的类,那么就判定是否是HttpRequestHandler这个类型
* 因为这个 uri 对应的是一个类
* 3、SimpleControllerHandlerAdapter,实现了 Controller 接口的
*/
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
这里我们就找到了AbstractHandlerMethodAdapter
这个适配器。
ha.handle方法
client 调用适配器的方法,实际由适配器调用具体的 target 的方法。这里就是AbstractHandlerMethodAdapter#handle
指向RequestMappingHandlerAdapter#handleInternal
追踪下去就是利用反射机制调用了对应的方法。
当然如果是SimpleControllerHandlerAdapter
这种就简单很多,直接调用类的方法。
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 转为接口
return ((Controller) handler).handleRequest(request, response);
}
后面返回处理的视图部分代码就不贴了。
总结
分析下来,其实 springmvc 的逻辑并不算太复杂,有几个比较有意思的点
- 它充分利用了 listener 和 servlet 的特性进行根容器和子容器的初始化工作
- 在处理请求时使用了策略模式和适配器模式,很好的兼容旧版的方法,也便于拓展
最后来个流程图
欢迎转发和关注
公众号:林子曰
开源项目:git@github.com:Lingouzi/blog.git