• SpringMvc请求原理流程


    springmvc是用户和服务沟通的桥梁,官网提供了springmvc的全面使用和解释:DispatcherServlet :: Spring Framework

    流程

    1.Tomcat启动

    2.解析web.xml文件,根据servlet-class找到DispatcherServlet,根据init-param来获取spring的配置文件,spring的配置文件配置的主要内容就是参数的扫描路径(扫描Bean)和自定义Bean

    1. <servlet>
    2. <servlet-name>mvc_shine_aaservlet-name>
    3. <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    4. <init-param>
    5. <param-name>contextConfigLocationparam-name>
    6. <param-value>classpath:mvc.xmlparam-value>
    7. init-param>
    8. <load-on-startup>1load-on-startup>
    9. servlet>
    10. <servlet-mapping>
    11. <servlet-name>mvc_shine_aaservlet-name>
    12. <url-pattern>/url-pattern>
    13. servlet-mapping>

    mvc.xml文件

    1. <context:component-scan base-package="com.qf.web"/>
    2. <bean class="com.yuyu.AService">
    3. <property name="id" value="2">property>
    4. bean>

    3.创建DispatcherServlet实例,创建实例后会执行父类(FrameworkServlet)的父类(HttpServletBean)的init方法,HttpServletBean 在执行init()方法最后会执行initServletBean()方法,该方法在HttpServletBean是一个空方法,目的是让继承他的子类自己去完成接下来的初始化操作。

    1. @Override
    2. public final void init() throws ServletException {
    3. // Set bean properties from init parameters.
    4. // 进行一些初始化操作
    5. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    6. if (!pvs.isEmpty()) {
    7. try {
    8. BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    9. ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
    10. bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
    11. initBeanWrapper(bw);
    12. bw.setPropertyValues(pvs, true);
    13. }
    14. catch (BeansException ex) {
    15. if (logger.isErrorEnabled()) {
    16. logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
    17. }
    18. throw ex;
    19. }
    20. }
    21. // Let subclasses do whatever initialization they like.
    22. // 核心代码,将剩余的初始化操作交给子类自己去实现
    23. initServletBean();
    24. }

    接下来程序就走到了FrameworkServlet的initServletBean()方法,

    1. @Override
    2. protected final void initServletBean() throws ServletException {
    3. getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    4. if (logger.isInfoEnabled()) {
    5. logger.info("Initializing Servlet '" + getServletName() + "'");
    6. }
    7. long startTime = System.currentTimeMillis();
    8. try {
    9. // 核心代码
    10. this.webApplicationContext = initWebApplicationContext();
    11. initFrameworkServlet();
    12. }
    13. catch (ServletException | RuntimeException ex) {
    14. logger.error("Context initialization failed", ex);
    15. throw ex;
    16. }
    17. if (logger.isDebugEnabled()) {
    18. String value = this.enableLoggingRequestDetails ?
    19. "shown which may lead to unsafe logging of potentially sensitive data" :
    20. "masked to prevent unsafe logging of potentially sensitive data";
    21. logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
    22. "': request parameters and headers will be " + value);
    23. }
    24. if (logger.isInfoEnabled()) {
    25. logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    26. }
    27. }

    该方法的核心就是

    this.webApplicationContext = initWebApplicationContext();

    这一步就是创建一个spring容器,该方法首先会尝试获取父容器,然后判断之前是否有创建过webApplicationContext容器。

    1. protected WebApplicationContext initWebApplicationContext() {
    2. WebApplicationContext rootContext =
    3. WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    4. WebApplicationContext wac = null;
    5. if (this.webApplicationContext != null) {
    6. // A context instance was injected at construction time -> use it
    7. wac = this.webApplicationContext;
    8. if (wac instanceof ConfigurableWebApplicationContext) {
    9. ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
    10. if (!cwac.isActive()) {
    11. // The context has not yet been refreshed -> provide services such as
    12. // setting the parent context, setting the application context id, etc
    13. if (cwac.getParent() == null) {
    14. // The context instance was injected without an explicit parent -> set
    15. // the root application context (if any; may be null) as the parent
    16. cwac.setParent(rootContext);
    17. }
    18. configureAndRefreshWebApplicationContext(cwac);
    19. }
    20. }
    21. }
    22. if (wac == null) {
    23. // No context instance was injected at construction time -> see if one
    24. // has been registered in the servlet context. If one exists, it is assumed
    25. // that the parent context (if any) has already been set and that the
    26. // user has performed any initialization such as setting the context id
    27. wac = findWebApplicationContext();
    28. }
    29. if (wac == null) {
    30. // 创建本地实例
    31. // No context instance is defined for this servlet -> create a local one
    32. wac = createWebApplicationContext(rootContext);
    33. }
    34. if (!this.refreshEventReceived) {
    35. // Either the context is not a ConfigurableApplicationContext with refresh
    36. // support or the context injected at construction time had already been
    37. // refreshed -> trigger initial onRefresh manually here.
    38. synchronized (this.onRefreshMonitor) {
    39. onRefresh(wac);
    40. }
    41. }
    42. if (this.publishContext) {
    43. // Publish the context as a servlet context attribute.
    44. String attrName = getServletContextAttributeName();
    45. getServletContext().setAttribute(attrName, wac);
    46. }
    47. return wac;
    48. }

    如果之前没有创建容器,程序会走到

            if (wac == null) {
                // 核心代码,创建本地容器实例
                // No context instance is defined for this servlet -> create a local one
                wac = createWebApplicationContext(rootContext);
            }

    通过createWebApplicationContext(rootContext),之后就会成功创建并初始化一个spring容器了

    1. protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    2. Class contextClass = getContextClass();
    3. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    4. throw new ApplicationContextException(
    5. "Fatal initialization error in servlet with name '" + getServletName() +
    6. "': custom WebApplicationContext class [" + contextClass.getName() +
    7. "] is not of type ConfigurableWebApplicationContext");
    8. }
    9. // 创建容器实例,里面的属性都是空的 等待填充
    10. ConfigurableWebApplicationContext wac =
    11. (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    12. wac.setEnvironment(getEnvironment());
    13. // 设置容器的父容器
    14. wac.setParent(parent);
    15. String configLocation = getContextConfigLocation();
    16. if (configLocation != null) {
    17. wac.setConfigLocation(configLocation);
    18. }
    19. // 对容器进行初始化等操作 填充容器
    20. configureAndRefreshWebApplicationContext(wac);
    21. return wac;
    22. }

    之后spring容器创建完成,就可以给外部提供访问了。

    springmvc的父子容器

    在前文中发现,DispatcherServlet在生成容器之前会去找一个rootContext父容器,那么这个父容器是什么呢?为什么要找父容器呢?

    现在如果在配置文件中声明了两个servlet,并且对应的spring配置文件配置了不同的bean,但是扫描的bean路径都相同的话,就会出现两个DispatcherServlet容器里会有一部分重复的bean

    1. <servlet>
    2. <servlet-name>app1servlet-name>
    3. <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    4. <init-param>
    5. <param-name>contextConfigLocation1param-name>
    6. <param-value>spring1.xmlparam-value>
    7. init-param>
    8. <load-on-startup>1load-on-startup>
    9. servlet>
    10. <servlet-mapping>
    11. <servlet-name>app1servlet-name>
    12. <url-pattern>/app1/*url-pattern>
    13. servlet-mapping>
    14. <servlet>
    15. <servlet-name>app2servlet-name>
    16. <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    17. <init-param>
    18. <param-name>contextConfigLocation2param-name>
    19. <param-value>spring2.xmlparam-value>
    20. init-param>
    21. <load-on-startup>1load-on-startup>
    22. servlet>
    23. <servlet-mapping>
    24. <servlet-name>app2servlet-name>
    25. <url-pattern>/app2/*url-pattern>
    26. servlet-mapping>

    springmvc为了解决这个问题就创造了一个父容器的概念,在springmvc官方提供的配置文件中就有一个属性 该属性就是定义的父容器,tomcat在读取web.xml文件时,首先读取的就是来创建父容器。之后再创建子容器,创建完子容器后就会将父容器放入子容器中,这样就可以避免Bean的重复创建。

    1. <web-app>
    2. <listener>
    3. <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    4. listener>
    5. <context-param>
    6. <param-name>contextConfigLocationparam-name>
    7. <param-value>/WEB-INF/app-context.xmlparam-value>
    8. context-param>
    9. <servlet>
    10. <servlet-name>appservlet-name>
    11. <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    12. <init-param>
    13. <param-name>contextConfigLocationparam-name>
    14. <param-value>param-value>
    15. init-param>
    16. <load-on-startup>1load-on-startup>
    17. servlet>
    18. <servlet-mapping>
    19. <servlet-name>appservlet-name>
    20. <url-pattern>/app/*url-pattern>
    21. servlet-mapping>
    22. web-app>

    springMvc的零配置

    spring官方不止提供了通过配置文件来配置springMvc,还提供了配置类的形式来配置,这样就可以省略掉xml配置文件了。

    1. public class MyWebApplicationInitializer implements WebApplicationInitializer {
    2. @Override
    3. public void onStartup(ServletContext servletContext) {
    4. // Load Spring web application configuration
    5. AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    6. // AppConfig对应之前的spring.xml配置文件配置包扫描路径
    7. context.register(AppConfig.class);
    8. // Create and register the DispatcherServlet
    9. DispatcherServlet servlet = new DispatcherServlet(context);
    10. ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
    11. registration.setLoadOnStartup(1);
    12. registration.addMapping("/app/*");
    13. }
    14. }
    1. @ComponentScan("com.yuyu")
    2. public class AppConfig {
    3. }

    使用代码的方式来实现父子容器也非常简单

    1. public class MyWebApplicationInitializer implements WebApplicationInitializer {
    2. @Override
    3. public void onStartup(ServletContext servletContext) {
    4. // Load Spring web application configuration
    5. AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    6. context.register(AppConfig.class);
    7. // 创建子容器,将context 父容器注入子容器
    8. AnnotationConfigWebApplicationContext context2 = new AnnotationConfigWebApplicationContext();
    9. context2.register(AppConfig.class);
    10. context2.setParent(context);
    11. // Create and register the DispatcherServlet
    12. DispatcherServlet servlet = new DispatcherServlet(context);
    13. ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
    14. registration.setLoadOnStartup(1);
    15. registration.addMapping("/app/*");
    16. }
    17. }

  • 相关阅读:
    接口调用三种方式
    Kafka系列之:Broker配置
    记录:R语言生成热图(非相关性)
    Mybatis_plus-逻辑删除、通用枚举、自动填充、插件等
    C++模拟实现——红黑树
    Qt QSS中 background-image,border-image,以及image属性差别
    连接本地mysql容器
    ansible第一天
    VUE前端实现“模糊搜索“(纯前端)
    易观千帆 | 2023年4月证券APP月活跃用户规模盘点
  • 原文地址:https://blog.csdn.net/qq_45743985/article/details/134464754