SpringMVC源码分析_1 SpringMVC容器启动和加载原理_为什么需要加载加载springmvc配置类-程序员宅基地

技术标签: # 源码分析  SpringMVC源码分析  

                                                                SpringMVC源码分析_1 SpringMVC启动和加载原理

                                                                                         1 SpringMVC容器启动和加载原理

 

                                                                                                                                                                                                                                                                                   作者:田超凡

                                                                                                                                                                                                                                                                                   版权所有,严禁复制转载

1 SpringMVC和Servlet的关系

Servlet是Sun公司提供的一门用于开发动态web资源的技术。

  Sun公司在其API中提供了一个servlet接口(javax.servlet.Servlet),用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:

  1、编写一个Java类,实现servlet接口(继承javax.servlet.HttpServlet,重写doGet()/doPost()方法处理GET、POST请求)

2、在web.xml中配置<servlet>和<servletMapping>,指定不同Servlet拦截不同格式的请求并处理,在SpringBoot中,如果需要集成servlet,可以不用在web.xml配置<servlet>,直接使用@WebServlet注解标注自定义Servlet即可,常用的servlet映射相关配置在这个注解里面已经帮我们封装好了,在启动类标注@ServletComponentScan即可扫描并注册自定义的servlet到SpringBoot容器中

3 Servlet是线程不安全的单例模式实现,每个Servlet都有自己的生命周期,总的来说,Servlet声明周期主要包括四个阶段:加载和实例化、初始化(init)、服务(service)、销毁(destroy),每个Servlet都只会被初始化一次,每次请求都是交给service方法执行

4 常见Servlet继承关系:自定义Servlet -> HttpServlet -> GenericServlet -> Servlet

按照一种约定俗成的称呼习惯,通常我们也把实现了servlet接口的java程序,称之为Servlet

SpringMVC是Spring家族中的基于MVC Model II模式实现的一大视图层框架,SpringMVC底层核心控制器DispatcherServlet本质就是基于Servlet重新封装的,所以说,SpringMVC可以理解为是Spring对Servlet重新封装的一套功能更加齐全、使用方式更加灵活、和Spring完美契合的Web框架。

SpringMVC是依赖于Servlet容器和生命周期管理的。

 

2 Servlet核心初始化器ServletContainerInitializer

主要作用:监听Web容器启动,注册第三方组件

SpringMVC中的作用:

当Web容器启动时,注册SpringMVC核心控制器DispatcherServlet

在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册servlet或者filtes等,servlet规范中通过ServletContainerInitializer实现此功能。

每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类。

 

  1. Servlet容器启动时会扫描当前应用里面每一个jar包META-INF/services/javax.servlet.ServletContainerInitializer文件中定义的ServletContainerInitializer的实现
  2. 自定义ServletContainerInitializer的实现类,使用javax.servlet.annotation.@HandlesTypes声明
    必须绑定在META-INF/services/javax.servlet.ServletContainerInitializer中
    该文件的内容就是自定义ServletContainerInitializer实现类的全类名;

 

相关代码

@HandlesTypes(value = MyHandlesType.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {

   
/**
     * @param
set 感兴趣类型 也就是MyHandlesType 所有子类型
     * @param
servletContext
    
* @throws ServletException
     */
   
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
       
// 1.打印所有感兴趣的类型
       
for (Class<?> c : set) {
            System.
out.println(c);
        }
       
// 2.servletContext 手动注册过滤器、servlet、监听器
       
ServletRegistration.Dynamic payServlet = servletContext.addServlet("testServlet", new TestServlet());
        payServlet.addMapping(
"/test");
    }
}

 

 

 

3 基于注解方式无xml启动SpringMVC

核心思想:

  1. 监听 Web容器启动,实现WebApplicationContext接口,重写onStartup()
  2. 在监听到Web容器启动时,先初始化SpringMVC注解驱动上下文AnnotationConfigWebApplicationContext,注册SpringMVC核心配置类。
  3. 创建SpringMVC核心控制器DispatcherServlet并注册到ServletContext容器上下文中,作用域是整个SpringMVC容器全局共享

 

