1. 为什么Spring要与web环境集成?
在之前讲解的案例中,我们将 Dao 层和 Service 层的类通过 IOC 容器实例化,在 web 层通过 ApplicationContext
接口实例化创建 Bean 调用具体业务。但是在实际开发中, 在 web 层不止一个类,所以每次从容器中获取 Bean 时都要实例化接口,这样弊端是配置文件加载多次,应用上下文对象创建多次。所以为了简化此过程,我们可以将 Spring 和 web 环境集成。
2. 集成的底层实现
在Web项目中,可以使用ServletContextListener
监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext
,在将其存储到最大的域servletContext
域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext
对象了。具体代码如下:
//监听器类
public class ContextLoaderListener implements ServletContextListener
{
public void contextInitialized(ServletContextEvent servletContextEvent) {
//ServletContext域实例化
ServletContext servletContext = servletContextEvent.getServletContext();
//读取web.xml中的全局参数
String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
//实例化 IOC容器
ApplicationContext app = new ClassPathXmlApplicationContext(contextConfigLocation);
//将Spring的应用上下文对象存储到ServletContext域中
servletContext.setAttribute("app",app);
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
//web中的web.xml 配置文件中添加
<!--全局初始化参数-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--配置监听器-->
<listener>
<listener-class>com.ContextLoaderListener</listener-class>
</listener>
//在 web 层中获取 Spring 应用上下文对象
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//ServletContext域实例化
ServletContext servletContext = this.getServletContext();
//取出 ServletContext域中的 Spring的应用上下文对象
ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app");
}
}
上述案例手动的完成了 Spring与web环境集成,但是在 web 层获取出 Spring的应用上下文对象时需要提供存入的名称,所以需要进一步提取参数,修改如下:
//添加一个工具类
public class WebApplicationContextUtils {
//定义一个静态方法
//用于返回 ServletContext域中的 Spring的应用上下文对象
public static ApplicationContext getWebApplicationContext(ServletContext servletContext){
return (ApplicationContext) servletContext.getAttribute("app");
}
}
//修改 web 层的代码
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//ServletContext域实例化
ServletContext servletContext = this.getServletContext();
//取出 ServletContext域中的 Spring的应用上下文对象
//只修改了这一行代码
ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
//获取 Bean
UserService userService = app.getBean(UserService.class);
}
}
上述就是使用手动完成 Spring与web环境的集成。上面讲了一大串代码,但是不需要记忆,有一个大概的了解,知道 Spring与web环境集成的底层是如何实现的。
3. Spring实现集成
其实在集成不用手动实现,Spring提供了一个监听器 ContextLoaderListener
就是对上述功能的封装,该监听器内部加载 Spring 配置文件,创建应用上下文对象,并存储到 ServletContext 域中,提供了一个客户端工具WebApplicationContextUtils
类供使用者获得应用上下文对象。
那么具体使用流程:
-
导入Spring集成web的包:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.8</version> </dependency>
-
在web.xml 配置文件中添加添加配置文件:
<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>
-
在web 层调用:
public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //ServletContext域实例化 ServletContext servletContext = this.getServletContext(); //取出 ServletContext域中的 Spring的应用上下文对象 //只修改了这一行代码 ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext); } }
对比两种方式,其实只是少了两个类的创建,Spring 已经将两个类的创建进行了封装,所以在使用过程中没有区别。
当然有一点需要提醒的,在两种方式中我们都使用到了ContextLoaderListener
类和WebApplicationContextUtils
类,在这两种方法中这两个类是有区别的。第一种方式中的两个类的类名是自定义的,也可以换成其他类名;第二种方式中的两个类的类名是已经固定了,无法改变。