深入剖析Tomcat(十、十一) 详解StandardWrapper

《深入剖析Tomcat》第十章介绍了Tomcat的安全机制,主要就是对servlet的访问做安全验证,如果Tomcat中设置了某些servlet需要指定角色的用户才能访问,则需要客户端进行登录验证,如果用户名密码正确并且该用户拥有该角色的话,Tomcat就会放行,否则就会返回403(无权访问)。用户名密码及角色配置是需要配置在Tomcat内部的,通常是放在一个叫做“tomcat-users.xml”的文件中。

鉴于当前java项目开发基本都在使用springboot框架,也不需要关注Tomcat本身的安全性问题(项目的权限验证由项目自身来实现),大家也没必要去给Tomcat去配用户名密码角色。所以这块内容就不再深入了解了,直接步入下一章:详解StandardWrapper。

StandardWrapper的基本流程

StandardWrapper是Wrapper容器的标准实现,Tomcat中使用的就是这个类,Wrapper容器是对一个servlet的包装,主要功能就是要去调用某个特定servlet的service方法。

所以不难想到,StandardWrapper首先要知道它持有的servlet是哪个,实现方式是它持有一个【String servletClass】属性,用来存放servlet的全限定名,通过 setServletClass(String servletClass) 方法可以设置这个属性值。

有了servlet的全限定名,StandardWrapper要想获取到一个servlet的实例,就必须要先对这个servlet进行类加载,要进行类加载就必须要拿到一个类加载器,而在Tomcat中类加载器是被包含一个叫做"加载器"的组件中的,加载器通常是 WebappLoader(实现了Loader接口)这个类的一个实例,StandardWrapper拿加载器的逻辑是这样的:先看自己有没有,没有的话就去拿父容器(Context容器)的,所以通常来说Tomcat会给Context容器分配一个加载器,Context底下的Wrapper小弟们都用它的就行。

public Loader getLoader() {if (loader != null) return loader;if (parent != null) return parent.getLoader();return null;}

拿到 WebappLoader 实例后就拿到了 ClassLoader 实例(类加载器),下一步很显然就是对servlet进行类加载操作,这个很简单,直接 ClassLoader.loadClass(String name) 方法就能把类加载好了,然后就是反射操作 newInstance 创建一个servlet的实例,接着servlet.service() 方法一调用,StandardWrapper的使命就基本完成了。

了解了StandardWrapper的基本工作流程后,再来看看它里面比较重要的两个设计 SingleThreadModel过滤器。

SingleThreadModel

javax.servlet.SingleThreadModel 是一个接口,没有任何方法定义,只是一个标识,用修饰servlet的具体实现类,如果一个servlet没有实现SingleThreadModel这个接口,那么在Tomcat的整个生命周期内,针对该servlet对象的StandardWrapper实例就只创建一个servlet实例,每个线程都公用这一个实例,允许并发调用。也就是说该servlet是单例的。

如果一个servlet实现了SingleThreadModel接口,那么多个线程要使用该servlet时,StandardWrapper就会为每个线程分配一个servlet实例,一个servlet实例在同一时刻只会被一个线程调用。Tomcat为了避免重复创建servlet对象耗费资源,所以在每个StandardWrapper实例中都维护了一个servlet对象池,一个线程要使用servlet对象时,就从对象池里拿一个给它用,如果池子里没有了就创建一个新的;线程使用完servlet对象后,StandardWrapper会再将其回收到对象池里。也就是说该servlet是多例的。

如果一个servlet实现了SingleThreadModel那么该servlet就是线程安全的吗?

这是一个认知误区?何为线程安全,那就是多线程并发调用的情况下,公共资源的完整性和逻辑正确性不会遭到破坏,公共资源就是多个线程都会用到的资源。而SingleThreadModel的实现逻辑只是保证了每个servlet内部持有的属性不会被其他线程破坏,但是如果该servlet用到了其他类里面的属性,那么仍然是存在线程安全问题的。

注意!SingleThreadModel这个接口在Servlet API 2.4版本(对应Tomcat 5.5.x版本)后已经被打上了 @Deprecated 标记了,不再推荐使用,但是代码逻辑仍然保留着。

上面提到的StandardWrapper的类加载、创建sevlet实例及SingleThreadModel的逻辑基本都包含在 StandardWrapper#allocate() 这个方法中,allocate() 方法的目的就是返回一个可用的 servlet 实例。

过滤器

过滤器的逻辑其实并不在StandardWrapper中,而是在它的基础阀 StandardWrapperValve 中。

