Spring容器

摩森特沃 2021年03月26日 431次浏览

声明:本文多处直接引用了文末的参考链接中的内容,如有侵权,请联系删除

什么是Spring容器

使用Spring框架编写应用程序之所以很简单,是因为有Spring容器帮程序员处理了大量繁琐的过程,可以说Spring容器是Spring框架的核心。那Spring容器到底是什么?

  • 从概念上讲:Spring容器是是用来管理对象的。并负责管理他们从创建、装配到销毁的整个生命周期。
  • 从具象化讲:在java项目中,我们使用实现了org.springframework.context.ApplicationContext接口的实现类。在web项目中,我们使用spring.xml——Spring的配置文件。
  • 从代码上讲:一个Spring容器就是某个实现了ApplicationContext接口的类的实例。也就是说,Spring容器其实就是一个ApplicationContext。

理解Spring容器

Spring的核心是容器,而容器并不唯一,框架本身就提供了很多个容器的实现,大概分为两种类型

  • 不常用的BeanFactory,这是最简单的容器,只能提供基本的DI功能;
  • 继承了BeanFactory后派生而来的ApplicationContext(应用上下文),它能提供更多企业级的服务,例如解析配置文本信息等等,这也是ApplicationContext实例对象最常见的应用场景。

BeanFactory和ApplicationContext是Spring的两大核心接口,而其中ApplicationContext是BeanFactory的子接口。它们都可以当做Spring的容器,Spring容器是生成Bean实例的工厂,并管理容器中的Bean。在基于Spring的Java EE应用中,所有的组件都被当成Bean处理,包括数据源,Hibernate的SessionFactory、事务管理器等。

生活中我们一般会把生产产品的地方称为工厂,而在这里bean对象的地方官方取名为BeanFactory,直译Bean工厂(com.springframework.beans.factory.BeanFactory),我们一般称BeanFactory为IoC容器,而称ApplicationContext为应用上下文。

BeanFactory

Spring容器最基本的接口就是BeanFactory。Spring Ioc容器的实现,从根源上是Beanfactory,但真正可以作为一个可以独立使用的ioc容器还是DefaultListableBeanFactory,因此可以这么说,
DefaultListableBeanFactory是整个Spring Ioc的始祖。

BeanFactory提供获取bean,是否包含bean,是否单例与原型,获取bean类型,bean别名的方法 。它最主要的方法就是getBean(String beanName)。

BeanFactory结构

  • HierarchicalBeanFactory:提供父容器的访问功能
  • ListableBeanFactory:提供了批量获取Bean的方法
  • AutowireCapableBeanFactory:在BeanFactory基础上实现对已存在实例的管理
  • ConfigurableBeanFactory:主要单例bean的注册,生成实例,以及统计单例bean
  • ConfigurableListableBeanFactory:继承了上述的所有接口,增加了其他功能:比如类加载器,类型转化,属性编辑器,BeanPostProcessor,作用域,bean定义,处理bean依赖关系,bean如何销毁等
  • 实现类DefaultListableBeanFactory:实现了ConfigurableListableBeanFactory,实现上述BeanFactory所有功能。它还可以注册BeanDefinition。

ApplicationContext

如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的身躯了。ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。

ApplicationContext结构

ApplicationContext类结构树

ApplicationContext的常用实现类

实现类作用
AnnotationConfigApplicationContext从一个或多个基于java的配置类中加载上下文定义,适用于java注解的方式
ClassPathXmlApplicationContext从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式
FileSystemXmlApplicationContext从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件
AnnotationConfigWebApplicationContext专门为web应用准备的,适用于注解方式
XmlWebApplicationContext从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式

ClassPathXmlApplicationContext和FileSystemXmlApplicationContext是最常使用的实现类,其使用方式如下

// 对于ClassPathXmlApplicationContext来说,"com/baobaotao/context/beans.xml"等同于"classpath: com/baobaotao/context/beans.xml"
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/baobaotao/context/beans.xml")

