在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指 Servlet)中获取到 Spring 容器的问题。
只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。
举例 :springWeb 项目 (在 spring-mybatis 基础上修改)
选择项目原型 maven-archetype-webapp
将 spring-mybatis 项目中以下内容复制到当前项目中:
(1)Service 层、Dao 层全部代码
(2)配置文件 applicationContext.xml 及 jdbc.properties,mybatis.xml
(3)pom.xml
(4)加入 servlet, jsp 依赖
在之前原有的 pom.xml 文件中再加入以下的内容:
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.2.1-b03version>
<scope>providedscope>
dependency>
如果是 tomcat10 的话将第一个换成
<dependency>
<groupId>jakarta.servletgroupId>
<artifactId>jakarta.servlet-apiartifactId>
<version>5.0.0version>
<scope>providedscope>
dependency>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<form action="RegisterServlet" method="get">
姓名: <input type="text" name="name"> <br>
年龄: <input type="text" name="age"> <br>
<input type="submit" value="注册">
form>
body>
html>
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String strName = request.getParameter("name");
String strAge = request.getParameter("age");
String config = "applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
System.out.println("容器的信息:" + ctx);
StudentService service = (StudentService) ctx.getBean("studentService");
Student student = new Student();
student.setName(strName);
student.setAge(Integer.parseInt(strAge));
service.addStudent(student);
request.getRequestDispatcher("/result.jsp").forward(request, response);
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Titletitle>
head>
<body>
<h1>注册成功h1>
body>
html>
<servlet>
<servlet-name>RegisterServletservlet-name>
<servlet-class>com.fancy.controller.RegisterServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>RegisterServletservlet-name>
<url-pattern>/RegisterServleturl-pattern>
servlet-mapping>
当表单提交,跳转到 success.jsp 后,多刷新几次页面,查看后台输出,发现每刷新一次页面,就 new 出一个新的 Spring 容器。即,每提交一次请求,就会创建一个新的 Spring 容器。对于一个应用来说,只需要一个 Spring 容器即可。所以,将 Spring 容器的创建语句放在 Servlet 的 doGet()或 doPost()方法中是有问题的。
此时,可以考虑,将 Spring 容器的创建放在 Servlet 进行初始化时进行,即执行 init() 方法时执行。并且,Servlet 还是单例多线程的,即一个业务只有一个 Servlet 实例,所有执行该业务的用户执行的都是这一个 Servlet 实例。这样,Spring 容器就具有了唯一性了。
但是,Servlet 是一个业务一个 Servlet 实例,即 LoginServlet 只有一个,但还会有StudentServlet、TeacherServlet 等。每个业务都会有一个 Servlet,都会执行自己的 init()方法,也就都会创建一个 Spring 容器了。这样一来,Spring 容器就又不唯一了。
对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个ServletContext 对象,该对象是在 Web 应用装载时初始化的。若将 Spring 容器的创建时机,放在ServletContext 初始化时,就可以保证 Spring 容器的创建只会执行一次,也就保证了Spring 容器在整个应用中的唯一性。
当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就保证了 Spring 容器的全局性。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.2.5.RELEASEversion>
dependency>
若要在 ServletContext 初 始 化 时 创 建 Spring 容 器 , 就 需 要 使 用 监 听 器 接 口ServletContextListener 对 ServletContext 进行监听。在 web.xml 中注册该监听器。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
Spring 为该监听器接口定义了一个实现类 ContextLoaderListener,完成了两个很重要的工作:创建容器对象,并将容器对象放入到了 ServletContext 的空间中。
打开 ContextLoaderListener 的源码。看到一共四个方法,两个是构造方法,一个初始化方法,一个销毁方法。
跟踪 initWebApplicationContext()方法,可以看到,在其中创建了容器对象。
并且,将创建好的容器对象放入到了 ServletContext 的空间中,key 为一个常量:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。
ContextLoaderListener 在对 Spring 容器进行创建时,需要加载 Spring 配置文件。其默认的 Spring 配置文件位置与名称为:WEB-INF/applicationContext.xml。但,一般会将该配置文件放置于项目的 classpath 下,即 src 下,所以需要在 web.xml 中对 Spring 配置文件的位置及名称进行指定。
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext.xmlparam-value>
context-param>
从监听器 ContextLoaderListener 的父类 ContextLoader 的源码中可以看到其要读取的配置文件位置参数名称 contextConfigLocation。
在 Servlet 中获取容器对象的常用方式有两种
(1) 直接从 ServletContext 中获取
从对监听器 ContextLoaderListener 的源码分析可知,容器对象在 ServletContext 的中存放的 key 为 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。所以,可以直接通过 ServletContext 的 getAttribute()方法,按照指定的 key 将容器对象获取到。
String attr = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
WebApplicationContext ac = (WebApplicationContext)this.getServletContext().getContext(attr);
(2) 通过 WebApplicationContextUtils 获取
工具类 WebApplicationContextUtils 有一个方法专门用于从 ServletContext 中获取 Spring容器对象:getRequiredWebApplicationContext(ServletContext sc)
调用 Spring 提供的方法获取容器对象:
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
以上两种方式,无论使用哪种获取容器对象,刷新 success 页面后,可看到代码中使用的 Spring 容器均为同一个对象。