自己在学习内存马的时候碰见了这两个东西,当时感觉被文章里面的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中注册或者扫描进去的对象我们是拿不到的
首先我们来测试一下去获取到当前对象的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都注册到了里面
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
如果上述分析有什么问题,还请各位师傅指正!