定义SpringMVC核心配置类,作用等效于SpringMVC核心配置文件springmvc.xml

开启SpringMVC使配置生效的方式有两种,只能任选其一,不能同时配置:

  1. 核心配置类使用@EnableWebMvc标注启用SpringMVC
  2. 核心配置类继承WebMvcConfigurationSupport

原理:

@EnableWebMvc注解中通过ImportSelector引入了DelegatingWebMvcConfiguration核心配置类,它也继承了WebMvcConfigurationSupport

所以如果核心配置类继承了WebMvcConfigurationSupport又同时标注@EnableWebMvc的话,自定义的SpringMVC配置可能会被覆盖掉,因为SpringMVC容器在加载配置类的时候,会优先加载@EnableWebMvc注解中引入的配置类中的配置项(DelegatingWebMvcConfiguration)作为SpringMVC生效的配置项

@Configuration
@EnableWebMvc
@ComponentScan
("com.mayikt.controller")
public class MyMvcConfig {
}

 

 

 

监听Web容器启动,定义DispatcherServlet初始化器,在容器启动时创建DispatcherServlet并注册到ServletContext上下文中

SpringMVC注解驱动上下文AnnotationConfigWebApplicationContext和SpringIOC上下文ApplicationContext的关系:

AnnotationConfigWebApplicationContext

  • AbstractRefreshableWebApplicationContext
  • ConfigurableWebApplicationContext
  • WebApplicationContext
  • ApplicationContext

因此总的来说,SpringMVC容器可以看做是SpringIOC容器的子容器

 

 

自定义SpringMVC初始化器,初始化SpringMVC上下文,创建核心控制器DispatcherServlet

public class WebInitializer implements WebApplicationInitializer {
   
public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException {
       
// 1.   创建SpringMVC容器
       
AnnotationConfigWebApplicationContext app = new AnnotationConfigWebApplicationContext();
       
// 2. 注册我们的配置文件
       
app.register(MyMvcConfig.class);
       
// 注册我们的
       
DispatcherServlet dispatcherServlet = new DispatcherServlet(app);
        ServletRegistration.Dynamic dynamic = servletContext.addServlet(
"dispatcherServlet", dispatcherServlet);
        dynamic.addMapping(
"/");
        dynamic.setLoadOnStartup(
1);// 最优先启动

   
}
}

 

 

 

4 SpringMVC拦截器使用

拦截器(Interceptor)与过滤器(Filter)区别

拦截器和过滤器都是基于SpringAOP实现,能够对请求执行之前和之后实现拦截。

过滤器是基于Servlet容器实现,对Web请求之前和之后实现拦截

拦截器不需要依赖于Servlet、不仅可以实现Web请求还有其他方法拦截等。

 

过滤器和拦截器的区别:

1 过滤器是Tomcat自带的,拦截器是SpringMVC自带的

2 过滤器只能拦截Web请求,过滤器不仅能够拦截Web请求,还可以拦截方法执行

3 过滤器和拦截器同时生效的情况下,过滤器会优先执行

 

  1. 自定义拦截器需要实现HandlerInterceptor拦截器接口,DispatcherServlet在拦截到请求之后会基于作用链调用HandlerInterceptor所有子拦截器的拦截方法,统一和HandlerMethod一起封装到了HandlerExecutionChain作用链中

HandlerInterceptor预定义了三种类型的拦截方法:

  1. preHandle在业务处理器处理请求之前被调用;
  2. postHandle在业务处理器处理请求执行完成后,生成视图之前执行;
  3. afterCompletion在DispatcherServlet完全处理完请求后被调用(生成视图之后执行),可用于清理资源等。

注意:afterCompletion除了作为最终拦截在请求方法执行完毕且视图渲染完毕之后执行,在前置拦截不通过、请求方法执行过程中出现异常,都会在返回响应前调用afterCompletion完成对当前请求的最终拦截处理

 

