SpringBoot和SpringMvc中的Context
2022-11-8 10:19:0 Author: xz.aliyun.com(查看原文) 阅读量:17 收藏

前言:

自己在学习内存马的时候碰见了这两个东西,当时感觉被文章里面的ChildContext和RootContext搞的很混乱,所以自己决定单独去学学这两个

对于ChildContext和RootContext的说明:

我们使用spring中的ContextLoaderListener和DispatcherServlet分别加载了不同的spring配置文件。

(这两个配置文件其实就分别对应了我们的RootContext和我们的ChildContext)

spring mvc程序里会有两个WebApplicationContext,一个是parent,从applicationContext.xml里加载的,一个是child,从servlet-context.xml里加载的。 两者是继承关系,child WebApplicationContext 可以通过getParent()函数获取到root WebApplicationContext。

这里我们需要解释一下上面这个servlet-context.xml,这个的意思其实就是指代的是我们上面的这个dispatcherServlet.xml

所以从这里的这个命名规则来说我们也应该知道,其实我们的child可以有多个,一个servlet的配置文件就能对应一个ChildContext,只不过在普通的环境使用中,特别是在我们的Springmvc中大多数都是只有两个配置文件的,他们分别是:

所以分别对应的其实就是一个RootContext和一个ChildContext上下文对象

引用的一个小案例:

该案例来源在网上看到的一个比较好帮助我们理解的案例:

package net.aty.springmvc;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import net.aty.service.MyService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;

@Controller
public class FirstController {

    @Autowired
    private MyService service;

    @Autowired
    private HttpServletRequest request;

    public FirstController() {
        System.out.println("I am a controller.");
    }

    @RequestMapping("/mvc/first/hello.do")
    @ResponseBody
    public String hello(@RequestParam("userName") String userName) {

        testContext();

        return service.process(userName);
    }

    private void testContext() {

        WebApplicationContext root = ContextLoader
                .getCurrentWebApplicationContext();

        ServletContext servletContext = root.getServletContext();

        // true
        System.out
                .println(root == servletContext
                        .getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));

        // true
        System.out.println(root == WebApplicationContextUtils
                .getWebApplicationContext(servletContext));

        WebApplicationContext child = (WebApplicationContext) request
                .getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);

        // false
        System.out.println("root:" + root.containsLocalBean("myService"));

        // true
        System.out
                .println("root:" + root.containsLocalBean("firstController"));

        // false
        System.out.println("child:" + child.containsLocalBean("myService"));

        // true
        System.out.println("child:"
                + child.containsLocalBean("firstController"));

        // true
        System.out.println("is parent==" + (child.getParent() == root));

    }

}

那么从上面的代码中我们可以得出:

ContextLoader.getCurrentWebApplicationContext();可以得到我们的RootContext对象

通过rootContext可以得到我们的servletContext对象

我们的rootContext是 ServletContext里面的一个属性,可以通过:getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)获取到

在ServletContext中我们还能找到 其他的childContext,比如上面我们提到的这个Dispatcher代表的servlet,也就是我们注册的这个springmvc的这个servlet

所代表的就是这个

(这也证明了不管我们的ChildContext还是RootContext都是我们的XmlWebApplicationContext对象,只是如果这个对象有parent对象那么这个对象就是我们的Child,如果没有就是我们的RootContext)

我们可以这样来验证一下:

(WebApplicationContext)request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE)==ContextLoader.getCurrentWebApplicationContext().getServletContext()

我们可以看到这个返回的true,那么这样证明了我们上面说得是正确的

当然这样也说明了我们可以通过request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE)来去获取我们的ChildContext对象

我们再来去看看这个思想,也就是上面提到的我们的RootContext对象不能包含我们ChildContext里面的Bean对象

这里我们先去看看我们的RootContext能否包含我们Child里面的Bean:

ContextLoader.getCurrentWebApplicationContext().containsLocalBean("org.springframework.web.servlet.view.InternalResourceViewResolver")

那么这里为什么我们想要去包含这个Controller呢,原因很简单,因为我们看