先说一下StandardWrapperValve的作用。

  1. 将Catalina接收到的request与response对象转换成servlet可用的HttpServletRequest与HttpServletResponse。
  2. 调用StandardWrapper的 allocate() 方法获取到一个servlet实例。
  3. 构建过滤器链 FilterChain。
  4. 执行所有过滤器的doFilter方法,最后调用 servlet 的 service() 方法。

过滤器链 FilterChain 又是一个“责任链”机制的应用,Tomcat中用到责任链的地方还挺多,每个容器中的 Pipeline 也是应用的“责任链”调用机制。不了解 Pipeline的同学可以看看第五章的文章【深入剖析Tomcat(五) 剖析Servlet容器并实现一个简易Context与Wrapper容器】

FilterChain的调用过程大致如下图

如何自定义过滤器并加入到过滤器链中呢?

首先需要有一个自定义过滤器的类,这个类需要实现 javax.servlet.Filter 接口,比如我自定义的过滤器叫MyFilter1

public class MyFilter1 implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("MyFilter1   start");chain.doFilter(request,response);System.out.println("MyFilter1   end");}}

MyFilter1的前置逻辑和后置逻辑都比较简单,就是简单输出了一个字符串。

然后怎么使这个类生效呢?也就是说怎么做才能将这个类加到Wrapper容器的过滤器链中呢?

两种方式(基于Spring框架):

1.声明一个FilterRegistrationBean(推荐使用)

这种方式需要为自定义过滤器类创建一个bean,Tomcat的Wrapper容器在构建过滤器链时会找到这个bean,并将其加入到过滤器链中,同时声明这个Bean时也可以为它设置排序值order,过滤器链中的过滤器都是按其order值从小到大排序的。

@Configuration
public class ServletConfiguration {@Beanpublic FilterRegistrationBean<MyFilter1> myFilter() {FilterRegistrationBean<MyFilter1> registrationBean = new FilterRegistrationBean<>();registrationBean.setFilter(new MyFilter1());registrationBean.addUrlPatterns("/servlet/*");registrationBean.setOrder(2);return registrationBean;}}

2.使用@WebFilter注解

在自定义Filter类上加上 @WebFilter 注解,然后Spring启动类上加上 @ServletComponentScan 注解, Wrapper容器也能找到它并将其纳入过滤器链中,不过缺点是不能自定义排序值,order值永远是2147483647(Integer.MAX_VALUE)。有的人说配合spring的 @Order 注解就能排序了,其实不然,说这话的人肯定也没有实践过。

@WebFilter(urlPatterns = "/servlet/*")
@Order(100)  // 这个注解在这里不生效,没什么作用
public class MyFilter1 implements Filter {………
}

综合上面两种方式,我还是推荐第一种:FilterRegistrationBean 的方式,毕竟给过滤器排序在很多场景下都是需要的。

源码分析

在Tomcat中,众多过滤器其实是挂在Context容器下的,StandardContext类下有个 filterMaps 属性,该属性存放了与该Context容器相关联的多个过滤器,SpringBoot在启动时会搜寻所有过滤器并将其加入到 filterMaps 属性中。当servlet请求打进来后,Wrapper容器在组装过滤器链时会去Context容器中取 filterMaps 属性来拼装过滤器链。

接下来基于SpringBoot框架来看看过滤器生效的过程

首先启动SpringBoot,ServletWebServerApplicationContext#selfInitialize 方法中会调用 getServletContextInitializerBeans() 方法来获取所有过滤器类(我们自定义的过滤器生不生效在这里就可以看出来了),这时候就能看到我们自定义的过滤器已经被检索到了

接下来for循环中执行方法“beans.onStartup(servletContext)” ,该方法逻辑中会调用到StandardContext的 addFilterMapBefore() 或者addFilterMap() 方法,这两个方法都是给Context容器添加过滤器,逻辑大致相似,区别是将过滤器往过滤器链的头上加还是尾上加,自定义过滤器会通过 addFilterMapBefore 这个方法被添加进来。注意看这里就是往 StandardContext 的 filterMaps 属性中放值的过程。

SpringBoot启动完后,StandardContext中的filterMaps 也就放好值了。

然后这时候来了一个servlet请求,通过Context的Pipeline和Wrapper Pipeline的流转,请求来到了Wrapper的基础阀:StandardWrapperValve,StandardWrapperValve的invoke方法中会去构建一个过滤器链的对象

ApplicationFilterFactory.createFilterChain方法中会取StandardContext中的 filterMaps 属性来构建过滤器链,构建过程就不展示代码了,感兴趣的可以翻翻这块的代码。