自定义Token验证拦截器UserTokenInterceptor

public class TokenInterceptor implements HandlerInterceptor {
   
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.
out.println(">>>preHandle<<<");
        String token = request.getParameter(
"token");
       
if (StringUtils.isEmpty(token)) {
            response.getWriter().print(
"not find token");
           
return false;
        }
       
return true;
    }

   
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.
out.println(">>>>>postHandle<<<<<<<<<");
    }

   
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.
out.println(">>>afterCompletion<<<");
    }
}

 

SpringMVC核心配置类中注入自定义拦截器,继承WebMvcConfigurationSupport并重写addInterceptors方法把自定义的拦截器加入到SpringMVC容器中

注意:

如果需要自定义SpringMVC配置,最好直接继承WebMvcConfigurationSupport,不要使用@EnableWebMvc注解(该注解表示使用默认的SpringMVC配置DelegatingWebMvcConfiguration来初始化SpringMVC容器),否则会覆盖自定义的配置项导致无法生效,包括拦截器配置。

@Configuration
@ComponentScan
("com.mayikt.controller")
//@EnableWebMvc
public class SpringMvcConfig extends WebMvcConfigurationSupport {
    @Bean
   
public ViewResolver viewResolver() {
        InternalResourceViewResolver internalResourceViewResolver =
new InternalResourceViewResolver();
       
// 前缀
       
internalResourceViewResolver.setPrefix("/WEB-INF/view/");
       
// 后缀
       
internalResourceViewResolver.setSuffix(".jsp");
       
return internalResourceViewResolver;
    }

   
@Bean
   
public TokenInterceptor tokenInterceptor() {
       
return new TokenInterceptor();
    }

   
/**
     *
注册拦截器
     *
     * @param
registry
    
*/
   
public void addInterceptors(InterceptorRegistry registry) {
       
super.addInterceptors(registry);
        registry.addInterceptor(tokenInterceptor()).addPathPatterns(
"/**");
    }

   
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {

    }
}

 

 

注意:使用拦截器一定要关闭@EnableWebMvc 否则拦截器不会生效。

原理:

1 @EnableWebMvc表示采用默认的SpringMVC初始化配置(DelegatingWebMvcConfiguration)来启动SpringMVC容器

2 @EnableWebMvc底层会通过@Import(DelegatingWebMvcConfiguration)加载DelegatingWebMvcConfiguration这个委托配置类

此处DelegatingWebMvcConfguration也继承了WebMvcConfigurationSupport,会覆盖掉我们自定义的SpringMVC配置,这是因为SpringIOC容器在加载配置类的时候会优先加载注解定义的基于ImportSelector引入的类

 

5 SpringMVC多线程异步处理

  1. 使用异步注解@EnableAsync
  2. Servlet上下文开启异步支持dynamic.setAsyncSupported(true);
  3. 基于Callable创建线程实现异步操作

@RequestMapping("/pay")
public String pay() {
    System.
out.println(">>>1.开始调用pay<<<<<<< ThradName:" + Thread.currentThread().getName());
   
payServie.pay();
    System.
out.println(">>>3.结束调用pay<<<<<<< ThradName:" + Thread.currentThread().getName());
   
return "pay";
}

 

 

使用异步Callable 带返回结果

@RequestMapping(value = "/asyncPay")
@ResponseBody
public Callable<String> asyncPay() {
    System.
out.println(">>>1.开始调用pay<<<<<<< ThradName:" + Thread.currentThread().getName());
    Callable callable= 
new Callable<String>() {
       
public String call() throws Exception {
           
payServie.pay();
           
return "success";
        }
    };
    System.
out.println(">>>3.结束调用pay<<<<<<< ThradName:" + Thread.currentThread().getName());
   
return callable;
}

 

dynamic.setAsyncSupported(true); 开启异步处理请求

 

6 SpringMVC容器启动和加载原理图

 

 

 

 

 

 

