一 servlet上下文ServletContext
(一)Tomcat 架构图
Server
Server 服务器的意思,代表整个 tomcat 服务器,一个 tomcat 只有一个 Server
Server 中包含至少一个 Service 组件,用于提供具体服务。这个在配置文件中也得到很好的体现(port=“8005” shutdown="SHUTDOWN"是在 8005 端口监听到"SHUTDOWN"命令,服务器就会停止)
Service
Service 中的一个逻辑功能层, 一个 Server 可以包含多个 Service
Service 接收客户端的请求,然后解析请求,完成相应的业务逻辑,然后把处理后的结果返回给客户端,一般会提供两个方法,一个 start 打开服务 Socket 连接,监听服务端口, 一个 stop 停止服务释放网络资源。
Connector
称作连接器,是 Service 的核心组件之一,一个 Service 可以有多个 Connector,主要是连接客户端请求,用于接受请求并将请求封装成 Request 和 Response,然后交给 Container 进行处理,Container 处理完之后在交给 Connector 返回给客户端。
Container
Service 的另一个核心组件,按照层级有 Engine,Host,Context,Wrapper 四种,一个Service 只有一个 Engine,其主要作用是执行业务逻辑
Engine
一个 Service 中有多个 Connector 和一个 Engine,Engine 表示整个 Servlet 引擎,一个Engine 下面可以包含一个或者多个 Host,即一个 Tomcat 实例可以配置多个虚拟主机,默认的情况下 conf/server.xml 配置文件中<Engine name="Catalina" defaultHost="localhost"> 定义了一个名为 Catalina 的 Engine。
一个 Engine 包含多个 Host 的设计,使得一个服务器实例可以承担多个域名的服务
Host
代表一个站点,也可以叫虚拟主机,一个 Host 可以配置多个 Context,在 server.xml 文件 中 的 默 认 配 置 为 <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">,
其中 appBase=webapps, 也就是<CATALINA_HOME>\webapps 目录,
unpackingWARS=true 属 性 指 定 在 appBase 指 定 的 目 录 中 的 war 包 都 自 动 的 解 压 ,
autoDeploy=true 属性指定对加入到 appBase 目录的 war 包进行自动的部署。
Context
Context,代表一个应用程序,就是日常开发中的web程序,或者一个WEB-INF目录以及下面的web.xml文件,换句话说每一个运行的 webapp 最终都是以 Context 的形式存在,每个 Context 都有一个根路径和请求路径;与 Host 的区别是 Context 代表一个应用,如默认配置下 webapps 下的每个目录都是一个应用,其中 ROOT 目录中存放主应用,其他目录存放别的子应用,而整个 webapps 是一个站点。
(二)ServletContext
描述:
公共接口ServletContext
定义servlet用于与其servlet容器通信的一组方法,例如,获取文件的MIME类型,调度请求或写入日志文件。
每个Java虚拟机的每个“ Web应用程序”都有一个上下文。(“ Web应用程序”是Servlet和内容的集合,这些Servlet和内容安装在服务器URL命名空间的特定子集下,例如/catalog
并可能通过.war
文件安装。)
对于在其部署描述符中标记为“ distributed”的Web应用程序,每个虚拟机都有一个上下文实例。在这种情况下,上下文不能用作共享全局信息的位置(因为该信息不是真正的全局信息)。请改用外部资源(如数据库)。
该ServletContext
对象包含在该ServletConfig
对象中,当初始化Servlet时,Web服务器将为该Servlet提供该Servlet。
何时创建?
服务器启动时会为每一个WEB应用程序创建一个ServletContext。
何时销毁?
服务器停止时销毁。
常用方法:
void setAttribute(键,值)
Object getAttribute(键)
方法:
1. addServlet() //提供了编程式的向servlet容器中注入servlet的方式,其达到的效果和在web.xml中配置或者使用WebServlet注解配置servlet是一样的。只不过这种方式更加灵活、动态。addServlet方法返回的是ServletRegistration实例(ervletRegistration.Dynamic集成ServletRegistration),使用这个实例可以进一步配置servlet的注册信息,比如配置url-pattern。
2. createServlet() //实例化一个servlet,得到一个servlet实例。这个传入的表示servlet类的Class实例必须要有个无参构造函数,因为这个方法的底层实现就是利用发射机制调用默认的无参构造函数进行实例化。这个方法返回的servlet实例没有多大的使用意义,可能还是需要调用相应的addServlet进行注册。
3. getServletRegistration() //根据servlet名称查找其注册信息,即ServletRegistration实例。
4. getServletRegistrations() //查询当前servlet上下文中所有的servlet的注册信息。
5. getSessionCookieConfig() //返回SessionCookieConfig实例,这个实例可以用于获取和设置会话跟踪的cookie的属性。多次调用getSessionCookieConfig方法返回的SessionCookieConfig实例是同一个,说明SessionCookieConfig是单例的。
6. setSessionTrackingModes() //设置会话的跟踪模式,getDefaultSessionTrackingModes用于获取默认的会话跟踪模式,getEffectiveSessionTrackingModes用于获取有效的会话跟踪模式。默认情况下,getDefaultSessionTrackingModes返回的会话跟踪模式就是有效的。
7. getJspConfigDescriptor() //获取web.xml和web-fragment.xml中配置的<jsp-config>数据。
8. getClassLoader() //获取当前servlet上文的类加载器。
9. declareRoles() //该方法用于申明安全角色。
10. getVirtualServerName() //返回servlet上下文(即应用)部署的逻辑主机名,比如在本机跑Tomcat,那么这个方法返回"Catalina/localhost"。
(三)参考文献
1、csdn ServletContext(源码分析) https://blog.youkuaiyun.com/qq_41076577/article/details/107853850
2、博客园 Servlet和Tomcat底层源码分析 https://www.cnblogs.com/sakura-yxf/p/12020312.html
3、JavaSchool ServletContext对象详解 http://www.51gjie.com/javaweb/854.html
4、博客园 Servlet上下文 https://www.cnblogs.com/shiy/p/6628613.html
5、csdn JavaWeb——Servlet(全网最详细教程包括Servlet源码分析) https://liuyangjun.blog.youkuaiyun.com/article/details/80292110?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
6、api谷歌翻译 http://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/ServletContext.html
二 spring上下文WebApplicationContext
(一)Spring基础篇——Spring容器和应用上下文理解
上文说到[回看],有了Spring之后,通过依赖注入的方式,我们的业务代码不用自己管理关联对象的生命周期。业务代码只需要按照业务本身的流程,走啊走啊,走到哪里,需要另外的对象来协助了,就给Spring说,我想要个对象——于是Spring就很贴心的给你个对象。听起来似乎很简单,使用起来也不难,但是如果仅仅是这样的拿来主义,倒也洒脱,不用费什么脑子。。。可是,你就真的不关心,Spring是从哪里把对象给你的吗?
如果你想要了解Spring深一些,而不仅仅是拿来用用,那么你就应该好好思考一下上诉问题,不然,这篇博文你还看个铲铲啊。。。你可以这样去思考:Spring既然要负责应用程序中那么多对象的创建管理,就像苹果要生产那么多的手机(对象)一样,肯定有一个专门搞对象的地方。苹果生产手机的地方叫工厂,比如富士康,但放在软件开发中,对于Spring搞对象的地方我们就不叫工厂了,而叫做容器。是的,容器的概念在java中你最熟悉的莫过于Tomcat了,它正是一个运行Servlet的web容器,而Spring要想实现依赖注入功能,就离不开对象生产的容器——如果没有容器负责对象的创建管理,你的程序代码只是喊要对象了,Spring也无处给你啊。实际上,容器是Spring框架实现功能的核心,容器不只是帮我们创建了对象那么简单,它负责了对象整个的生命周期的管理——创建、装配、销毁。关于Spring的这个容器你最常听闻的一个术语就是IOC容器。所谓IOC,是一种叫控制反转的编程思想,网上有很通俗易懂的总结,我就不胡乱阐述了。总之一句话,我的应用程序里不用再过问对象的创建和管理对象之间的依赖关系了,都让IOC容器给代劳吧,也就是说,我把对象创建、管理的控制权都交给Spring容器,这是一种控制权的反转,所以Spring容器才能称为IOC容器。不过这里要厘清一点:并不是说只有Spring的容器才叫IOC容器,基于IOC容器的框架还有很多,并不是Spring特有的。
好了,终于把Spring的容器概念阐述的差不多了,但有什么卵用呢?光有容器你其实什么都干不了!你以为容器那么科幻,跟叮当猫面前的百宝袋一样,你想要啥它就给你啥?实际上,容器里面什么都没有,决定容器里面放什么对象的是我们自己,决定对象之间的依赖关系的,也是我们自己,容器只是给我们提供一个管理对象的空间而已。那么,我们怎么向容器中放入我们需要容器代为管理的对象呢?这就涉及到Spring的应用上下文了。什么是应用上下文呢,你可以简单的理解成就是将你需要Spring帮你管理的对象放入容器的那么一种。。一种。。额。。一种容器对象——是的,应用上下文即是Spring容器的一种抽象化表述;而我们常见的ApplicationContext本质上说就是一个维护Bean定义以及对象之间协作关系的高级接口。额,听起来是不是很抽象拗口?那你再读一遍呢。。。这里,我们必须明确,Spring的核心是容器,而容器并不唯一,框架本身就提供了很多个容器的实现,大概分为两种类型:一种是不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;还有一种就是继承了BeanFactory后派生而来的应用上下文,其抽象接口也就是我们上面提到的的ApplicationContext,它能提供更多企业级的服务,例如解析配置文本信息等等,这也是应用上下文实例对象最常见的应用场景。有了上下文对象,我们就能向容器注册需要Spring管理的对象了。对于上下文抽象接口,Spring也为我们提供了多种类型的容器实现,供我们在不同的应用场景选择——
① AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式;
② ClassPathXmlApplicationContext:从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式;
③ FileSystemXmlApplicationContext:从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件;
④ AnnotationConfigWebApplicationContext:专门为web应用准备的,适用于注解方式;
⑤ XmlWebApplicationContext:从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。
有了以上理解,问题就很好办了。你只要将你需要IOC容器替你管理的对象基于xml也罢,java注解也好,总之你要将需要管理的对象(Spring中我们都称之问bean)、bean之间的协作关系配置好,然后利用应用上下文对象加载进我们的Spring容器,容器就能为你的程序提供你想要的对象管理服务了。下面,还是贴一下简单的应用上下文的应用实例:
我们先采用xml配置的方式配置bean和建立bean之间的协作关系:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="man" class="spring.chapter1.domain.Man">
<constructor-arg ref="qqCar" />
</bean>
<bean id="qqCar" class="spring.chapter1.domain.QQCar"/>
</beans>
然后通过应用上下文将配置加载到IOC容器,让Spring替我们管理对象,待我们需要使用对象的时候,再从容器中获取bean就ok了:
public class Test {
public static void main(String[] args) {
//加载项目中的spring配置文件到容器
// ApplicationContext context = new ClassPathXmlApplicationContext("resouces/applicationContext.xml");
//加载系统盘中的配置文件到容器
ApplicationContext context = new FileSystemXmlApplicationContext("E:/Spring/applicationContext.xml");
//从容器中获取对象实例
Man man = context.getBean(Man.class);
man.driveCar();
}
}
以上测试中,我将配置文件applicationContext.xml分别放在项目中和任意的系统盘符下,我只需要使用相应的上下文对象去加载配置文件,最后的结果是完全一样的。当然,现在项目中越来越多的使用java注解,所以注解的方式必不可少:
//同xml一样描述bean以及bean之间的依赖关系
@Configuration
public class ManConfig {
@Bean
public Man man() {
return new Man(car());
}
@Bean
public Car car() {
return new QQCar();
}
}
public class Test {
public static void main(String[] args) {
//从java注解的配置中加载配置到容器
ApplicationContext context = new AnnotationConfigApplicationContext(ManConfig.class);
//从容器中获取对象实例
Man man = context.getBean(Man.class);
man.driveCar();
}
}
自此,Spring容器和应用上下文就算阐述的差不多了,具体的技能点在今后的博文中会慢慢的给大家奉上。博文有阐述欠妥或者不准确的地方,欢迎大神大仙大侠们指正,陈某不胜感激。
(二)参考文献
1、知乎 理解Spring的上下文 https://www.zhihu.com/zvideo/1332063705220579328(视频)
2、博客园 陈本布衣 Spring基础篇——Spring容器和应用上下文理解 https://www.cnblogs.com/chenbenbuyi/p/8166304.html
三 spingmvc上下文WebApplicationContext
(一)spingmvc上下文WebApplicationContext
在spring和springmvc进行整合的时候,一般情况下我们会使用不同的配置文件来配置spring和springmvc,因此我们的应用中会存在至少2个ApplicationContext实例,由于是在web应用中,因此最终实例化的是ApplicationContext的子接口WebApplicationContext。如下图所示:
上图中显示了2个WebApplicationContext实例,为了进行区分,分别称之为:Servlet WebApplicationContext、Root WebApplicationContext。 其中:
- Servlet WebApplicationContext:这是对J2EE三层架构中的web层进行配置,如控制器(controller)、视图解析器(view resolvers)等相关的bean。通过spring mvc中提供的DispatchServlet来加载配置,通常情况下,配置文件的名称为spring-servlet.xml。
- Root WebApplicationContext:这是对J2EE三层架构中的service层、dao层进行配置,如业务bean,数据源(DataSource)等。通常情况下,配置文件的名称为applicationContext.xml。在web应用中,其一般通过ContextLoaderListener来加载。
spring父子容器
spring总的上下文容器有父子之分,父容器和子容器。 ** 父容器对子容器可见,子容器对父容器不可见 ** 。
对于传统的spring mvc来说,spring mvc容器为子容器,也就是说ServletDispatcher对应的容器为子容器,而web.xml中通过ConextLoaderListener的contextConfigLocation属性配置的为父容器。
ServletContext
首先,对于一个web应用,其部署在web容器中,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext,其为后面的spring IoC容器提供宿主环境;
根WebApplicationContext(注:ServletContext与WebApplicationContext共生死)
其次,在web.xml中会提供有contextLoaderListener。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized方法会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文被称为根上下文,即WebApplicationContext,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
子WebApplicationContext(注:ServletContext与WebApplicationContext共生死)
再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
重复扫描
Spring 项目在启动的时候是先初始化Spring 容器,会根据web.xml中配置的ContextLoaderListener 指定的配置文件扫描bean.初始化完成后再开始SpringMvc容器的初始化。如果定义的扫描范围重复的话,会导致bean会初始化两次,同时也会引起一些问题,比如事务失效,properties文件中使用@Value注入的属性无法获取,因为这些配置一般会在Spring的配置文件中配置。而Spring获取bean的机制是先从子类容器中去获取bean,子类获取不到时,再到父类容器中去寻找。所以如果是重复扫面的话相当于父容器中bean白定义了,因为子容器中都有一份,永远只会先去子容器中。
<context:component-scan base-package="com.summer">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
推荐下面配置,Controller 单独扫描,其他扫描排除Controller 保证扫描不重复
<context:component-scan base-package="com.summer" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
如果扫描重复的话,则会出现以下几种情况:
扫描的类增多,项目启动的时间变慢
@PostConstruct注解标注的方法被执行两次
会使Spring配置的事务失效(Spring是父容器,先初始化,SpringMVC是子容器,后初始化。子容器可以访问父容器的bean,父容器不能访问子容器的bean,当SpringMVC初始化时,它会将已经在父容器中的service等重新初始化一次,而SpringMVC不支持事务)
(二)参考文献
1、csdn spring与springmvc父子上下文WebApplicationContext https://blog.youkuaiyun.com/chuixue24/article/details/103820840
2、博客园 spring和springmvc父子容器概念介绍 https://www.cnblogs.com/grasp/p/11042580.html
3、csdn spring中的web上下文,spring上下文,springmvc上下文区别(超详细) https://blog.youkuaiyun.com/crazylzxlzx/article/details/53648625
四 上下文理解总结
(一)理解(环境、容器、公用信息(环境变量,实例变量,局部变量,其他类的状态,当前环境的状态))
Context在Java中的出现是如此频繁,但其中文翻译“上下文”又是如此诡异拗口,因此导致很多人不是很了解Context的具体含义是指什么,所以很有必要来深究一下这词的含义。 先来举几个JAVA中用到Context的例子:
(1)JNDI的一个类javax.naming.InitialContext,它读取JNDI的一些配置信息,并内含对象和其在JNDI中的注册名称的映射信息。
请看下面的代码
InitialContext ic=new InitialContext();
RMIAdaptor server=(RMIAdaptor)ic.lookup("jmx/invoker/RMIAdaptor");
这是一段JBoss中获取MBean的远程调用类的代码。
在这里面通过InitialContext中JNDI注册的名称“jmx/invoker/RMIAdaptor”来获得RMIAdaptor对象。
这和JAVA集合中的MAP有点象,有一个String的key,key对映着它的对象。
(2)再来看看下面Spring中最常见的几句代码。
ApplicationContext是内含configuration.xml配置文件的信息,使得可以通过getBean用名称得到相应的注册对象。
ApplicationContext ctx= newFileSystemXmlApplicationContext("configuration.xml");
Object obj= ctx.getBean("Object_Name");
从上面的代码,我很能体会到Context所代表的意义:公用信息、环境、容器....。
(3) 所以我觉得Context翻译成上下文并不直观,按照语言使用的环境,翻译成“环境”、“容器”可能更好。把Context翻译成“上下文”只是不直观罢了,不过也没大错。我们来看看中文的“上下文”是什么意思。我们常说听话传话不能“断章取义”,而要联系它的“上下文”来看。比如,小丽对王老五说“我爱你”,光看这句还以为在说情话呢。但一看上下文--“虽然我爱你,但你太穷了,我们还是分手吧”,味道就完全变了。从这里来看“上下文”也有“环境”的意思,就是语言的环境。
(4) PS:上下文其实是一个抽象的概念。我们常见的上下文有Servlet中的pageContext,访问JNDI时候用的Context。写过这些代码的人可能比较容易理解,其实他们真正的作用就是承上启下。比如说pageContext他的上层是WEB容器,下层是你写的那个Servlet类,pageContext作为中间的通道让Servlet 和Web容器进行交互。再比如访问JNDI的Context,他的上层是JNDI服务器(可能是远程的),下层是你的应用程序,他的作用也是建立一个通道让你能访问JNDI服务器,同时也让JNDI服务器接受你的请求,同样起到交互作用。
(二)参考文献
1、博客园 java上下文Context类 https://www.cnblogs.com/baoendemao/p/3804756.html
2、博客园 java中的上下文什么意思? https://www.cnblogs.com/cherishforchen/p/10918449.html
3、csdn java web上下文理解 https://blog.youkuaiyun.com/smile_lg/article/details/79415232
4、腾讯云 Java中的Context上下文 https://cloud.tencent.com/developer/news/596959
在某些API中,您可以在界面/类中看到此名称,例如Servlet的ServletContext
,JSF的FacesContext
,Spring的ApplicationContext
,Android的Context
,JNDI的InitialContext
等。他们都经常遵循Facade Pattern,它提取了环境细节,终端用户不需要知道在一个单一的接口/类。