此为我对12306学习开源项目的个人部分心得和拙见,可以多多支持项目作者:马丁,项目来源地址:nageoffer.com/12306
关于ticket/query接口查询全链路追踪的结果
tomcat的线程池
在使用我们的接口的时候,肯定是发一个请求过去,在发请求的时候,首先由tomcat服务器捕获到,并在tomcat内部的线程池起一个worker任务并执行
在此可以看到,线程命名为9002,是ticket接口的部分,调用了tomcat的线程池处理任务。tomcat的池子默认大小为10,核心线程数为10,最大线程数为200,ctl为RUNNING
WrappingRunnable和Worker
WrappingRunnable
是一个包装器类,它将一个 Runnable
对象包装起来,并在其 run
方法中执行包装的 Runnable
的 run
方法。这种包装器通常用于在执行任务时添加额外的功能,比如异常处理、日志记录、任务执行前后的钩子等,而不改变原有 Runnable
的实现。
在 run
方法中,它调用包装的 Runnable
的 run
方法,并捕获任何可能发生的异常。如果发生异常,它会调用 handleException
方法来处理异常。
这种模式非常有用,因为它允许你在不修改原有 Runnable
实现的情况下,为任务执行添加额外的逻辑。例如,你可以使用 WrappingRunnable
来确保所有任务都在执行前后记录日志,或者在任务执行失败时执行一些清理工作。
之后调用Worker执行线程池任务,worker内部封装了自己的线程
当 run
方法被调用时,它实际上是在请求 ThreadPoolExecutor
执行其 runWorker
方法,并将当前的 Worker
实例作为参数传递。
SocketWrapper包装器设计
执行tomcat线程池Worker任务,进入SocketProcessorBase的方法
这个套接字socketWrapper是多线程争抢的共享资源,需要上锁。
- 获取锁:
-
Lock lock = this.socketWrapper.getLock();
:首先,从socketWrapper
对象中获取一个锁对象。这个锁对象用于控制对共享资源的访问,确保在同一时间只有一个线程可以执行临界区代码。
- 锁定:
-
lock.lock();
:调用锁的lock
方法来获取锁。如果锁当前被其他线程持有,当前线程将被阻塞,直到锁可用。
- 尝试执行任务:
-
if (!this.socketWrapper.isClosed()) {
:检查socketWrapper
是否已经关闭。如果套接字没有关闭,继续执行。this.doRun();
:调用doRun
方法来实际执行任务。这个方法包含了实际的业务逻辑,可能涉及到对套接字的读写操作。return;
:如果doRun
方法执行成功,直接返回,不再执行后续的finally
块中的代码。
- 释放锁:
-
finally {
:无论doRun
方法是否成功执行,或者是否发生异常,finally
块总是会被执行。lock.unlock();
:在finally
块中,调用锁的unlock
方法来释放锁。这是非常重要的,因为它允许其他等待锁的线程继续执行。
这种模式(也称为"锁-尝试-解锁"模式)是处理并发编程中同步问题的一种常见做法。它确保了即使在发生异常的情况下,锁也能被正确释放,从而避免了死锁和其他并发问题。
需要注意的是,socketWrapper
应该是一个自定义的包装类,它封装了套接字操作,并提供了获取锁和检查套接字状态的方法。这种设计模式有助于将同步机制与业务逻辑分离,使得代码更加清晰和易于维护。
什么叫同步机制和业务逻辑分离呢?可以理解为socket的套接字操作的业务代码是没有外部的同步机制的,外部同步机制由包装类提供,这样就使得socket套接字同步机制和业务代码分离了
执行套接字的doRun方法
这段代码是 doRun
方法的实现,它看起来是用于处理网络通信中的握手、事件处理和资源管理。这个方法可能是 NioEndpoint
类中的一部分,用于处理非阻塞I/O操作。下面是对这段代码的详细解释:
- 获取轮询器:
-
Poller poller = NioEndpoint.this.poller;
:从NioEndpoint
实例中获取Poller
对象,它负责管理I/O事件的轮询。
- 检查轮询器是否为空:
-
- 如果
poller
为空,说明轮询器可能已经被关闭或未初始化,因此需要关闭套接字。
- 如果
- 处理握手:
-
- 尝试执行TLS握手 。如果握手已完成,
handshake
被设置为0。 - 如果握手未完成,并且当前事件不是停止、断开连接或错误事件,则调用
handshake
方法进行握手,并更新事件状态。 - 如果握手失败或发生异常,
handshake
被设置为-1,并记录日志。
- 尝试执行TLS握手 。如果握手已完成,
- 根据握手结果处理事件:
-
- 如果
handshake
为0,表示握手成功,调用process
方法处理套接字(this.socketWrapper)事件(this.event) 。 - 如果
handshake
为-1,表示握手失败,处理连接失败事件并关闭套接字。 - 如果
handshake
为1或4,表示需要注册读或写事件的兴趣。
- 如果
- 异常处理:
-
- 捕获
CancelledKeyException
异常,关闭套接字。 - 捕获
VirtualMachineError
异常,调用ExceptionUtils.handleThrowable
方法处理。 - 捕获其他所有
Throwable
异常,记录错误日志并关闭套接字。
- 捕获
- 清理资源:
-
- 在
finally
块中,将socketWrapper
和event
设置为null
,释放资源。 - 如果
NioEndpoint
正在运行且processorCache
不为空,则将当前处理器推入缓存。
- 在
这段代码展示了一个复杂的网络事件处理流程,包括握手、事件处理、异常处理和资源管理。它使用了多个异常处理块来确保在发生任何异常时都能正确地关闭套接字和释放资源,这是网络编程中常见的模式,以确保资源不会被泄露。
握手成功之后去处理套接字和事件
这段代码是一个名为 process
的方法的实现,它属于一个处理抽象连接的处理器 类(AbstractEndpoint.Handler
),用于处理网络套接字事件。这个方法处理不同类型的 SocketEvent
,并根据事件类型和处理器的状态来决定如何响应。
- 日志记录:
-
- 如果调试日志开启,记录处理事件的开始。
- 检查
SocketWrapperBase
是否为空:
-
- 如果
wrapper
为空,直接返回SocketState.CLOSED
。
- 如果
- 获取处理器:
-
- 从
wrapper
中获取当前的Processor
处理器对象。
- 从
- 处理超时事件:
-
- 如果事件是
TIMEOUT
并且处理器为空或者处理器不支持异步操作,返回SocketState.OPEN
。
- 如果事件是
- 处理断开连接或错误事件:
-
- 如果处理器为空并且事件是
DISCONNECT
或ERROR
,返回SocketState.CLOSED
。
- 如果处理器为空并且事件是
- 处理器升级:
-
- 如果处理器为空,尝试根据协商的协议创建新的处理器。
- 处理处理器:
-
- 如果处理器不为空,从等待处理器列表中移除。
- 调用
processor.process
方法处理事件,并根据返回的状态执行不同的操作。
- 处理器升级和长轮询:
-
- 如果状态是
UPGRADING
,处理升级逻辑。 - 如果状态是
LONG
,执行长轮询逻辑。
- 如果状态是
- 释放和回收处理器:
-
- 如果状态是
OPEN
,释放处理器并重新注册读兴趣。 - 如果状态是
UPGRADED
或ASYNC_IO
,添加处理器到等待列表。
- 如果状态是
- 异常处理:
-
- 捕获并处理可能的
SocketException
、IOException
、ProtocolException
和OutOfMemoryError
异常。
- 捕获并处理可能的
- 释放处理器:
-
- 在方法结束前,释放处理器资源。
- 返回状态:
-
- 根据处理器处理事件的结果返回相应的
SocketState
。
- 根据处理器处理事件的结果返回相应的
这个方法是网络通信中的核心逻辑,它处理了从正常数据处理到异常处理的多种情况。它确保了在不同网络事件和处理器状态变化时,能够正确地管理资源和响应事件。代码中使用了多个 try-catch
块来确保即使在发生异常时,资源也能被正确释放,并且能够记录必要的错误信息。
可以看到这里的socketchannel的结果
最后这里是使用http11Processor处理器来处理我们的请求
CoyoteAdapter
CoyoteAdapter 是 Tomcat 中的一个核心组件,它确保了 HTTP 请求能够在 Tomcat 的连接器和 Servlet 容器之间正确地传递和处理。通过适配器模式,CoyoteAdapter 提供了必要的接口和实现,使得 Tomcat 能够灵活地处理各种 HTTP 请求和响应。
接着适配器会取出服务容器,执行对应的第一个方法
这里首先获取主机,即tomcat的实例部署主机
这里的Request类是没有host字段的,但是这个getHost方法是怎么来的呢?我们继续往下看
这个mappingData是什么东西呢?MappingData
类是 Tomcat 内部请求处理流程中的一个关键组件,它提供了一个结构化的方式来存储和管理请求映射的所有相关信息。通过 recycle
方法,Tomcat 可以高效地重用这些对象,减少垃圾收集的压力,提高服务器性能。
它属于 org.apache.catalina.mapper
包。这个类是 Apache Tomcat 服务器的一部分,用于存储和管理请求映射数据。以下是对类成员和方法的解释:
- 成员变量:
-
Host host
:存储与请求相关的主机(Host)对象。Context context
:存储与请求相关的上下文(Context)对象。int contextSlashCount
:计算上下文路径中斜杠(/
)的数量。Context[] contexts
:存储与请求匹配的所有上下文对象数组。Wrapper wrapper
:存储与请求相关的包装器(Wrapper)对象,通常对应一个 Servlet。boolean jspWildCard
:标记是否匹配了 JSP 通配符。MessageBytes requestPath
:存储请求的路径。MessageBytes wrapperPath
:存储包装器的路径。MessageBytes pathInfo
:存储路径信息。MessageBytes redirectPath
:存储重定向路径。MappingMatch matchType
:存储匹配类型,表示请求与映射规则之间的匹配程度。
invoke认证链
在执行适配器的CovoteAdapter方法时,会经过多重类的Invoke方法
1、首先经过StandardEngineValve(选择主机)
StandardEngineValve
是 Apache Tomcat 中的一个核心组件,它是 Engine
容器级别的阀门(Valve),用于处理进入 Tomcat 的所有请求。
- 功能描述:
-
StandardEngineValve
是ValveBase
的具体实现,它负责在Engine
容器级别处理请求。它的主要任务是选择适当的子Host
来处理请求,如果没有匹配的Host
,则返回 HTTP 错误。
- 请求处理:
-
- 在
StandardEngineValve
的invoke
方法中,它会根据请求的服务器名选择相应的Host
来处理请求。如果找不到合适的Host
,它会发送一个错误响应给客户端。
- 在
2、经过ErrorReportValve(错误调查)
ErrorReportValve
是 Apache Tomcat 中用于输出 HTML 错误页面的阀门(Valve)。以下是关于 ErrorReportValve
的一些关键信息:
- 功能:
-
ErrorReportValve
负责在发生错误时生成 HTML 格式的错误报告。这个阀门可以附加在Host
级别,尽管它也可以工作在Context
级别。
- 错误处理:
-
- 当
invoke
方法被调用,并且响应状态码大于或等于 400,或者抛出了未捕获的异常时,ErrorReportValve
会触发错误处理。
- 当
3、经过StandardHostValve(路由请求到主机)
- 功能:
-
StandardHostValve
是 Tomcat 中用于处理 HTTP 请求并将其路由到正确的 Web 应用程序的阀门(Valve)。它负责决定哪个应用程序应该处理收到的请求,并执行其他安全性和性能相关的任务,例如检查请求是否来自受信任的客户端并缓存 DNS 查询结果以加速请求处理。
- 请求处理:
-
- 在
StandardHostValve
的invoke
方法中,它会根据请求选择适当的Context
来处理请求。如果没有找到匹配的Context
,请求将被终止。
- 在
4、身份验证的AuthenticatorBase(进行身份验证)
AuthenticatorBase
是 Apache Tomcat 中的一个类,它提供了 Valve
接口的基本实现,该接口在 web 应用程序的部署描述符中强制执行 <security-constraint>
元素。这个类是 Authenticator
接口的一个实现,同时也是一个阀门(Valve),它可以被安装到 Context
容器的 pipeline
列表中,以便在访问 web 项目时进行身份验证。
5、StandardContextValve(容器访问)
StandardContextValve
是 Apache Tomcat 中的一个核心组件,它是 Context
容器级别的阀门(Valve),用于处理进入特定 Context
的所有请求。以下是关于 StandardContextValve
的一些关键信息:
- 功能描述:
-
StandardContextValve
是ValveBase
的具体实现,它负责在Context
容器级别处理请求。它的主要任务是确保请求被正确地映射到相应的Wrapper
(通常是 Servlet),并处理应用程序级的事件监听器。
- 请求处理:
-
- 在
StandardContextValve
的invoke
方法中,它会检查请求的路径,防止访问如/META-INF/
和/WEB-INF/
等受限资源。如果请求路径是受限的,它会直接返回 404 错误。
- 在
可以看到这里的wrapper包含了之前的选择的信息,比如tomcat选择的引擎、主机、容器和wrapper,这里的wrapper是dispatcherServlet
DispatcherServlet
是 Spring MVC 框架的核心组件,它作为前端控制器(Front Controller),负责协调和组织不同组件完成请求处理并返回响应工作。这里可以理解为因为Spring容器内置的服务器是tomcat服务器,所有tomcat服务器接受到的请求必然要调用Spring的Servlet
6、最后到StandardWrapperValve来执行invoke
StandardWrapperValve
是 Tomcat 中用于处理 Web 应用程序请求的一个重要组件,它是 StandardWrapper
实例上的基本阀门(Valve),主要负责管理 Servlet 的生命周期和执行请求处理。以下是 StandardWrapperValve
的一些核心功能和特点:
- Servlet 实例管理:
-
StandardWrapperValve
负责调用StandardWrapper
的allocate
方法来获取 Servlet 实例,并在请求处理完毕后释放 Servlet 实例。
- 过滤器链执行:
-
- 它创建与 Servlet 关联的过滤器链,并调用
doFilter
方法来执行这些过滤器,这包括调用 Servlet 的service
方法。
- 它创建与 Servlet 关联的过滤器链,并调用
- 请求处理:
-
StandardWrapperValve
在其invoke
方法中处理请求,包括检查应用程序和 Servlet 是否可用,以及处理各种异常情况。
doFilter和internalDoFilter和doFilterinternal
在我们多次执行了tomcat内部的invoke方法后,开始执行了doFilter的过程,但可以看到,我们是先使用了tomcat的doFilter和internalDoFilter,再使用Spring框架的doFilter和internalDoFilter,这还不止一次
从上图可以看到,前四次执行了相同的步骤(但是内部会有微笑的区别),第五次开始使用了12306自定义的UserTransmitFilter过滤器,第六次不执行Spring框架的两个Filter方法,第七次只执行了tomcat的两个Filter方法。为什么要执行这么多次doFilter?为什么每次还不一样?为什么在第五次执行自定义的过滤器?接下来我们继续看。
首先我们要明确一下,tomcat和spring框架的doFilter方法和internal方法是有区别的,tomcat是doFilter和internalDoFilter,而spring框架的是doFilter和doFilterinternal。
tomcat的doFilter和internalDoFilter
- doFilter 方法:
-
doFilter
是FilterChain
接口的一部分,它是 Java Servlet API 的一部分。所有的过滤器链实现都必须实现这个方法。- 在 Tomcat 的
ApplicationFilterChain
类中,doFilter
方法被用来启动过滤器链的执行。它调用internalDoFilter
方法来处理请求和响应。
- internalDoFilter 方法:
-
internalDoFilter
是 TomcatApplicationFilterChain
类中的一个私有方法,它负责实际的过滤器链遍历和调用。- 这个方法检查当前位置
pos
是否小于过滤器总数n
,如果是,则获取下一个ApplicationFilterConfig
,调用其Filter
的doFilter
方法,并传递当前的请求和响应对象以及当前的FilterChain
实例。 - 如果所有的过滤器都已经执行完毕(即
pos
大于或等于n
),internalDoFilter
会调用目标Servlet
的service
方法来处理请求。
可以看到这里的过滤器总数是6个,是哪6个呢?
可以看到这里的过滤器是上图对应的六个过滤器,里面包含了过滤器类的路径,说明tomcat服务器里面的应用程序过滤器配置是包含了自己本类和其他类的过滤配置的。所以我们先调用了springwebfilter里面的characterEncodingFilter
WsFilter
第六次的时候,执行了WsFilter的doFilter
WsFilter
是 Apache Tomcat 中用于处理 WebSocket 连接的服务器端过滤器。以下是它的两个主要功能和特点:
- WebSocket 连接处理:
-
WsFilter
负责处理 WebSocket 连接的初始 HTTP 请求 。它检查传入的请求是否为 WebSocket 协议升级请求,即检查请求头中是否包含Upgrade: websocket
。如果是,WsFilter
会触发 WebSocket 握手过程 ,将请求从 HTTP 协议升级到 WebSocket 协议。
- 过滤器链调用:
-
WsFilter
实现了Filter
接口中的doFilter
方法,该方法会在每次请求/响应对通过过滤器链时被调用。WsFilter
通过FilterChain
对象将请求和响应传递给链中的下一个实体,或者阻止请求处理。
WsFilter
是 Tomcat WebSocket 支持的关键组件,它确保 WebSocket 连接可以被正确地建立和处理。当服务器接收到一个 WebSocket 客户端请求时,WsFilter
会判断该请求是否为 WebSocket 协议升级请求,并根据请求路径查找对应的 Endpoint
处理类,然后进行协议升级。这个过滤器还执行一些其他功能,例如处理 HTTP 请求中的 WebSocket 升级头,并确保响应头正确设置。此外,它还为每个 WebSocket 连接创建一个独立的线程,并在服务器端处理 WebSocket 消息。
它是一个过滤器(Filter)的核心方法,通常用在处理WebSocket请求的WsFilter
中。这个方法决定了是否将HTTP请求升级为WebSocket连接。以下是对这段代码的详细解释:
- 检查WebSocket端点注册:
-
if (this.sc.areEndpointsRegistered() && UpgradeUtil.isWebSocketUpgradeRequest(request, response))
:首先检查是否有WebSocket端点注册在服务器上,并且当前请求是否是一个WebSocket升级请求。this.sc
是ServerContainer
的一个实例,它负责管理WebSocket端点。
- 类型转换:
-
HttpServletRequest req = (HttpServletRequest)request;
和HttpServletResponse resp = (HttpServletResponse)response;
:将ServletRequest
和ServletResponse
转换为HttpServletRequest
和HttpServletResponse
,以便访问HTTP特定的方法。
- 获取请求路径:
-
String pathInfo = req.getPathInfo();
获取请求的路径信息。String path;
声明一个变量来存储完整的请求路径。- 如果
pathInfo
为null
,则path
等于req.getServletPath()
;否则,path
是servletPath
和pathInfo
的组合。
- 查找映射结果:
-
WsMappingResult mappingResult = this.sc.findMapping(path) ;
:使用ServerContainer
的findMapping
方法查找与请求路径匹配的WebSocket映射结果。
- 处理映射结果:
-
- 如果
mappingResult
为null
,表示没有找到匹配的WebSocket端点,因此调用chain.doFilter(request, response);
将请求传递给过滤器链中的下一个过滤器。 - 如果找到了映射结果,使用
UpgradeUtil.doUpgrade
方法来升级HTTP连接到WebSocket连接。mappingResult.getConfig()
和mappingResult.getPathParams()
提供了映射配置和路径参数。
- 如果
- 处理非WebSocket请求:
-
- 如果最初的
if
条件不满足(即没有注册WebSocket端点或当前请求不是WebSocket升级请求),同样调用chain.doFilter(request, response);
将请求传递给下一个过滤器,这意味着请求将被当作普通HTTP请求处理。
- 如果最初的
这个方法是WebSocket握手过程的关键部分,它确保了只有正确的请求会被升级为WebSocket连接,而其他请求将继续沿着过滤器链进行处理。
Spring OncePerRequestFilter的doFilter和doFilterinternal
前四次,tomcat执行的是两个filter的ApplicationFilterChain链,而spring框架在前四次中,doFilter执行的都是OncePreRequestFilter,doFilterinternal执行的是CharacterEncodingFilter、ServerHttpObservationFilter、FormContentFilter和RequestContextFilter。
我们来看看Spring的doFilter
doFilter
- doFilter 方法:
-
doFilter
是OncePerRequestFilter
类中实现Filter
接口的方法。它负责判断当前请求是否已经经过过滤,如果没有,则执行过滤逻辑。(过滤请求只会执行一次)- 在
doFilter
方法中,通过检查请求属性(request attribute) 来判断过滤器是否已经执行过。如果请求属性中已经标记了"already filtered",则不会再次执行过滤逻辑,直接将请求传递给下一个过滤器或目标资源。 - 这个方法使用了一个请求属性来标记请求是否已经被过滤,这个属性的名称可以通过
getAlreadyFilteredAttributeName
方法获取,默认是过滤器实例名加上 ".FILTERED" 后缀。
doFilterinternal
- doFilterInternal 方法:
-
doFilterInternal
是OncePerRequestFilter
中的一个抽象方法,它需要在子类中实现具体的过滤逻辑。- 这个方法与
doFilter
方法的合同相同,但它保证在每个请求中只被调用一次。 doFilterInternal
提供了HttpServletRequest
和HttpServletResponse
参数,而不是ServletRequest
和ServletResponse
,使得在实现过滤逻辑时更加方便和安全。
CharacterEncodingFilter
为请求设置encoding编码方式,默认为utf-8
因为前面的方法执行都传入了过滤链,所以方法执行会递归到tomcat的dofilter方法继续执行完过滤器链剩下的内容。
ServerHttpObservationFilter
这个过滤器的主要目的是观察和记录 HTTP 请求和响应的相关数据,通常用于监控、日志记录、性能评估等场景。
可以看到当前过滤器给请求创建了一个观察Observation,其中Observation有两种实现Noop和Simple
在 Spring Framework 中,观察性(Observability)支持可以通过 ServerHttpObservationFilter
实现,它提供了两种实现模式:Noop 和 Simple。
- Noop 实现:
-
- Noop(No Operation)模式是一种空实现,它不会执行任何实际的观察性操作。这种模式通常用于测试或在不需要观察性信息时,以避免引入额外的性能开销。在这种模式下,
ServerHttpObservationFilter
不会对请求和响应进行任何监控或记录,相当于一个空的过滤器链。
- Noop(No Operation)模式是一种空实现,它不会执行任何实际的观察性操作。这种模式通常用于测试或在不需要观察性信息时,以避免引入额外的性能开销。在这种模式下,
- Simple 实现:
-
- Simple 实现提供了基本的观察性功能,它会记录关键的请求和响应数据,如HTTP请求方法、状态码、URI等。这种模式适用于需要基本监控和日志记录的场景。Simple 实现会使用默认的
KeyValues
来创建观察性数据,包括错误信息、方法名称、结果和状态码等(见下图)。
- Simple 实现提供了基本的观察性功能,它会记录关键的请求和响应数据,如HTTP请求方法、状态码、URI等。这种模式适用于需要基本监控和日志记录的场景。Simple 实现会使用默认的
这两种模式为不同的使用场景提供了灵活性。Noop 实现适合于性能敏感或不需要观察性数据的环境,而 Simple 实现则为需要基本监控的应用程序提供了一个轻量级的解决方案。我们可以看到在观察中注册registry的Simple实现,就是键值对格式。
FormContentFilter
Spring框架中的FormContentFilter
是一个用于处理HTTP请求中表单提交内容的过滤器,特别是对于那些非POST请求(如PUT、DELETE、PATCH)提交的表单数据。
可以看到我们这次提交的表单为空
FormContentFilter
的主要目的是确保即使在非POST请求中,也可以通过ServletRequest.getParameter*()
系列方法获取表单参数。这对于处理HTTP PUT、DELETE和PATCH请求中的表单数据特别有用,因为默认情况下,Servlet规范只要求解析POST请求的表单数据。
RequestContextFilter
RequestContextFilter
是 Spring 框架中一个非常重要的过滤器(拦截器),其主要功能和用途如下:
- 请求上下文绑定:
-
RequestContextFilter
是一个Servlet Filter
,它的作用是将当前 HTTP 请求绑定到当前线程。 这样做的目的是让在当前线程中任何没有当前请求对象为参数的地方也可以使用RequestContextHolder
获得当前请求对象及其属性。
- Locale 和请求属性暴露:
-
- 该过滤器通过
LocaleContextHolder
和RequestContextHolder
暴露本地化上下文对象LocaleContext
和请求上下文RequestAttributes
,使得在整个请求处理过程中可以方便地访问这些上下文信息。
- 该过滤器通过
- 线程局部变量:
-
RequestContextFilter
使用ThreadLocal
变量来存储请求和本地化信息,确保这些信息在多线程环境中的线程安全性。
- 子线程继承:
-
RequestContextFilter
提供了一个属性threadContextInheritable
,允许配置是否将LocaleContext
和RequestAttributes
设置为可以被子线程继承。这主要用于那些在请求处理过程中生成的后台线程,这些线程只在请求处理期间使用一次。
- 配置和使用:
-
- 在 Spring Boot 应用程序中,
RequestContextFilter
默认已经包含在内,通常不需要手动配置。但如果需要自定义过滤器的顺序或参数,可以通过FilterRegistrationBean
进行配置。
- 在 Spring Boot 应用程序中,
- 应用场景:
-
RequestContextFilter
特别适用于需要在非 Web 请求线程中访问当前请求上下文信息的场景,或者在 Spring MVC 控制器以外的地方访问当前请求的上下文信息。
通过使用 RequestContextFilter
,开发者可以在请求处理的任何地方方便地获取请求相关的信息,如请求参数、会话、身份验证信息等,而无需将请求对象作为参数到处传递,这大大提高了代码的整洁性和可维护性。
UserTransmitFilter!!!!
UserTransmitFilter
是一个实现了 Filter
接口的过滤器类。它主要用于在 HTTP 请求处理过程中,从请求头中提取用户信息,并将这些信息设置到 UserContext
中,以便在后续的请求处理过程中可以方便地访问当前用户的上下文信息。同时,它还在请求处理完毕后,清理 UserContext
中的用户信息,以避免潜在的内存泄漏。
可以看到其实现了Filter接口,此时我们就知道了实现了Filter接口的实例类会进入tomcat的ApplicationFilterConfig实例中参与多个拦截器的调用
DispatcherServlet
到第七次的时候,此时我们的pos下标的值7已经大于6了,就会退出调用,去执行tomcatwrapper对应的servlet组件了,比如现在的DispatcherServlet
首先进入HttpServlet执行请求
之后跳转到FrameworkServlet执行serivce,这里发现这个请求是有方法的(GET方法),就去调用父类(HttpServlet)的service方法,父类可以解决的事情,就不用我子类去做
此时执行doGet方法执行Http请求的方法
这里又转为调用子类FrameworkServlet的doGet方法了,并执行processRequest方法。
processRequest
这段代码是 FrameworkServlet
类中的 processRequest
方法的实现,它是 Spring 框架中处理 HTTP 请求的核心方法之一。这个方法负责初始化请求处理环境、执行请求处理、以及在请求完成后进行清理和日志记录。以下是对这段代码的详细解释:
- 性能监控:
-
long startTime = System.currentTimeMillis();
:记录请求开始处理的时间,用于计算请求处理的总时间。
- 异常处理:
-
Throwable failureCause = null;
:用于存储请求处理过程中抛出的任何异常,以便后续可以进行日志记录或其他处理。
- 本地化上下文:
-
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
:保存请求处理前的本地化上下文。LocaleContext localeContext = this.buildLocaleContext(request);
:根据请求构建新的本地化上下文。
这里新构建的上下文是DispatcherServlet
- 请求属性:
-
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
:保存请求处理前的请求属性。
之前的请求属性会被封装到RequestFacade中,RequestFacade
是 Apache Tomcat 中的一个类,它是 HttpServletRequest
接口的一个实现。RequestFacade
提供了一个用于操作 HTTP 请求信息的高级抽象,包括请求头、请求参数、路径信息等。
-
ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
:构建新的请求属性对象。
- 异步处理:
-
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
:获取请求的异步管理器。
-
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
:注册一个可调用拦截器,用于处理请求绑定。
这个RequestBindingInterceptor拦截器的主要作用是在异步请求处理前后初始化和重置 Spring 的上下文持有者 ,即 LocaleContextHolder
和 RequestContextHolder
。
RequestContextHolder
和 LocaleContextHolder
是 Spring 提供的两个重要的上下文持有者,它们分别用于存储请求相关的属性和本地化信息。RequestBindingInterceptor
确保这些上下文在异步请求处理期间被正确地设置和清除。
- 初始化上下文持有者:
-
this.initContextHolders(request, localeContext, requestAttributes);
:初始化本地化上下文和请求属性持有者。
上下文为DispatcherServlet。
- 执行请求处理:
-
this.doService(request, response);
:调用doService
方法实际处理请求。
- 异常处理:
-
catch
块捕获IOException
、ServletException
和其他Throwable
异常,设置failureCause
并重新抛出异常。
- 清理和日志记录:
-
finally
块确保无论请求处理是否成功,都会执行清理和日志记录操作。this.resetContextHolders(request, previousLocaleContext, previousAttributes);
:重置本地化上下文和请求属性持有者。requestAttributes.requestCompleted();
:标记请求属性为已完成。this.logResult(request, response, (Throwable)failureCause, asyncManager);
:记录请求处理结果。this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
:发布请求处理事件。
这个方法体现了 Spring 框架中请求处理的典型模式,包括请求的初始化、执行、异常处理和清理。通过这种方式,Spring 确保了请求处理的一致性和可管理性,同时也提供了丰富的扩展点,如拦截器和事件发布。
DispatcherServlet
我们来看看DispatcherServlet是做什么的
DispatcherServlet
是 Spring 框架中的核心组件,它是 Spring MVC 的前端控制器(Front Controller),负责处理所有的 HTTP 请求和响应。以下是 DispatcherServlet
的一些关键特性和作用:
- 请求分发:
-
DispatcherServlet
接收进入的 HTTP 请求(比如我的查询请求URL就是DispatcherServlet来接受并分发给ticketController的),并根据 URL 和配置将请求分发到相应的控制器 (Controller)进行处理。
- 处理器映射:
-
- 它使用
HandlerMapping
接口来确定哪个 Controller 应该处理这个请求 。HandlerMapping
根据请求的 URL 和其他条件(如请求方法)选择合适的 Controller。
- 它使用
- 处理器适配器:
-
- 一旦确定了 Controller,
DispatcherServlet
使用HandlerAdapter
接口来调用 Controller 的处理方法。HandlerAdapter
负责将请求数据转换为 Controller 方法的参数,并调用 Controller。
- 一旦确定了 Controller,
- 视图解析:
-
- Controller 方法执行后,通常返回一个
ModelAndView
对象,其中包含模型数据和视图名称。DispatcherServlet
使用ViewResolver
接口将视图名称解析为具体的视图实现,以便生成响应。
- Controller 方法执行后,通常返回一个
- 拦截器集成:
-
DispatcherServlet
支持与HandlerInterceptor
接口集成,允许在请求的多个阶段(如请求前、请求后、视图渲染后)执行自定义逻辑。
- 异常处理:
-
- 它还负责处理 Controller 抛出的异常,通常通过
HandlerExceptionResolver
接口来完成。
- 它还负责处理 Controller 抛出的异常,通常通过
- 配置和初始化:
-
- 在 Spring 应用程序中,
DispatcherServlet
通常通过 Java 配置或 XML 配置文件进行配置。它需要被声明为一个 Servlet 并映射到特定的 URL 模式。
- 在 Spring 应用程序中,
- Spring Boot 集成:
-
- 在 Spring Boot 应用程序中,
DispatcherServlet
的初始化和配置是由 Spring Boot 的自动配置机制完成的。Spring Boot 提供了一个DispatcherServlet
的自定义实现,FrameworkServlet
,它自动配置了许多与 MVC 相关的属性。
- 在 Spring Boot 应用程序中,
- 请求处理流程:
-
- 请求处理流程大致包括:接收请求、寻找
HandlerMapping
、调用HandlerAdapter
、处理视图解析、返回响应。
- 请求处理流程大致包括:接收请求、寻找
DispatcherServlet
作为 Spring MVC 的中心,它不仅处理请求和响应,还负责整个请求生命周期的管理,包括拦截器的执行和视图的解析。通过理解 DispatcherServlet
的工作原理,可以更好地控制和优化 Web 应用程序的请求处理流程。
doService方法
doDispatch方法
doDispatch
方法负责整个请求的生命周期,从检查请求是否为多部分请求(如文件上传),到查找处理器(Handler),执行处理器,以及处理异步请求。
doDispatch中,它使用 HandlerMapping
接口来确定哪个 Controller 应该处理这个请求 。HandlerMapping
根据请求的 URL 和其他条件(如请求方法)选择合适的 Controller。
你可以发现,这个拦截器链是针对某个类的某个方法的,比如ticketcontroller的分页查询方法,加上4个拦截器,太离谱了
这里通过mappedHandler找到了对应使用的Bean,这里还是使用了cglib动态代理的方式实现了Aop代理,同时把TicketServiceImpl对应需要的实现类找出来了
boolean multipartRequestParsed = false;
:标记请求是否为多部分请求并已解析。
同时在获取Handler结果的时候,调用了四个拦截器
我们看看这个handler究竟在搞什么东东
获取适配器
在 Spring MVC 框架中,ModelAndView
是一个非常重要的类,它用于封装处理 HTTP 请求后的结果,包括模型数据(Model)和视图信息(View)。
方法的适配器
handleInternel
在handleInternel方法中,其会执行ModelAndView视图类对象的invokeHandlerMethod方法
这个handlerMethod是什么?就是我们刚刚为查询方法生成含有四个拦截器的处理链
这段代码是 Spring MVC 中 FrameworkServlet
类的 handleInternal
方法的实现。这个方法是处理 HTTP 请求的核心逻辑,它负责调用控制器方法(由 HandlerMethod
表示)并处理返回的 ModelAndView
对象。以下是对这段代码的详细解释:
- 检查请求:
-
this.checkRequest(request);
:检查请求是否有效,例如检查是否为异步请求。
- 同步会话:
-
if (this.synchronizeOnSession) { ... }
:如果设置了会话同步(synchronizeOnSession
),则在会话级别同步请求处理,以确保线程安全。
- 获取会话:
-
HttpSession session = request.getSession(false);
:尝试获取当前请求的 HTTP 会话,不创建新的会话。
- 会话同步块:
-
- 如果存在会话,使用
WebUtils.getSessionMutex(session)
获取会话互斥锁,并在同步块中调用控制器方法。 Object mutex = WebUtils.getSessionMutex(session);
:获取会话互斥锁对象。synchronized(mutex) { ... }
:确保在同步块中执行控制器方法,避免并发问题。
- 如果存在会话,使用
- 调用控制器方法:
-
mav = this.invokeHandlerMethod(request, response, handlerMethod);
:调用控制器方法并返回ModelAndView
对象。
- 缓存控制:
-
if (!response.containsHeader("Cache-Control")) { ... }
:检查响应是否已经包含Cache-Control
头,如果没有,则进行缓存控制。
- 会话属性缓存:
-
if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { ... }
:如果处理器方法有会话属性,则应用缓存秒数。this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
:设置响应的缓存秒数。
- 准备响应:
-
else { this.prepareResponse(response); }
:如果没有会话属性,调用prepareResponse
方法准备响应。
- 返回
ModelAndView
:
-
return mav;
:返回控制器方法处理的结果,即ModelAndView
对象。
这个方法体现了 Spring MVC 中请求处理的典型模式,包括会话同步、控制器方法调用、缓存控制和响应准备。通过这种方式,Spring 确保了请求处理的一致性和可管理性,同时也提供了丰富的扩展点,如拦截器和事件发布。
invokeHandlerMethod
这段代码是 Spring MVC 中 FrameworkServlet
类的 invokeHandlerMethod
方法的实现。这个方法负责实际调用控制器(由 HandlerMethod
表示)的方法,并处理返回的结果。以下是对这段代码的详细解释:
- 创建 Web 请求对象:
-
ServletWebRequest webRequest = new ServletWebRequest(request, response);
:创建一个ServletWebRequest
对象,用于封装HttpServletRequest
和HttpServletResponse
。- 创建一个webRequest,封装我们的uri资源路径
- 创建数据绑定工厂:
-
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
:获取用于创建DataBinder
的工厂。
- 创建模型工厂:
-
ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
:创建用于初始化模型的工厂。
- 创建可调用处理器方法:
-
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
:创建一个可调用的处理器方法对象。- 获取针对分页查询请求的处理器方法
- 设置参数解析器和返回值处理器:
-
- 如果存在自定义的参数解析器或返回值处理器,则设置到
invocableMethod
中。
- 如果存在自定义的参数解析器或返回值处理器,则设置到
- 设置数据绑定工厂和参数发现器:
-
invocableMethod.setDataBinderFactory(binderFactory);
和invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
:设置数据绑定工厂和参数名称发现器。
- 初始化模型和视图容器:
-
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
:创建模型和视图容器。modelFactory.initModel(webRequest, mavContainer, invocableMethod);
:初始化模型。
现在视图容器还是空的,因为还没有开始执行业务代码。
- 异步请求处理:
-
- 创建异步 Web 请求对象,并设置超时时间。
- 获取异步管理器,并设置任务执行器、异步 Web 请求对象、可调用拦截器和延迟结果拦截器。
- 处理异步结果:
-
- 如果异步管理器有并发结果,从异步管理器中获取结果,并清除并发结果。
- 包装异步结果:
-
invocableMethod = invocableMethod.wrapConcurrentResult(result);
:如果存在异步结果,包装到invocableMethod
中。
- 调用处理器方法:
-
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
:调用处理器方法,并处理返回结果。
- 返回 ModelAndView:
-
- 如果异步处理已经开始,则返回
null
;否则,从模型和视图容器中获取ModelAndView
对象并返回。
- 如果异步处理已经开始,则返回
invokeAndHandle
从这一步开始,准备进入动态代理获取Bean的阶段
doInvoke
首先获取多个方法
底层使用反射来获取Bean
这里我们要执行Bean的方法,方法的参数是我们在页面给出的两个地区
接下来进入反射java.Reflect Method类的invoke方法
首先我们要知道,java.lang.reflect和java.internal.reflect这两个有什么区别?同时delegate是什么?
在 Java 中,java.lang.reflect
是 Java 核心库的一部分,提供反射相关的类和接口。反射是一种强大的机制,允许程序在运行时访问和操作类和对象的内部属性,如字段、方法和构造函数。
以下是 java.lang.reflect
包中一些核心的类和接口:
Class
:表示类的实例,是反射的核心。Field
:表示类的成员变量。Method
:表示类的方法。Constructor
:表示类的构造函数。Array
:提供动态创建和访问 Java 数组的方法。Modifier
:提供类和成员的访问权限和修饰符信息的工具类。
java.internal.reflect
并不是 Java 标准库中的一个有效包。在 Java 9 之前,java.*
包下的内部类和接口都是放在 sun.*
包下,这是 Sun 公司的内部 API,不推荐开发者使用,因为它们可能在不同版本的 Java 中发生变化,不具备向后兼容性。
从 Java 9 开始,这些内部 API 被移动到了 java.internal.*
包下,这是为了区分公开的 API 和内部实现细节。java.internal.*
包下的类和接口仍然是不推荐使用的,因为它们是 Java 运行时的内部实现细节,可能会在不同版本之间发生变化。总的来说,java.lang.reflect
是标准的反射 API,而 java.internal.reflect
是 Java 内部使用的包,不应用于普通的应用程序开发中。开发者应该只使用 java.lang.reflect
包来实现反射相关的功能。
delegate
在 Java 中,"delegate"(代理或委托)是一种设计模式,它允许一个对象(称为代理)将某些任务委托给另一个对象(称为目标或被代理对象) 。这种模式在 Java 编程中非常常见,并且是 Java 框架中许多功能的基础,如 Spring AOP(面向切面编程)。
代理模式的主要目的是:
- 控制访问:
-
- 代理可以控制对实际对象的访问,提供额外的安全检查或日志记录。
- 延迟初始化:
-
- 代理可以延迟目标对象的创建,直到它实际需要时才进行初始化。
- 增加额外的功能:
-
- 代理可以在不修改目标对象代码的情况下,为目标对象增加额外的功能。
- 优化性能:
-
- 代理可以缓存目标对象的结果,减少对目标对象的直接调用,从而提高性能。
- 实现功能解耦:
-
- 代理可以将功能实现与接口定义分离,提高代码的灵活性和可维护性。
在 Java 中,代理模式可以通过以下几种方式实现:
- 静态代理:
-
- 通过创建一个实现了相同接口的类来实现代理,这个类持有目标对象的引用,并在调用目标对象的方法前后添加额外的逻辑。
- JDK动态代理(Java 反射):
-
- 使用
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来动态地创建代理对象。这种方式不需要显式地创建代理类,而是在运行时动态生成。
- 使用
- CGLIB 代理:
-
- 对于那些没有实现接口的类,可以使用 CGLIB 库来创建代理。CGLIB 通过使用字节码生成技术来创建目标类的子类,从而实现代理功能。
- AspectJ 代理:
-
- AspectJ 是一个面向切面编程的框架,它允许开发者使用注解或 XML 配置来声明切面和通知(Advice),这些通知可以在目标方法执行前后执行。
代理模式是 Java 中实现模块化和功能增强的强大工具,它在许多框架和库中都有广泛的应用。
其代理模式调用了NativeMethodAccessorImpl的invoke0方法,NativeMethodAccessorImpl
是 Java 反射机制中的一个类,它位于 java.lang.reflect
包下。这个类是 MethodAccessor
接口的一个实现,用于在反射中调用 Java 方法。以下是关于 NativeMethodAccessorImpl
的一些关键信息:
- 作用:
-
NativeMethodAccessorImpl
负责在反射调用中执行实际的方法调用。它是MethodAccessor
接口的一个实现,该接口定义了invoke
方法,用于执行反射调用。
- 实现细节:
-
NativeMethodAccessorImpl
在第一次调用invoke
方法时被创建,并更新给Method
对象的methodAccessor
属性。之后,Method
对象的所有invoke
方法调用都会委托给这个NativeMethodAccessorImpl
实例。
- 性能优化:
-
NativeMethodAccessorImpl
是用 native code 实现的,这意味着它的执行速度相对较快。然而,在调用一定次数(默认15次)之后,Java 会生成一个GeneratedMethodAccessorXXX
类,这是一个 Java 实现的MethodAccessor
,用于替代NativeMethodAccessorImpl
,以减少 native 方法调用的性能开销。
- 生成 Java 实现:
-
- 超过调用次数阈值后,
NativeMethodAccessorImpl
会触发生成一个GeneratedMethodAccessorXXX
类,这是一个动态生成的 Java 类,用于后续的反射调用。这个类是通过MethodAccessorGenerator
生成的,它负责创建字节码并实例化新的MethodAccessor
。
- 超过调用次数阈值后,
- 调用流程:
-
- 所有的反射方法调用最初都会通过
NativeMethodAccessorImpl
,然后根据调用次数可能切换到GeneratedMethodAccessorXXX
。这个切换过程是由DelegatingMethodAccessorImpl
管理的,它是一个代理类,负责在NativeMethodAccessorImpl
和GeneratedMethodAccessorXXX
之间进行切换。
- 所有的反射方法调用最初都会通过
- 源码注释:
-
NativeMethodAccessorImpl
的源码注释说明了它的用途:"Used only for the first few invocations of a Method; afterward, switches to bytecode-based implementation",意味着它只在方法的前几次调用中使用,之后会切换到基于字节码的实现。
NativeMethodAccessorImpl
是 Java 反射机制中的一个重要组件,它提供了高性能的方法调用实现,并在多次调用后优化为纯 Java 实现,以提高性能和减少对 native 方法的依赖。
这段代码是 NativeMethodAccessorImpl
类中的 invoke
方法的实现,它是 java.lang.reflect
包的一部分。这个方法是 MethodAccessor
接口的核心,用于通过反射调用 Java 方法。以下是对这段代码的详细解释:
- 方法签名:
-
invoke
方法接收三个参数:obj
(方法调用的目标对象),args
(方法调用的参数数组),并可能抛出IllegalArgumentException
和InvocationTargetException
异常。
- 性能优化逻辑:
-
if (++numInvocations > ReflectionFactory.inflationThreshold() && !method.getDeclaringClass().isHidden() && generated == 0 && U.compareAndSetInt(this, GENERATED_OFFSET, 0, 1))
:这段代码检查是否达到了反射调用的阈值(由ReflectionFactory.inflationThreshold()
定义),并且方法所在的类不是隐藏类,以及是否已经生成了替代的MethodAccessorImpl
。如果所有条件都满足,并且compareAndSetInt
成功地将generated
标记设置为 1,则会生成一个新的MethodAccessorImpl
实例。
- 生成新的 MethodAccessorImpl:
-
MethodAccessorImpl acc = (MethodAccessorImpl) new MethodAccessorGenerator().generateMethod(...)
:使用MethodAccessorGenerator
生成一个新的MethodAccessorImpl
实例,这个新实例将用于后续的方法调用。生成的方法访问器基于目标方法的类、名称、参数类型、返回类型、异常类型和修饰符。
- 设置代理:
-
parent.setDelegate(acc)
:将新生成的MethodAccessorImpl
实例设置为当前NativeMethodAccessorImpl
实例的代理,这样后续的调用将直接委托给这个新实例。这个生成的类是为了优化反射调用的性能,通过减少 native 方法调用的次数来提高效率。这是 Java 反射机制内部优化的一部分,与 CGLIB 无关。
- 异常处理:
-
catch (Throwable t) { ... }
:如果在生成新的方法访问器时发生异常,会捕获这个异常,将generated
标记恢复为 0,并重新抛出异常。
- 调用原始方法:
-
return invoke0(method, obj, args)
:如果不需要生成新的方法访问器,或者在生成过程中没有发生异常,最终会调用invoke0
方法,这是实际执行方法调用的本地方法。
这段代码展示了 Java 反射机制中的性能优化策略,即在一定次数的反射调用之后,动态生成一个基于 Java 字节码的方法访问器,以提高后续调用的性能。这种策略有助于在需要高性能和灵活性的反射调用中找到平衡。
这里要提醒一下,我们的动态代理JDK或者CGLIB代理处理器实现的接口分别是InvocationHandler和MethodInterceptor,这里是代理的处理器,并不是具体的实现类,CGLIB的代理类实例是enhancer,而JDK的实例是调用Proxy类的newProxyInstance方法实现的
arduino
MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
new MyInvocationHandler(new MyInterfaceImpl())
);
// 使用 CGLIB 创建代理对象
public class CglibProxyDemo {
public static void main(String[] args) {
// 创建 Enhancer 对象
Enhancer enhancer = new Enhancer();
// 设置被代理类的 Class
enhancer.setSuperclass(MyClass.class);
// 设置回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
MyClass proxyInstance = (MyClass) enhancer.create();
// 通过代理对象调用方法
proxyInstance.doSomething();
}
}
Result pageListTicketQuery
首先我们会进入创建返回实体的内容,之后将请求走查询责任链,将请求的信息进行过滤
首先验证参数(参数非空责任链)是否为空,不为空则进入规则责任链(出发日期不能小于当前日期,出发地不能小于当前日期等),最后进入查询列车车票流程过滤器,验证数据是否正确。如果缓存有数据,我们就去缓存中获取数据查询结果,否则上分布式锁和双重判定去数据库里查询数据,最后将数据回写到缓存中去
首先去缓存组件获取我们的缓存实例,看看缓存组件是怎么干活的
首先写一个哈希的操作,通过哈希操作去hmget我们的路线结果
我们将请求过滤之后,发现我们请求中的两个参数都在对应的query_all_region_list中存在,故返回结果,接下来走缓存去查询对应的链路。
首先去查地区编号和国家地区站点的映射表,进行更新。之前刚才的站点查询过滤器虽然执行的流程和这个一样,但是只是验证编号的合法性而没有存储信息,这时候就是需要站点信息的,所以去缓存查对应的站点。比如请求发的是BJP,我们更换成了北京
去查站点的映射,如果缓存中没有,就上分布式锁加双重判定的方式去查数据库,将数据库的列车关系(t_train_station_relation)回写到缓存中。
找到了对应三条信息
之后我们就要根据我们查出的路线,去找路线对应的价格、座位类型,最后还要查询座位的数量。这里使用的是Cache的实例,其获取不到数据内部会自动去查数据库回写
我们去查座位的数量,内部使用seatMarginCacheLoader
缓存的数量座位查询,因为是使用seatMarginCacheLoader加载的,一次性加载完全部的座位,只需要查询即可。
之后返回响应的封装结果
SpringAOP动态代理
在这里我们看到returnDTO之后,进入了一个动态拦截器(这个是CglibAopProxy类里面的!!)
DynamicAdvisedInterceptor
是 Spring AOP 框架中处理 CGLIB 代理的核心类,它确保了 AOP 增强逻辑能够正确地应用到目标对象的方法调用中。通过使用 DynamicAdvisedInterceptor
,Spring AOP 提供了强大的动态代理功能,使得开发者可以在不修改目标对象代码的情况下,为其添加额外的功能。
可以看到这个类实现了cglib里面的MethodInterceptor,所以Spring AOP的代理是基于cglib的动态代理,增强的目标类为ticketServiceImpl,执行方法为查询方法
proceed可以理解为具体执行的增强目标类的方法,但proceed方法最终还是执行代理处理器的invoke方法
可以看到执行了mi(ticketServiceImpl).proceed方法
之后到了这一部分,竟然使用了JDK的动态代理?Proxy的invoke方法
在执行完jdk的动态代理之后,我们就获得了对应方法执行的响应结果
之后一直调用到我们一开始的tomcat线程池结束请求的流程,到此整一个流程结束