7 SpringMVC容器启动和加载源码分析

  1. 自定义SpringMVC容器初始化器,实现WebApplicationInitializer

WebApplicationInitializer是Web容器初始化的监听器,作用等效于ServletContainerInitializer

在监听方法onStartup中初始化SpringMVC注解驱动上下文AnnotationConfigWebApplicationContext

根据OOP继承原则,多级继承关系下构造函数的执行顺序是先执行父类构造函数,再执行子类构造函数。

此处SpringMVC注解驱动上下文AnnotationConfigWebApplicationContext继承链和构造函数执行顺序(自底向上)如下:

AnnotationConfigWebApplicationContext继承关系

  • AbstractRefreshableWebApplicationContext –2 setDisplayName()
  • AbstractRefreshableConfigApplicationContext
  • AbstractRefreshableApplicationContext
  • AbstractApplicationContext –1 PathMatchingResourcePatternResolver
  • ConfigurableApplicationContext
  • ApplicationContext

 

AbstractRefreshableWebAppliationContext实现的接口

  • ConfigurableWebApplicationContext
  • WebApplicationContext
  • ApplicationContext

 

  1. AnnotationConfigWebApplicationContext创建完成后,调用register()注册SpringMVC核心配置类,使SpringMVC核心配置生效

AnnotationConfigWebApplicationContext中的全局变量componentClasses存放的是需要在Web容器初始化时加载到SpringIOC容器中的SpringMVC核心配置类

 

 

  1. 创建SpringMVC核心控制器DispatcherServlet,将创建好的SpringMVC注解驱动上下文AnnotationConfigWebApplicationContext传入给FrameworkServlet,注入到WebApplicationContext中

 

 

 

标记当前DispatcherServlet作为统一拦截HTTP请求并调用doService()处理请求的Servlet

 

  1. 将创建好的DispatcherServlet注册到ServletContext上下文中

调用addMapping绑定拦截URL规则是默认拦截所有请求

调用setLoadOnStartup(1)标识DispatcherServlet作为优先级最高的Servlet,永远最先执行拦截。

setAsyncSupported(true)表示启用Servlet对异步处理的支持,等效于@EnableAsync

 

  1. 开始执行DispatcherServlet的初始化操作,DispatcherServlet本质就是一个Servlet,依据Servlet生命周期的定义,加载和实例化都已经执行完毕,接下来开始初始化DispatcherServlet,调用init()实现初始化

DispatcherServlet在Servlet中的继承关系如下:

DispatcherServlet

  • FrameworkServlet
  • HttpServlet
  • GenericServlet
  • Servlet

 

8 DispatcherServlet初始化流程源码分析

GenericServlet init()

 

-> HttpServletBean init()

 

-> FrameworkServlet initServletBean()

 

-> initWebApplicationContext()

(1) configureAndRefreshWebApplicationContext() 加载SpringMVC核心配置类到SpringIOC容器中

加载SpringMVC核心配置类到SpringIOC容器的实现步骤:

FrameworkServlet initWebApplicationContext()

-> configureAndRefreshWebApplicationContext()

-> AbstractApplicationContext refresh()

-> obtainFreshBeanFactory()

-> AbstractRefreshableApplicationContext refreshBeanFactory()

-> AnnotationConfigWebApplicationContext loadBeanDefinitions()

-> 将注册到AnnotationConfigWebApplicationContext中的SpringMVC核心配置类componentClasses批量加载到SpringIOC容器中

AnnotatedBeanDefinitionReader regist() / ClassPathBeanDefinitionScanner scan()

 

(2) onRefresh() -> initStrategies()初始化DispatcherServlet

 

 

 

-> DispatcherServlet onRefresh()

-> initStrategies()

 

-> HandlerMapping、HandlerAdapter、HandlerExceptionResolver、ViewResolver等DispatcherServlet处理请求流程中的核心接口和类进行初始化

