Spring注解驱动开发第52讲——使用ServletContext注册web三大组件
发布日期:2021-06-30 17:56:35 浏览次数:3 分类:技术文章

本文共 13003 字,大约阅读时间需要 43 分钟。

写在前面

上一讲,我们说了一下基于运行时插件的ServletContainerInitializer机制,而在这一讲中,我们就来详细讲一下ServletContext用来注册web组件的用法,即使用ServletContext注册web组件。这些web组件就是我们通常所说的web三大组件,也就是Servlet、Filter以及Listener。

为什么我们一定要掌握使用ServletContext来注册web组件呢?因为我们是一定会遇到这样场景的,如果是以注解的方式来注册web组件,那么前提是这些web组件是由我们自己来编写的,这样,我们才可以把注解加上。但是,如果项目中导入的是第三方jar包,它们里面是有一些组件的,比如在项目中导入了阿里巴巴的连接池里面的Filter,对于这些组件而言,如果项目中有web.xml文件,那么我们就可以将它们配置在该配置文件中了,但是,我们现在的项目中是没有web.xml文件的,所以我们就要利用ServletContext将它们给注册进来了。

使用ServletContext注册web三大组件

ServletContext里面有如下这些方法。

在这里插入图片描述

有了这些方法,我们就可以利用它们给ServletContext里面注册一些组件了。接下来,我们就来编写一些示例组件。

首先,编写一个Servlet,例如UserServlet,如下所示。

package com.meimeixia.servlet;import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class UserServlet extends HttpServlet {
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub resp.getWriter().write("tomcat..."); } }

然后,再来编写一个Filter,例如UserFilter,要想成为一个Filter,它必须得实现Servlet提供的Filter接口。