我们这个扫描组件是在我们的dispatcherServlet中声明的,那么这样也就说明这几个Bean对象在我们的这个ChildContext中,所以当然我们的RootContext是包含不到这几个Bean的,那么这里我们如果去尝试使用ChildContext来去包含这个里面扫描过的Bean标签呢?

(WebApplicationContext)request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE).containsBean("testC")

WebApplicationContext child = (WebApplicationContext) request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE);
child.containsLocalBean("testC");

那么我们来去尝试看看结果:

那么这里我们可以看到这个是包含成功了的,并且我们还要注意一个细节,这里写入到我们Context中的这个Bean的开头是我们的小写字母t

那么我们来看我们的RootContext:

ContextLoader.getCurrentWebApplicationContext().containsLocalBean("testC")

那么这里确实是没有包含的

那么这里也告诉我们获取Context对象最好是去获取它的ChildContext对象,不然有些在ChildContex中注册或者扫描进去的对象我们是拿不到的

我们来看看SpirngBoot中的Context:

首先我们来测试一下去获取到当前对象的rootContext对象:

首先我们来使用这个方法:

ContextLoader.getCurrentWebApplicationContext()

获取不到的原因:springboot无论以main方法还是spring-boot:run的方式执行都不会跑SpringBootServletInitializer中的onStartup导致ContextLoaderListener没有执行。

我们来看第二种方法:

WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());

这里我们可以看到是获取到,获取到的是AnnotationConfigServletWebApplicationContext这个类

通过它的parent为null我们也可以知道它是我们的RootContext对象

这个时候我们去使用上面Spring中获取childContext的方法去获取一下我们的这个Context对象:

WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest())

那么在这个地方我们就发现在Spirng中通过这个方式获取到的ChildContext对象在这里获取到的仍然是上面获取到的RootContext对象

我们换一种方式来看看:

(WebApplicationContext)request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE)

我们去查看里面的bean

我们在AnnotationConfigServletWebServerApplicationContext里面找到了我们注册的bean

它的ServletContext:

和我们SpirngMvc中的servletContext是一样的

所以在SpringBoot中其实还解决了在SpringMVC中容易出现的这么一个问题:

这里通过我们的一些调试分析也发现其实我们的SpringBoot中其实采用的就是上面的第二种解决办法,它并没有Child和ParentContext的区分,这里面有且仅有一个Context对象,我们所有的Bean都注册到了里面

总结

SpringMvc和SpirngBoot中的相同点:

1:ServletContext都为我们的ApplicationContextFacade对象

2:作用范围都是ServletContext最大,因为其他的Context,包括我们的RootContext对象和childContext对象都是它里面的一个属性,一个attributes

不同点:

1:SpringBoot中只有一个RootContext,没有child,所有bean都注册在这里面,而SpingMvc中有两个一个root一个child

2:在SpirngMvc中遇见最多的RootContext对象和ChildContext对象就是这两个:

对应在web.xml中的配置就是:

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:dispatcherServlet.xml</param-value>
    </init-param>
  </servlet>

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

如果有多个配置文件就能对应生成多个ChildContext上下文对象

3:在SpringMvc中在哪个配置文件中声明注解扫描标签,那么扫描到的bean就会被注册到该配置文件对应的哪个Context上下文环境中去

如:

<context:component-scan base-package="Controller"/>

其他:

我们要明白其实所谓的RootContext和ChildContex他们之间的区分其实就是一个所谓的继承关系,简单来说有parent属性的就是child没有的就是root

child中可以访问到parent里面的资源,parent中访问不到child中的资源,这也是后期注入内存马中为什么更多的是去选择获取ChildContext而不是RootContext的原因

对于Bean的存储,还是存储在我们Context里面的Beanfactory这个属性里面的,存储的方式要注意,它的类名首字母是小写存放的

参考:

https://blog.csdn.net/aitangyong/article/details/50563239
https://blog.csdn.net/hengyunabc/article/details/47072637

如果上述分析有什么问题,还请各位师傅指正!


文章来源: https://xz.aliyun.com/t/11814
如有侵权请联系:admin#unsafe.sh