注意:在初始化这些DispatcherServlet处理请求过程中的核心类和接口时,会默认先从spring-webmvc包中的DispatcherServlet.properties加载对应类型指定的所有需要被加载的类,把加载后的类存放到对应集合中,如handlerMappings、handlerAdapters、viewResolvers、handlerExceptionResolvers等

 

 

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_30056341/article/details/113488746

智能推荐

development/tools/idegen/idegen.sh  Couldn‘t find host out directory. Make sure ANDROID_HOST_OUT is_development/tools/idegen/idegen.sh失败-程序员宅基地

文章浏览阅读936次。执行$:development/tools/idegen/idegen.sh  Couldn't find host out directory. Make sure ANDROID_HOST_OUT is $:~/rom-space$ export ANDROID_HOST_OUT=out/host/$:~/rom-space$ development/tools/idegen/idegen.shRead excludes: 3msTraversed tree: 16592ms$:~/ro_development/tools/idegen/idegen.sh失败

英语文化研究-程序员宅基地

文章浏览阅读230次。世界主流媒体:华尔街日报纽约时报华盛顿邮报时代周刊美国新闻周刊泰晤士报今日美国BBC VOA1575词汇 英语语言学研究阅读500词 考的不是套路,是人的一种科学的思维模式英语语法大全: 词汇 语法 句子 短语 介词动词名词谓词主宾补..._英语文化的研究

java aes256 加密_如何基于Java 256位AES密码的加密?-程序员宅基地