package com.meimeixia.servlet;import java.io.IOException;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;public class UserFilter implements Filter {
// 销毁方法 @Override public void destroy() {
// TODO Auto-generated method stub } @Override public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
// 过滤请求 System.out.println("UserFilter...doFilter方法..."); // 放行 arg2.doFilter(arg0, arg1); } // 初始化方法 @Override public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub }}

接着,再来编写一个Listener,例如UserListener,我们要知道监听器是有很多的,所以这儿我们不妨让UserListener来实现ServletContextListener接口,以监听ServletContext的创建和启动过程。

package com.meimeixia.servlet;import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;/** * ServletContextListener的作用:监听项目的启动和停止 * @author liayun * */public class UserListener implements ServletContextListener {
// 这个方法是来监听ServletContext销毁的,也就是说,我们这个项目的停止 @Override public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub System.out.println("UserListener...contextDestroyed..."); } // 这个方法是来监听ServletContext启动初始化的 @Override public void contextInitialized(ServletContextEvent arg0) {
// TODO Auto-generated method stub System.out.println("UserListener...contextInitialized..."); }}

OK,我们写好了以上三个web组件,即Servlet、Filter以及Listener了。下面,我们就可以直接调用ServletContext的方法来注册这些组件了,由于项目中没有web.xml配置文件,所以我们就不得不写Java代码了。

注册Servlet

我们先来注册Servlet,即UserServlet。当我们调用ServletContext对象的addServlet方法来注册Servlet时,发现两个参数名字的提示分别是arg0和arg1,意义不明,这导致我们很困惑。

在这里插入图片描述

为什么会出现这种现象呢?这是因为我们还没有绑定Tomcat服务器的源码。所以,为了让addServlet方法中两个参数名字的提示更加有意义,我们得绑定Apache Tomcat v8.0版本服务器的源码,步骤如下所示。

第一步,按住Ctrl键,然后鼠标点击如下addServlet方法。

在这里插入图片描述

第二步,此时,会跳转到如下窗口,然后点击窗口中的Attach Source...按钮。

在这里插入图片描述

第三步,按照下图所示的详细步骤绑定Tomcat服务器的源码。

在这里插入图片描述

绑定好之后,我们再次来调用ServletContext对象的addServlet方法,如下图所示,可以看到现在该方法中的两个参数名字的提示终于是有意义的了。

在这里插入图片描述

那么,我们现在调用哪一个addServlet方法来注册UserServlet呢?我们不妨调用如下第二个addServlet方法,在第一个参数的位置传入我们UserServlet的名字,例如userServlet,在第二个参数的位置传入我们自己new的一个UserServlet对象。

在这里插入图片描述

其实,用哪一种addServlet方法都是可行的,只不过这儿我们选用了第二种方法而已。

此时,不出预料的话,应该会返回一个Dynamic类型的对象,但是为了将返回类型写得更详细点,我们可以将其写成ServletRegistration.Dynamic,如下所示。

package com.meimeixia.servlet;import java.util.Set;import javax.servlet.ServletContainerInitializer;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.ServletRegistration;import javax.servlet.annotation.HandlesTypes;import com.meimeixia.service.HelloService;@HandlesTypes(value={
HelloService.class})public class MyServletContainerInitializer implements ServletContainerInitializer {
/* * 参数: * ServletContext sc:代表当前web应用。一个web应用就对应着一个ServletContext对象,此外,它也是我们常说的四大域对象之一, * 我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到 * * Set
> arg0:我们感兴趣的类型的所有后代类型 * */ @Override public void onStartup(Set
> arg0, ServletContext sc) throws ServletException {
// TODO Auto-generated method stub System.out.println("我们感兴趣的所有类型:"); // 好,我们把这些类型来遍历一下 for (Class
clz : arg0) {
System.out.println(clz); } // 注册Servlet组件 ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet()); }}

至此,我们只是给ServletContext中注册了一个UserServlet组件。但是,该UserServlet的映射信息我们还没配置呢,即它是来处理什么样的请求的。那怎么办呢?很简单,返回的ServletRegistration.Dynamic对象有一个addMapping方法,调用它即可配置UserServlet的映射信息,如下所示。

package com.meimeixia.servlet;import java.util.Set;import javax.servlet.ServletContainerInitializer;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.ServletRegistration;import javax.servlet.annotation.HandlesTypes;import com.meimeixia.service.HelloService;@HandlesTypes(value={
HelloService.class})public class MyServletContainerInitializer implements ServletContainerInitializer {
/* * 参数: * ServletContext sc:代表当前web应用。一个web应用就对应着一个ServletContext对象,此外,它也是我们常说的四大域对象之一, * 我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到 * * Set
> arg0:我们感兴趣的类型的所有后代类型 * */ @Override public void onStartup(Set
> arg0, ServletContext sc) throws ServletException {
// TODO Auto-generated method stub System.out.println("我们感兴趣的所有类型:"); // 好,我们把这些类型来遍历一下 for (Class
clz : arg0) {
System.out.println(clz); } // 注册Servlet组件 ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet()); // 配置Servlet的映射信息 servlet.addMapping("/user"); }}

从上可以看到,我们的UserServlet现在是来处理user请求的。

注册Listener

注册Listener同上。那么,我们现在应该调用哪一个addListener方法来注册UserListener呢?我们不妨调用如下第一个addListener方法,在参数位置传入UserListener的类型,这样就会自动帮我们创建出UserListener对象,并将其注册到ServletContext中了。

在这里插入图片描述

其实,用哪一种addListener方法都是可行的,只不过这儿我们选用了第一种方法而已。监听器的注册也还蛮简单的,如下所示。

package com.meimeixia.servlet;import java.util.Set;import javax.servlet.ServletContainerInitializer;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.ServletRegistration;import javax.servlet.annotation.HandlesTypes;import com.meimeixia.service.HelloService;@HandlesTypes(value={
HelloService.class})public class MyServletContainerInitializer implements ServletContainerInitializer {
/* * 参数: * ServletContext sc:代表当前web应用。一个web应用就对应着一个ServletContext对象,此外,它也是我们常说的四大域对象之一, * 我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到 * * Set
> arg0:我们感兴趣的类型的所有后代类型 * */ @Override public void onStartup(Set
> arg0, ServletContext sc) throws ServletException {
// TODO Auto-generated method stub System.out.println("我们感兴趣的所有类型:"); // 好,我们把这些类型来遍历一下 for (Class
clz : arg0) {
System.out.println(clz); } // 注册Servlet组件 ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet()); // 配置Servlet的映射信息 servlet.addMapping("/user"); // 注册Listener组件 sc.addListener(UserListener.class); }}

注册Filter

现在我们来注册Filter,即UserFilter。注册Servlet和Filter有一点特殊之处,那就是注册它俩之后都得配置其映射信息。

那么问题又来了,我们现在应该调用哪一个addFilter方法来注册UserFilter呢?我们不妨调用如下第一个addFilter方法,在第一个参数的位置传入我们UserFilter的名字,例如userFilter,在第二个参数的位置传入UserFilter的类型,即UserFilter.class,这样,Servlet容器(即Tomcat服务器)就会帮我们创建出一个UserFilter对象,并将其注册到ServletContext中。

在这里插入图片描述

其实,用第二种addFilter方法也是可行的,只不过是在第二个参数的位置传入我们自己new出来的UserFilter对象。

同样地,此时,应该会返回一个Dynamic类型的对象,只不过它是FilterRegistration里面的Dynamic,如下所示。

package com.meimeixia.servlet;import java.util.Set;import javax.servlet.FilterRegistration;import javax.servlet.ServletContainerInitializer;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.ServletRegistration;import javax.servlet.annotation.HandlesTypes;import com.meimeixia.service.HelloService;@HandlesTypes(value={
HelloService.class})public class MyServletContainerInitializer implements ServletContainerInitializer {
/* * 参数: * ServletContext sc:代表当前web应用。一个web应用就对应着一个ServletContext对象,此外,它也是我们常说的四大域对象之一, * 我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到 * * Set
> arg0:我们感兴趣的类型的所有后代类型 * */ @Override public void onStartup(Set
> arg0, ServletContext sc) throws ServletException {
// TODO Auto-generated method stub System.out.println("我们感兴趣的所有类型:"); // 好,我们把这些类型来遍历一下 for (Class
clz : arg0) {
System.out.println(clz); } // 注册Servlet组件 ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet()); // 配置Servlet的映射信息 servlet.addMapping("/user"); // 注册Listener组件 sc.addListener(UserListener.class); // 注册Filter组件 FilterRegistration.Dynamic filter = sc.addFilter("userFilter", UserFilter.class); }}

你看到了没有,调用ServletContext对象的addServlet方法(即注册Servlet)和addFilter方法(即注册Filter),都会返回一个Dynamic对象,只不过一个是ServletRegistration里面的Dynamic,一个是FilterRegistration里面的Dynamic,大家可一定要注意哟~~~

然后,我们需要利用返回的FilterRegistration.Dynamic对象中的addMappingForXxx方法配置UserFilter的映射信息。

在这里插入图片描述

那么,我们现在调用哪一个addMappingForXxx方法来配置UserFilter的映射信息呢?不妨使用第二个方法,即addMappingForUrlPatterns方法,因为在该方法中我们可以自己来写url映射。

package com.meimeixia.servlet;import java.util.EnumSet;import java.util.Set;import javax.servlet.DispatcherType;import javax.servlet.FilterRegistration;import javax.servlet.ServletContainerInitializer;import javax.servlet.ServletContext;import javax.servlet.ServletException;import javax.servlet.ServletRegistration;import javax.servlet.annotation.HandlesTypes;import com.meimeixia.service.HelloService;@HandlesTypes(value={
HelloService.class})public class MyServletContainerInitializer implements ServletContainerInitializer {
/* * 参数: * ServletContext sc:代表当前web应用。一个web应用就对应着一个ServletContext对象,此外,它也是我们常说的四大域对象之一, * 我们给它里面存个东西,只要应用在不关闭之前,我们都可以在任何位置获取到 * * Set
> arg0:我们感兴趣的类型的所有后代类型 * */ @Override public void onStartup(Set
> arg0, ServletContext sc) throws ServletException {
// TODO Auto-generated method stub System.out.println("我们感兴趣的所有类型:"); // 好,我们把这些类型来遍历一下 for (Class
clz : arg0) {
System.out.println(clz); } // 注册Servlet组件 ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet()); // 配置Servlet的映射信息 servlet.addMapping("/user"); // 注册Listener组件 sc.addListener(UserListener.class); // 注册Filter组件 FilterRegistration.Dynamic filter = sc.addFilter("userFilter", UserFilter.class); // 配置Filter的映射信息 filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); }}

可以看到,addMappingForUrlPatterns方法中传入的第一个参数还是蛮奇怪的,居然是EnumSet.of(DispatcherType.REQUEST),该参数表示的是Filter拦截的请求类型,即通过什么方式过来的请求,Filter会进行拦截。我们不妨点进DispatcherType枚举的源码里面去看一看,如下图所示,可以看到好多的请求类型,不过常用的就应该是FORWARDREQUEST它俩。

在这里插入图片描述

现在addMappingForUrlPatterns方法中传入的第一个参数是EnumSet.of(DispatcherType.REQUEST),表明我们写的UserFilter会拦截通过request方式发送过来的请求。

该方法中的第二个参数(即isMatchAfter)我们直接传入true就行,第三个参数(即urlPatterns)就是Filter要拦截的路径,目前我们传入的是/*,即拦截所有请求。

以上就是我们以编码的方式向ServletContext对象中注册web中的三大组件。

启动项目,进行测试

现在我们来启动项目进行测试,看我们注册的以上三个组件有没有起到作用。如果注册的UserFilter真的起到作用了,那么它就会在放行目标请求之前打印相应内容;如果注册的UserListener真的起到作用了,那么在其创建和销毁过程中也会有相应内容打印;如果注册的UserServlet真的起到作用了,那么当我们发送一个user请求后,就能在浏览器页面中看到有相应内容输出了。

真的是这样吗?我们不妨来启动一下我们的项目,发现Eclipse控制台打印了如下内容。

在这里插入图片描述

可以看到我们注册的UserListener确实起到作用了,在项目启动的时候,有相关内容输出,因为它本来就是监听项目的启动和停止的。

然后,我们来访问项目的首页,此时,浏览器中显示的是首页的内容,如下图所示。

在这里插入图片描述

Eclipse控制台却打印出了如下内容,这说明我们注册的UserFilter确实起到作用了,在目标请求放行之前打印了相应内容。

在这里插入图片描述

接着,我们在浏览器地址栏中输入http://localhost:8080/servlet3.0-liayun/user进行访问,即向服务器发送了一个user请求,此时,我们注册的UserServlet就会来处理该请求,并给浏览器响应相应内容,如下图所示。

在这里插入图片描述

而且,我们注册的UserFilter也会起到作用,即在目标请求放行之前会打印相应内容,不信,你看Eclipse控制台打印的内容。

在这里插入图片描述

最后,我们来停止Tomcat服务器,此时,由于我们注册的UserListener会监听到项目的停止,因此监听ServletContext销毁的方法也会运行,在Eclipse控制台也会有相应内容输出,如下图所示。

在这里插入图片描述

总结

我们可以通过编码的方式在项目启动的时候,给ServletContext(即当前项目)里面来注册组件。当然,并不是说,你只要拿到了ServletContext对象就能注册组件了,因为必须是在项目启动的时候,才能注册组件。

而且,在项目启动的时候,我们可以有两处来使用ServletContext对象注册组件。

第一处就是利用基于运行时插件的ServletContainerInitializer机制得到ServletContext对象,然后再往其里面注册组件。本讲通篇所讲述的就是在这一处使用ServletContext对象来注册组件。

第二处,你可能想不到,我们上面不是编写过一个监听器(即UserListener)吗?它是来监听项目的启动和停止的,在监听项目启动的方法中,传入了一个ServletContextEvent对象,即事件对象,我们就可以通过该事件对象的getServletContext方法拿到ServletContext对象,拿到之后,是不是就可以往它里面注册组件了啊?

package com.meimeixia.servlet;import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;/** * ServletContextListener的作用:监听项目的启动和停止 * @author liayun * */public class UserListener implements ServletContextListener {
// 这个方法是来监听ServletContext销毁的,也就是说,我们这个项目的停止 @Override public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub System.out.println("UserListener...contextDestroyed..."); } // 这个方法是来监听ServletContext启动初始化的 @Override public void contextInitialized(ServletContextEvent arg0) {
// TODO Auto-generated method stub ServletContext servletContext = arg0.getServletContext(); System.out.println("UserListener...contextInitialized..."); }}

温馨提示:在项目运行的时候,再给ServletContext对象里面来注册组件,那是不行的,这是出于安全来考虑的。

转载地址:https://liayun.blog.csdn.net/article/details/114870942 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring注解驱动开发第53讲——Servlet 3.0与Spring MVC的整合分析
下一篇:Spring注解驱动开发第51讲——ServletContainerInitializer来了,傻孩子们,快跑啊!

发表评论

最新留言

很好
[***.229.124.182]2024年04月22日 21时17分25秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章