filterChain 对象构建好后,还是在StandardWrapperValve的invoke方法中,执行 filterChain 的 doFilter 方法,该方法会按照顺序来依次执行各个过滤器的 doFilter 方法,在过滤器链的末尾调用一下servlet的service方法,拿到返回结果,到此为止,在这一次servlet请求中该Wrapper容器的任务就完成了。

过滤器链(ApplicationFilterChain)的doFilter方法会调用internalDoFilter方法,过滤器链的执行逻辑就在internalDoFilter方法中,为了表达主要意思,我将删减后的代码展示给你,执行后的效果就和上面画的【Servlet过滤器链调用示意图】一样。

private void internalDoFilter(ServletRequest request,ServletResponse response) throws IOException, ServletException {// Call the next filter if there is oneif (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];Filter filter = filterConfig.getFilter();filter.doFilter(request, response, this);return;}// We fell off the end of the chain -- call the servlet instanceservlet.service(request, response);
}

OK,StandardWrapper的功能介绍就到此为止,Wrapper容器是对某个具体servlet的一个封装,并提供了 SingleThreadModel和过滤器的机制来丰富 servlet 的功能,通过这篇文章你应该也对过滤器有了一个全面的认识,这下不会再对Tomcat的过滤器和spring的拦截器傻傻分不清楚了吧,如果你真的还不清楚的话,就等我出spring mvc的文章吧,哈哈!

下篇文章来分析下Context容器的标准实现:StandardContext,敬请期待!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/36338.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows git配置多个账号

window下git多账号配置_百度搜索 (baidu.com) 最重要的是这里生成新的id_rsa文件的时候&#xff0c;bash窗口是在 .ssh路径下 其实就是这个窗口在什么路径下执行的就是生成在什么路径 下面窗口路径不对&#xff0c;不是Desktop&#xff0c;应该是.ssh 如果是Desktop或者任何一…

2024-6-24(沉默Netty,MongoDB)

1.Netty概念 Netty 是一个基于 JAVA NIO 类库的异步通信框架&#xff0c;它的架构特点是&#xff1a;异步非阻塞、基于事件驱动、高性能、高可靠性和高可定制性。 Dubbo&#xff0c;Kafka&#xff0c;ES等框架都是基于Netty开发的&#xff0c;可以把Netty理解为进行网络编程的…

数据库管理-第209期 HaloDB-Oracle兼容性测试01(20240621)

数据库管理209期 2024-06-21 数据库管理-第209期 HaloDB兼容性测试&#xff08;20240621&#xff09;1 数据类型2 字段默认值3 序列总结 数据库管理-第209期 HaloDB兼容性测试&#xff08;20240621&#xff09; 作者&#xff1a;胖头鱼的鱼缸&#xff08;尹海文&#xff09; Or…

Arcgis地统计分析工具灰色不可用 解决方法

使用Arcmap&#xff0c;调用地统计分析工具&#xff08;Geostatistical Analyst&#xff09;下的探索数据&#xff08;Explore Data&#xff09;&#xff0c;发现工具呈灰色不可用。这是由于扩展模块中没有将该模块做勾选设置导致的。下面介绍一下如何解决地统计分析工具不可用…

小型智能驱鸟器,建筑驱鸟专用

随着城市化进程的加快&#xff0c;鸟类与人类的居住空间逐渐交织重合&#xff0c;鸟类对建筑物的侵扰问题也愈发凸显。家庭庭院、住宅窗前、屋顶&#xff0c;甚至那些承载着历史与文化底蕴的名胜古迹和精美雕像&#xff0c;都时常受到鸟粪的污染。鸟粪具有腐蚀性且很难清理&…

应用监控pinpoint调研

参考 https://blog.csdn.net/Rose_juvenile/article/details/135285508?utm_mediumdistribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-135285508-blog-132330996.235^v43^pc_blog_bottom_relevance_base6&spm1001.2101.3001.4242.1&a…

Windows中配置python3.11环境安装教程

希望文章能给到你启发和灵感&#xff5e; 如果觉得文章对你有帮助的话&#xff0c;点赞 关注 收藏 支持一下博主吧&#xff5e; 阅读指南 开篇说明一、基础环境说明1.1 硬件环境1.2 软件环境 二、软件的下载和安装2.1 下载2.2 安装2.3 验证安装结果 三、最后 开篇说明 应要求为…

DB-GPT Docker部署

感谢阅读 拉取镜像linux判断拉取是否成功的方法windows判断拉取是否成功的方法 模型以及启动容器模型启动容器 界面如下&#xff08;0.56&#xff09;&#xff1a; 拉取镜像 docker pull eosphorosai/dbgpt:latestlinux判断拉取是否成功的方法 docker images | grep "eo…