文章浏览阅读534次。小编典典与带外接收者共享password(a char[])和salt 所byte[]选择的 SecureRandom- 8个字节,这是个好习惯,不需要保密)。然后从此信息中得出一个好的密钥:/* Derive the key, given password and salt. */SecretKeyFactory factory = SecretKeyFactory.getInstance("P..._secretkeyfactory factory = secretkeyfactory.getinstance("pbkdf2withhmacsha25

PHP在线小说电子书阅读系统设计与实现-程序员宅基地

文章浏览阅读1.9k次,点赞27次,收藏20次。【后台功能】系统设置:设置关于我们、联系我们、加入我们、法律声明广告管理:设置小程序首页轮播图广告和链接留言列表:所有用户留言信息列表,支持删除会员列表:查看所有注册会员信息,支持删除资讯分类:录入、修改、查看、删除资讯分类录入资讯:录入资讯标题、内容等信息管理资讯:查看已录入资讯列表,支持删除和修改资讯评论列表:所有用户的评论信息列表资讯评论管理:支持对评论信息审核,删除;审核后的信息用户才可见图书分类设置:设置有哪些科目类型录入图书:录入图书标题、选择分类、图书价格、上传图片、图书详

饲料企业精细化生产管理方案_饲料厂的管理新方案有哪些-程序员宅基地

文章浏览阅读199次。摘要:现代化的饲料企业需要用现代化的方法进行管理,只有脱去作坊型粗放化管理的外衣,才能实现工业化生产的精细管理。饲料企业的生产管理可以细化为十项日常工作,每一项工作的开展都要本着从虚到实、由小到大、由浅入深、循序渐进的原则进行。  关键词:精细化生产管理 企业管理重在创新,但凡成功的企业,其管理模式与管理方法都能够体现出鲜明的时代特色。在饲料工业坎坷而又辉煌的发展历程中,生产管理一直与精细化..._饲料厂的管理新方案有哪些

lvgl页面管理 简单实现_lvgl界面关联-程序员宅基地

文章浏览阅读4.6k次,点赞12次,收藏69次。在lvgl使用的过程中,最初的时候,肯定都会遇到这样的问题,页面之间的切换以及空间的释放。如果不合理的设计的话,一上来就将所有的页面进行初始化,那将会占用许多不必要的内存空间,硬写之间的切换的话,界面一旦多起来,那切换逻辑将会绕来绕去,最终会写不下去。所以合理设计一个页面管理函数或者说是对象将会非常重要。欢迎关注 !!!!!!!基于上面三点就可以完成一个基本的页面管理对象了,是不是非常简单。页面管理对象的实现,页面管理对象主要是为了方便页面之间的切换以及内存的释放(即删除没有加载在界面上页上的资源)。_lvgl界面关联

随便推点

Linux查看GPIO状态的方法_gpio-385 (? ) out hi 显示问号-程序员宅基地

文章浏览阅读3.3k次。$ cat /sys/kernel/debug/gpio GPIOs 0-287, platform/1c20800.pinctrl, 1c20800.pinctrl: gpio-37 (? ) out hi gpio-38 (? ) in lo gpio-39 (? ) in lo gpio-40 (? ) out hi gpio-41 ._gpio-385 (? ) out hi 显示问号

hisi3520dv300上alc5616驱动笔记_alc5616 liux驱动-程序员宅基地

文章浏览阅读2.6k次。alc5616_i2c_write(ADDR_ALC5616, 0xFA, 0x11); // reg = alc5616_i2c_read(ADDR_ALC5616, 0xFA); if (reg != 0x11) { alc5616_i2c_write(ADDR_ALC5616, 0xFA, 0x11); } reg = ..._alc5616 liux驱动

模型 框架效应-程序员宅基地

文章浏览阅读901次,点赞29次,收藏20次。框架效应(Framing Effect)是心理学和行为经济学中的一个重要概念,它描述了人们在面对相同的问题或决策时,由于问题呈现方式的不同(即“框架”不同),而做出不同选择的现象。这个效应揭示了人们在做决策时并非总是理性的,而是受到信息呈现方式的影响。框架效应的概念最早由心理学家阿莫斯·特沃斯基(Amos Tversky)和丹尼尔·卡尼曼(Daniel Kahneman)于1981年提出。他们通过研究人们在面对风险决策时的行为,发现问题的呈现方式(框架)对人们的选择有显著影响。

[附源码]JAVA+ssm计算机毕业设计高校教材管理系统(程序+Lw)_基于ssm的教材管理平台-程序员宅基地

文章浏览阅读195次。作为一名管理员,可以看到学生和教师能看到的一切内容,而且还可以进行一系列的操作,管理员是权限最大的,无论哪个模块它都可以控制,包括人员的新增和删除,信息的增删改,并且还可以审核用户提出的问题,当然也可以删除一些不正当的语言。模式中的缺限,去解决其中的不足等,通过对本系统,不仅能使工作量不断地减少,还能使工作和管理的效率更加高。当你成为教师后,进行注册、登录,教师账号、密码都正确的时候就可以成功登录,此时进入页面,可以修改个人信息,对教材信息、教材订购、教材订阅、公告信息等内容进行添加、删除、审核等操作。_基于ssm的教材管理平台

FFMPeg代码分析:AVPacket结构体和av_read_frame函数-程序员宅基地

文章浏览阅读3k次。转自:http://blog.csdn.net/shaqoneal/article/details/16960927AVPacket结构用于保存压缩编码过的数据。在解码时,该结构的实例通常作为解复用器(demuxer)的输出并输入到解码器中;在编码时,通常是编码器的输出,并输入到复用器(muxer)中。该结构体的定义如下:[cpp] view plaincopy_av_read_frame

夜神模拟器抓包微信小程序(进入浏览器,弹出安全警告(安全证书有问题解决方法)_夜神模拟器用证书代理出现问题-程序员宅基地

文章浏览阅读4.8k次,点赞3次,收藏11次。如图标识(我把文件直接放到夜神模拟器的安装目录,也就是not_adb.exe程序目录下,是为了执行下方adb push 9a5ba575.0 /system/etc/security/cacerts/ //将证书放到系统证书目录,不报找不到文件的错)然后打开模拟器,把开发者模式打开,先打开设置,拉到最下面有一个“关于平板电脑”,点击进入。然后进入开发者选项,打开usb调试。adb push 9a5ba575.0 /system/etc/security/cacerts/ //将证书放到系统证书目录。_夜神模拟器用证书代理出现问题

推荐文章

热门文章

相关标签