// 对于FileSystemXmlApplicationContext来说,"com/baobaotao/context/beans.xml"等同于"file:com/baobaotao/context/beans.xml"
ApplicationContext ctx = new FileSystemXmlApplicationContext("com/baobaotao/context/beans.xml");

// 指定一组配置文件,Spring会自动将多个配置文件在内存中"整合"成一个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"conf/beans1.xml","conf/beans2.xml"});

FileSystemXmlApplicationContext和ClassPathXmlApplicationContext都可以显式使用带资源类型(classpath:或file:)前缀的路径,它们的区别在于如果不显式指定资源类型前缀,将分别将路径解析为文件系统路径和类路径罢了。

在获取ApplicationContext实例后,就可以像BeanFactory一样调用getBean(beanName)返回Bean了。ApplicationContext的初始化和BeanFactory有一个重大的区别:BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例目标Bean;而ApplicationContext则在初始化应用上下文时就实例化所有Singleton Bean。因此ApplicationContext的初始化时间会比BeanFactory稍长一些,但一旦ApplicationContext初始化完成,程序后面获取Singleton Bean实例时候将有较好的性能。也可以为bean设置lazy-init属性为true,即Spring容器将不会预先初始化该bean。

WebApplicationContext

WebApplicationContext,是继承于ApplicationContext的一个接口,扩展了ApplicationContext,是专门为Web应用准备的,它允许从相对于Web根目录的路径中装载配置文件完成初始化。

从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。Spring专门为此提供一个工具类WebApplicationContextUtils,通过该类的getWebApplicationContext(ServletContext sc)方法,即可以从ServletContext中获取WebApplicationContext实例。

由于Web应用比一般的应用拥有更多的特性,因此WebApplicationContext扩展了ApplicationContext。WebApplicationContext定义了一个常量ROOT_WEB_APPLICATION_ CONTEXT_ATTRIBUTE,在上下文启动时,WebApplicationContext实例即以此为键放置在ServletContext的属性列表中,因此我们可以直接通过以下语句从Web容器中获取WebApplicationContext:

