Web项目中重复创建Spring容器的解决办法

Web 项目使用 Spring 的问题

[内容时间:2022.01]

在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet )中获取到 Spring 容器的问题。只要在 web 层获取到了 Spring 容器,就可以在容器中获取到 Service 对象

代码示例

次数省略了 index 页面、pom文件、spring 配置文件、mybatis 配置文件、mapper 文件

RegisterServlet (重点代码)

// RegisterServlet 中 doGet() 或者 doPost() 方法 
protected void doGet(HttpServletRequest request,HttpServletResponse response){
    
    //省略获取参数 ************
    //************************
    
    //创建spring容器
    String configLocation = "application.xml";
    //重点↓↓↓
    ApplicationContext ctx = new ClassPathXmlApplicationContext(configLocation);
    //重点↓↓↓
    System.out.println("容器的信息:" + ctx);
    
    //省略从容器获取对象 *******
    //省略执行业务 *************
    
    //调用视图层,跳转
    request.getRequestDispatcher("/result.jsp").forward(request,response);
}
复制代码

运行结果分析

当表单提交,跳转到 success.jsp 后,多刷新几次页面,查看后台控制台输出,发现每次刷新页面,就 new 出一个新的 Spring 容器。

也就是说,每提交一次请求,每跑一次 doGet 或 doPost 方法,就会创建一个新的 Spring 容器,对于一个应用来说,只需要一个 Spring 容器即可。

所以,将 Spring 容器的创建语句放在 Servlet 的 doGet() 或 doPost() 方法中是有问题的

rongqi.png

此时可以考虑,将 Spring 容器的创建爱你放在 Servlet 初始化的时候进行。即:执行 init() 方法的时候执行。并且,Servlet 还是单例多线程的,一个业务只有一个 Servlet 实例,所有执行这个业务的用户执行的都是这一个 Servlet 实例。志这样子 Spring 容器就具有唯一性了。

但是,Servlet 是一个业务对应一个 Servlet 实例,LoginServlet 只有一个,但还会有 StudentServlet、TeacherServlet 等等。每个业务都会对应一个 Servlet,都会执行自己的 init() 方法。也就会创建一个 Spring 容器。

这样一来,Spring 容器又不唯一了

使用 Spring 的监听器 ContextLoaderListener(掌握)

对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个 ServletContext 对象。该对象是 Web 应用装载时初始化的。若将 Spring 容器的创建时机,放在 ServletContext 初始化时,就能保证 Spring 容器的创建只会执行一次,也就保证了 Spring 容器在整个应用中的唯一性

当 Spring 容器创建好之后,在整个应用生命周期中,Spring 容器应该是可以随时被访问的,具有全局性。而放入 ServletContext 对象的属性,就具有应用的全局性。所以,将创建好的 Spring 容器以属性的形式放入 ServletContext 的空间中,就保证了 Spring 容器的全局性

上述的这些工作,已经被封装在了如下 Spring 的 jar 包相关的 API 中:spring-web-5.2.5.RELEASE

步骤

添加 Maven 依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
复制代码

注册监听器

如果要在 ServletContext 初始化时创建 Spring 容器,就需要使用监听器接口 ServletContextListener 对 ServletContext 进行监听,在 web.xml 中注册该监听器

<!-- 此监听器的作用是初始化spring容器:在ServletContext创建的时候,初始化spring容器 -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
复制代码

为监听器指定 Spring 配置文件路径

ContextLoaderListener 在对 Spring 容器进行创建时,需要加载 Spring 配置文件。默认的 Spring 配置文件与名称为:WEB-INF/applicationContext.xml

但是一般不将配置文件放在此目录文件夹下。一般放置在 classpath 下,也就是 src 下,所以需要在 web.xml 中对 Spring 配置文件的位置以及名称进行指定

<!-- 为监听器指定Spring配置文件的位置 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
复制代码

获取 Spring 容器对象

在 Servlet 中获取容器对象的常用方式有两种

1、直接从 ServletContext 中获取

从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 中存放的 key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 这个常量

所以,可以直接通过 ServletContext 的 getAttribute() 方法,按照指定的 key 将容器对象获取到

String attribute = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
WebApplicationContext ac = (WebApplicationContext) this.getServletContext().getAttribute(attribute);
复制代码

2、通过 WebApplicationContextUtils 获取

工具类 WebApplicationContextUtils 中有一个方法专门用于 ServletContext 中获取 Spring 容器对象:getRequiredWebApplicationContext(ServletContext sc)

//框架提供的方法,获取容器对象
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext);
复制代码

查看源码,看调用关系,就可以看到它是从 ServletContext 中读取的属性值,即 Spring 容器

06_spring_yuanma.png

以上两种方式,无论使用哪种获取容器对象,刷新 success 页面后,可看到代码中使用的 Spring 容器均为同一个对象。 06_jiejuele.png

猜你喜欢

转载自juejin.im/post/7096782920790048782