龙国南方航空滑块acw_v2+cookie+风控处理+type后缀

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未经许可禁…

中小企业的数字化转型业务场景落地案例

引言&#xff1a;随着商业活动的复杂化和全球化程度的提高&#xff0c;合同作为商业交易的重要组成部分&#xff0c;其数量、条款和复杂性都在不断增加。企业面临着越来越多的合同管理挑战&#xff0c;包括合同数量增多、条款繁琐、文件分散存储等问题。而中小企业由于管理不到…

elk对于集群实例的日志的整合-基于logstash采集日志

说明&#xff1a;基于logstash采集日志 环境&#xff1a; 物理机192.168.31.151 一.启动2个测试实例&#xff0c;每5-10s随机生成一条订单日志 实例一 包位置&#xff1a;/home/logtest/one/log-test-0.0.1-SNAPSHOT.jar 日志位置:/docker/elastic/logstash_ingest_data/l…

MYSQL存储过程的创建

关于存储过程的题目 1、创建存储过程,查看user表中的所有数据 2、创建存储过程avg_order_quantity,返回所有订单的平均工资 3、创建存储过程show_max_bprice,用来查看bookS的单价最贵的价格 4、创建存储过程show_min_bprice,用来查看bookS的单价最低的价格&#xff0c;并将…

ulimit报错

问题 执行命令“ulimit -c 2048 ”时报错&#xff1a;“bash: ulimit: core file size: cannot modify limit: Operation not permitted” 原因 权限不够。 解决办法 执行命令“sudo gedit /etc/security/limits.conf”打开文件limits.conf文件内添加内容如下&#xff1a;…

【索引】数据库索引之散列索引

目录 1、什么是散列&#xff1f; 2、如何评价一个散列函数的好坏&#xff1f; 3、散列中的桶溢出处理 4、散列在索引中的应用 4、顺序索引和散列索引的比较 1、什么是散列&#xff1f; 顺序文件组织的一个缺点是我们必须访问索引结构来定位数据&#xff0c;或者必须使用二…

软件协同开发是一种通过团队合作来创建软件的开发方法

软件协同开发是一种通过团队合作来创建软件的开发方法。与传统的瀑布模型相比&#xff0c;软件协同开发强调团队成员之间的合作和沟通&#xff0c;以实现更高效的开发过程和更优质的软件产品。 在软件协同开发中&#xff0c;团队成员通过一系列工具和技术来协同工作。这些工具…

MATLAB2024a下的BP神经网络分类工具箱预测

1 打开BP神经网络分类工具箱GUI界面 图1-1 如图1-1所示&#xff0c;虽然叫神经网络模式识别但确实是BP神经网络分类工具箱&#xff0c;如果想要使用其他神经网络模型&#xff0c;可以打开左边的深度网络网络设计器&#xff0c;如图1-2、图1-3所示&#xff1a; 图1-2 图1-3 2 导…

以入站营销为核心,撬动To B业务增长新杠杆

传统的营销模式已逐渐失效&#xff0c;企业需要寻找新的营销策略来吸引客户并推动业务增长。Outbound marketing&#xff08;出站营销&#xff09;作为一种传统的营销方式&#xff0c;虽然在过去发挥了重要作用&#xff0c;但在数字化时代&#xff0c;其局限性也日益明显。 数字…

use embeddings stored in vector db to reduce work for LLM generating response

题意&#xff1a;使用存储在向量数据库中的嵌入来表示&#xff0c;以减少大型语言模型&#xff08;LLM&#xff09;生成响应的工作量。 问题背景&#xff1a; Im trying to understand what the correct strategy is for storing and using embeddings in a vector database, …

优刻得首个「国产千卡智算集群」落地,支持智源千亿大模型训练

在人工智能引领的时代浪潮中&#xff0c;算力已成为技术进步与创新的核心驱动力。面对当下AI算力需求的飙升、高端AI芯片供应受限的挑战&#xff0c;加之OpenAI带来的技术封锁&#xff0c;唯有坚定不移的发展自主可控的国产技术方案&#xff0c;持续壮大国产智算集群规模&#…

如何在ArcGIS Pro中提取行政区划

我们在《2024版有审图号的SHP行政区划》一文中&#xff0c;为你分享过全国省市县级的行政区划。 现在再为你分享一下&#xff0c;如何在ArcGIS Pro中提取目标范围行政区划的方法&#xff0c;你还可在以文末查看领取该行政区划数据的方法。 直接选择 在菜单栏上点击一下选择下…