WebApplicationContext wac = (WebApplicationContext)servletContext.getAttribute(

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

这正是我们前面所提到的WebApplicationContextUtils工具类getWebApplicationContext(ServletContext sc)方法的内部实现方式。这样Spring的Web应用上下文和Web容器的上下文就可以实现互访,二者实现了融合

WebApplicationContext配置和初始化

WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实例,也就是说它必须在拥有Web容器的前提下才能完成启动的工作。有过Web开发经验的读者都知道可以在web.xml中配置自启动的Servlet(ContextLoaderServlet)或定义Web容器监听器(ServletContextListener),借助这两者中的任何一个,我们就可以完成启动Spring Web应用上下文的工作。

所有版本的Web容器都可以定义自启动的Servlet,但只有Servlet 2.3及以上版本的Web容器才支持Web容器监听器。有些即使支持Servlet 2.3 的Web服务器,但也不能在Servlet初始化之前启动Web监听器,如Weblogic 8.1、WebSphere 5.x、Oracle OC4J 9.0。

Spring分别提供了用于启动WebApplicationContext的Servlet和Web容器监听器:

  • org.springframework.web.context.ContextLoaderServlet
  • org.springframework.web.context.ContextLoaderListener

两者的内部都实现了启动WebApplicationContext实例的逻辑,我们只要根据Web容器的具体情况选择两者之一,并在web.xml中完成配置就可以了。
下面是使用ContextLoaderListener启动WebApplicationContext的具体配置:

<!--1,指定配置文件--> 
<context-param>                                                        
     <param-name>contextConfigLocation</param-name>   
     <param-value> 
       /WEB-INF/baobaotao-dao.xml, /WEB-INF/baobaotao-service.xml  
   </param-value> 
</context-param> 
<!--2,声明Web容器监听器--> 
<listener>   
     <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class> 
</listener> 

ContextLoaderListener默认读取/WEB-INF/下的applicationContext.xml文件。但是通过context-param指定配置文件路径(必须以contextConfigLocation为参数名称)后,便会去你指定的路径下读取对应的配置文件,并进行初始化。项目启动时,便会执行类ContextLoaderListener的相关方法,创建WebApplicationContext(Web应用上下文)并以键值对形式存放与ServletContext中。用户可以指定多个配置文件,用逗号、空格或冒号分隔均可。对于未带资源类型前缀的配置文件路径,WebApplicationContext默认这些路径相对于Web的部署根路径。当然,我们可以采用带资源类型前缀的路径配置,如"classpath*:/baobaotao-*.xml"和上面的配置是等效的。

ContextLoaderListener做了哪些工作

  1. servlet容器启动,为应用创建一个“全局上下文环境”:ServletContext
  2. 容器调用web.xml中配置的contextLoaderListener,初始化WebApplicationContext上下文环境(即IOC容器),加载context-param指定的配置文件信息到IOC容器中。WebApplicationContext在ServletContext中以键值对的形式保存
  3. 容器初始化web.xml中配置的servlet,为其初始化自己的上下文信息servletContext,并加载其设置的配置信息到该上下文中。将WebApplicationContext设置为它的父容器
  4. 此后的所有servlet的初始化都按照3步中方式创建,初始化自己的上下文环境,将WebApplicationContext设置为自己的父上下文环境

如果在不支持容器监听器的低版本Web容器中,我们可采用ContextLoaderServlet完成相同的工作:

<context-param> 
     <param-name>contextConfigLocation</param-name>   
     <param-value>/WEB-INF/baobaotao-dao.xml, /WEB-INF/baobaotao-service.xml </param-value> 
</context-param> 
…  
<!--1,声明自动启动的Servlet --> 
<servlet>   
    <servlet-name>springContextLoaderServlet</servlet-name> 
    <servlet-class>org.springframework.web.context.ContextLoaderServlet </servlet-class> 
    <!--2,启动顺序--> 
    <load-on-startup>1</load-on-startup> 
</servlet> 

在web.xml中,可以配置多个Servlet(面向Servlet编程,servlet类内部实现doGet和doPost方法处理请求):

<servlet>
	<init-param>
        <param-name>param1</param-name>
        <param-value>avalible in servlet init()</param-value>
    </init-param>
    <servlet-name>ServletDemo</servlet-name>
	<servlet-class>demo.ServletDemo</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ServletDemo</servlet-name>
    <url-pattern>/servlet</url-pattern>
</servlet-mapping>

SpringIOC容器先根据监听初始化WebApplicationContext,然后再初始化web.xml中其他配置的servlet,并将WebApplicationContext设置为它的父容器。所以最后的关系是ServletContext包含了WebApplicationContext,WebApplicationContext包含了其他的Servlet上下文环境。

下图以DispatcherServlet等Servlet为例进行了说明(DispatcherServlet是用于web应用中处理请求的必要配置):

servlet关系

对于作用范围而言,在DispatcherServlet中可以引用由ContextLoaderListener所创建的ApplicationContext中的内容,而反过来不行。当Spring在执行ApplicationContext的getBean时,如果在自己context中找不到对应的bean,则会在父ApplicationContext中去找。这也解释了为什么我们可以在DispatcherServlet中获取到由ContextLoaderListener对应的ApplicationContext中的bean。

在web.xml中可以定义的参数

  • 全局参数(ServletContext),通过<context-param></context-param>定义,该类型参数在servlet里面可以通过getServletContext().getInitParameter("context/param")得到;
  • 特定servlet的参数,通过在servlet中声明,通过this.getInitParameter("param1")得到;
<init-param>
    <param-name>param1</param-name>
    <param-value>avalible in servlet init()</param-value>                                                                   </init-param> 
// 通过这种方式定义,只能在servlet的init()方法中通过this.getInitParameter("param1")取得

ServletContext

ServletContext是来自于servlet规范里的概念,它是servlet用来与容器间进行交互的接口的组合,也就是说,这个接口定义了一系列的方法,servlet通过这些方法可以很方便地与自己所在的容器进行一些交互。

ServletContext的特点

  • WEB容器在启动时,它会为每个Web应用程序都创建一个对应的ServletContext对象,它代表当前Web应用。这个对象全局唯一,而且工程内部的所有servlet都共享这个对象。所以也叫全局应用程序共享对象;
  • ServletContext对象可以通过ServletConfig.getServletContext()方法获得对ServletContext对象的引用,也可以通过this.getServletContext()方法获得其对象的引用;
  • 由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象(扩展中有对域对象的介绍)。公共聊天室就会用到它;
  • 当web应用关闭、Tomcat关闭或者Web应用reload的时候,ServletContext对象会被销毁,因此其生命周期为从创建开始到服务器关闭结束。

ServletContext的应用

多个Servlet通过ServletContext对象实现数据共享

这个很好理解,类似于Session,我们也可以通过ServletContext对象来共享数据,但要注意的是,Session只能在一个客户端共享数据,它独占一个客户端。而ServletContext中的数据是可以供所有客户端共享的。

实现Servlet的请求转发

// 常用的请求转发是通过request对象实现的:
request.getRequestDispatcher("/url").forward(request, response);

// ServletContext也可以实现请求转发:
this.getServletContext().getRequestDispatcher("/url").forward(request, response);
这两个转发效果是一样的。

获取Web应用的初始化参数

上文已经提到过,此处不再赘述,主要有以下两个核心方法

  • getServletContext().getInitParameter(name):根据指定的参数名获取参数值
  • getServletContext().getInitParameterNames():获取所有参数名称列表

利用ServletContext对象读取资源文件(比如properties文件)

读取资源文件要根据资源文件所在的位置分为两种情况:

  • 文件在WebRoot文件夹下
    文件在WebRoot文件夹下,即我们的Web应用的根目录下。这时候我们可以使用ServletContext来读取该资源文件。
    假设我们Web根目录下有一个配置数据库信息的dbinfo.properties文件,里面配置了name和password属性,这时候可以通过ServletContext去读取这个文件:
// 这种方法的默认读取路径就是Web应用的根目录
InputStream stream = this.getServletContext().getResourceAsStream("dbinfo.properties");
// 创建属性对象
Properties properties = new Properties();
properties.load(stream);
String name = properties.getProperty("name");
String password = properties.getProperty("password");
out.println("name="+name+";password="+password);
  • 文件放在了src目录下
    如果文件放在了src目录下,通过ServletContext是读不到的,必须要使用类加载器去读取:
// 类加载器的默认读取路径是src根目录
InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("dbinfo.properties")
// 文件在src目录下的某个包下的读取方式
InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("com/gavin/dbinfo.properties")

补充一点,ServletContext可以获取文件的全路径,当然这个也是在Web应用根目录下的文件。比如我们在WebRoot文件夹下有一个images文件夹,images文件夹下有一个Servlet.jpg图片,为了得到这个图片的全路径,如下:

// 如何读取到一个文件的全路径,这里会得到在Tomcat的全路径
String path = this.getServletContext().getRealPath("/images/Servlet.jpg");

总结

  • ServletContext代表的是当前运行的程序应用;
  • WebApplicationContext是ServletContext的一个属性,可以通过servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)的方式获取,其继承自ApplicationContext;
  • ApplicationContext代表的是Spring的容器,一般称作应用上下文,是BeanFactory的子接口;
  • Beanfactory定义了Spring容器的总体规范,是Spring的心脏。

扩展内容

域对象

域对象是服务器在内存上创建的存储空间,用于在不同动态资源(servlet)之间传递与共享数据。

域对象的常见方法

  • setAttribute(name,value);name是String类型,value是Object类型:往域对象里面添加数据,添加时以key-value形式添加
  • getAttribute(name);:根据指定的key读取域对象里面的数据
  • removeAttribute(name);:根据指定的key从域对象里面删除数据

引用参考