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

news2024/11/16 22:00:35

《深入剖析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 {
    @Override
    public 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 {

    @Bean
    public 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 one
    if (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 instance
    servlet.service(request, response);
}

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

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

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

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

相关文章

Charles 显示内存不足解决方法

弹窗出现&#xff1a;Charles is running low on memory. Recording has been stopped. Please clear the session to free memory and continue recording. 官网解决方法&#xff1a; Charles runs out of memory After recording for a while Charles will run low on ava…

智能视频监控平台智能边缘分析一体机安防监控平台吸烟检测算法应用场景

智能边缘分析一体机吸烟检测算法是一种集成了先进图像处理、模式识别和深度学习技术的算法&#xff0c;专门用于实时监测和识别公共场所中的吸烟行为。以下是关于该算法的详细介绍&#xff1a; 工作原理 1、视频采集&#xff1a; 通过安装在公共场所的摄像头&#xff0c;实时…

js实现拖拽排序

<!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>拖拽排序</title><style>* {margin: 0;p…

成为一个NB程序员,必看的5大定律!

请把这篇文章读进脑子里去&#xff0c;且在现实中用起来 除了超有意思也真的能“镀金”~~ 顺便吆喝一声&#xff0c;如果你计算机、软件工程、电子等相关专业本科及以上学历&#xff0c;欢迎来共事。前后端/测试​均可投&#xff0c;技术大厂。 定律一&#xff1a;晕轮效应 又…

头歌——机器、深度学习——手写体识别

第1关&#xff1a;神经网络基本概念 任务描述 本关任务&#xff1a;根据本节课所学知识完成本关所设置的选择题。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.神经网络基本概念。 神经网络基本概念 神经网络由输入层、隐藏层、输出层组成&#xff1b;…

IS022000与HACCP:提升食品安全管理的完美结合

国际标准化组织&#xff08;ISO&#xff09;于2005年9月发布了IS022000:2005标准&#xff0c;这是一项针对食品安全管理体系的国际标准。我国以等同采用的方式制定了国家标准GB/T 22000-2006《食品安全管理体系食品链中各类组织的要求》&#xff08;以下简称“GB/T22000”&…

# Kafka_深入探秘者(4):kafka 主题 topic

Kafka_深入探秘者&#xff08;4&#xff09;&#xff1a;kafka 主题 topic 一、kafka 主题管理 1、kafka 创建主题 topic 命令 1&#xff09;命令&#xff1a; # 切换到 kafka 安装目录 cd /usr/local/kafka/kafka_2.12-2.8.0/# 创建一个名为 heima 的 主题 bin/kafka-topic…

Java项目:基于SSM框架实现的电子竞技管理平台【ssm+B/S架构+源码+数据库+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的电子竞技管理平台 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能…

最新AI智能聊天对话问答系统源码(图文搭建部署教程)+AI绘画,文生图,TTS语音识别输入,文档分析

一、人工智能语言模型和AI绘画在多个领域广泛应用 人工智能语言模型和AI绘画在多个领域都有广泛的应用。以下是一些它们的主要用处&#xff1a; 人工智能语言模型 内容生成 写作辅助&#xff1a;帮助撰写文章、博客、报告、剧本等。 代码生成&#xff1a;自动生成或补全代码&…

python项目加密和增加时间许可证

1.bat&#xff0c;执行如下的命令&#xff0c;第一句是更新或增加许可证 第二句是加密draw_face.py python offer.py pyarmor obfuscate -O dist draw_face.py绘制自制人脸.py&#xff0c;调用加密的代码draw_face代码 import sys import os import cv2# 添加加密模块所在的路…

国内顶级汽车制造厂的创新实践:如何利用实时数据湖为更多业务提供新鲜数据?

使用 TapData&#xff0c;化繁为简&#xff0c;摆脱手动搭建、维护数据管道的诸多烦扰&#xff0c;轻量代替 OGG、DSG 等同步工具&#xff0c;「CDC 流处理 数据集成」组合拳&#xff0c;加速仓内数据流转&#xff0c;帮助企业将真正具有业务价值的数据作用到实处&#xff0c…

如何在服务器之间同步文件?

业务需求 因业务需求需要在多台服务器之间做文件资源的双向同步&#xff0c;选择 ownCloud davfs2 rsync 来实现 ownCloud ownCloud 是一个开源免费专业的私有云存储项目&#xff0c;它能帮你快速在个人电脑或服务器上架设一套专属的私有云文件同步网盘。 ownCloud 能让你…

ArkTS开发系列之导航 (2.5.2 页面组件导航)

上篇回顾: ArkTS开发系列之导航 (2.5.1 页面路由&#xff09; 本篇内容&#xff1a;主要学习页面内组件导航 一、 知识储备 1. Navigation 一般作为页面的根容器&#xff0c;包括单页面、分栏和自适应三种显示模式。 自适应模式 (NavigationMode.Auto) &#xff0c;需要注意…

三相变压器:应用和连接配置

变压器的功能和应用 变压器的类型和用途多种多样&#xff0c;可根据其应用、结构类型和尺寸进行分类。 一般来说&#xff0c;变压器的主要功能是改变交流电&#xff08;AC&#xff09;的电压水平&#xff0c;提高电压以供长距离传输或降低电压以供家庭和工业消费者使用。 它…

优先级队列模拟实现

目录 1.堆的概念 2.堆性质堆中的某个元素小于或大于他的左右孩子 3.小根堆实例 4.堆创建 4.1调整思路 4.2向下调整思路 4.3代码实现&#xff08;大根堆&#xff09; 5.堆的删除 6.堆的插入 7.常用接口 7.1PriorityQueue和PriorityBlockingQueue 1.堆的概念 如果有一…

常见硬件工程师面试题(二)

大家好&#xff0c;我是山羊君Goat。 对于硬件工程师&#xff0c;学习的东西主要和电路硬件相关&#xff0c;所以在硬件工程师的面试中&#xff0c;对于经验是十分看重的&#xff0c;像PCB设计&#xff0c;电路设计原理&#xff0c;模拟电路&#xff0c;数字电路等等相关的知识…

微服务(服务治理)

服务远程调用时存在的问题 注册中心原理 服务治理中的三个角色分别是什么&#xff1f; 服务提供者&#xff1a;暴露服务接口&#xff0c;供其它服务调用服务消费者&#xff1a;调用其它服务提供的接口注册中心&#xff1a;记录并监控微服务各实例状态&#xff0c;推送服务变更信…

软件工程体系概念

软件工程 软件工程是应用计算机科学、数学及 管理科学等原理开发软件的工程。它借鉴 传统工程的原则、方法&#xff0c;以提高质量&#xff0c;降 低成本为目的。 一、软件生命周期 二、软件开发模型 1.传统模型 瀑布模型、V模型、W模型、X 模型、H 模型 (1)瀑布模型 瀑布…

材料科学SCI期刊,中科院2区,影响因子4.7

一、期刊名称 Progress in Natural Science-Materials International 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;材料科学 影响因子&#xff1a;4.7 中科院分区&#xff1a;2区 三、期刊征稿范围 由中国材料研究会负责的同行评议 由中国材料研究会&…

java8 将对象list中的某一个属性取出组成一个list

实体类 public class Sp {String spdm;String spmc;public Sp() {}public Sp(String spdm, String spmc) {this.spdm spdm;this.spmc spmc;}public String getSpdm() {return spdm;}public void setSpdm(String spdm) {this.spdm spdm;}public String getSpmc() {return sp…