Tomcat 一次请求的生命周期

在使用 Tomcat 的时候,我们只需要在 Servlet 实现类中写我们的业务逻辑代码即可,不需要管 Socket 连接、协议处理要怎么实现,因为这部分作为不经常变动的部分,被封装到了 Tomcat 中,程序员只需要引入 Tomcat 中即可,这也是面向对象编程的经典实践。

那么 Tomcat 中的一次请求都要经过哪些类的处理呢?以及 Tomcat 在处理的时候做了哪些方面的考量和设计呢?

Connector

我们知道,Tomcat 中的 Connector 组件负责 Socket 连接的创建和管理,以及网络字节流的传输。在 Connector 组件中,有三个组件 Endpoint、Processor、Adapt。

  • Endpoint:负责 ServerSocket 的创建和循环获取 Socket 连接
  • Processor:根据具体的协议,解析字节流中的数据
  • Adapt:将 Tomcat Request 转换成 ServletRequest,将 ServletResponse 转换成 Tomcat Response

Tomcat 的设计者将 Endpoint 和 Processor 又做了一次封装,将它们封装在 ProtocolHandler 中,表示协议处理器。

从上面看,请求会先在 Endpoint 对象中被接受,然后在 Processor 中被解析,最后通过 Adapt 转换后发送给 Servlet 容器。

Endpoint

NioEndpoint#startInternal

生命周期方法,在启动 Tomcat 的时候会调用 Server#start -> Service.start -> Connector.start -> ProtocolHandler.start() -> EndPoint.start() -> Endpoint.startInternal()

@Override
public void startInternal() throws Exception {if (!running) {running = true;paused = false;if (socketProperties.getProcessorCache() != 0) {processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getProcessorCache());}if (socketProperties.getEventCache() != 0) {eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getEventCache());}if (socketProperties.getBufferPool() != 0) {nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getBufferPool());}// Create worker collectionif (getExecutor() == null) {createExecutor();}initializeConnectionLatch();// Start poller thread// 启动一个 Poller 线程并赋值给 NioEndpoint 的 poller 成员变量// 这个变量在后续处理 PollerEvent 的时候会用到poller = new Poller();Thread pollerThread = new Thread(poller, getName() + "-Poller");pollerThread.setPriority(threadPriority);pollerThread.setDaemon(true);pollerThread.start();// 启动 Acceptor 线程startAcceptorThread();}
}

AbstractEndpoint#startAcceptorThread

创建并启动 Acceptor 线程

protected void startAcceptorThread() {acceptor = new Acceptor<>(this);String threadName = getName() + "-Acceptor";acceptor.setThreadName(threadName);Thread t = new Thread(acceptor, threadName);t.setPriority(getAcceptorThreadPriority());t.setDaemon(getDaemon());t.start();
}

Acceptor#run

Acceptor 的 run 方法中循环获取 socket 连接,为了减少篇幅和方便阅读,我简化了代码:

public void run() {int errorDelay = 0;long pauseStart = 0;try {// 循环,直到我们接收到一个 shutdown 命令while (!stopCalled) {if (stopCalled) {break;}state = AcceptorState.RUNNING;try {// 如果我们到达了最大连接数,则阻塞该线程endpoint.countUpOrAwaitConnection();if (endpoint.isPaused()) {continue;}U socket = null;try {// 获取 socket 连接socket = endpoint.serverSocketAccept();} catch (Exception ioe) {...}// Successful accept, reset the error delayerrorDelay = 0;if (!stopCalled && !endpoint.isPaused()) {// setSocketOptions() 方法会把 socket 交给合适的 processor 处理if (!endpoint.setSocketOptions(socket)) {endpoint.closeSocket(socket);}} else {endpoint.destroySocket(socket);}} catch (Throwable t) {...}}} finally {stopLatch.countDown();}state = AcceptorState.ENDED;
}

NioEndpoint#setSocketOptions

这里主要做了三件事:

  1. 设置 socket 为非阻塞模式
  2. 将 socket 封装到 NioSocketWrapper 中
  3. 将 NioSocketWrapper 注册到 Poller 中

其中 NioSocketWrapper 是 NioEndpoint 的静态内部类,Poller 是 NioEndpoint 的内部类,它实现了 Runnable 接口。

@Override
protected boolean setSocketOptions(SocketChannel socket) {NioSocketWrapper socketWrapper = null;try {// Allocate channel and wrapperNioChannel channel = null;if (nioChannels != null) {channel = nioChannels.pop();}if (channel == null) {SocketBufferHandler bufhandler = new SocketBufferHandler(socketProperties.getAppReadBufSize(),socketProperties.getAppWriteBufSize(),socketProperties.getDirectBuffer());if (isSSLEnabled()) {channel = new SecureNioChannel(bufhandler, this);} else {channel = new NioChannel(bufhandler);}}// NioSocketWrapper 是 NioEndpoint 的静态内部类// 将 socket 封装到 NioSocketWrapper 中NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);channel.reset(socket, newWrapper);connections.put(socket, newWrapper);socketWrapper = newWrapper;// 设置 socket 为非阻塞模式socket.configureBlocking(false);socketProperties.setProperties(socket.socket());socketWrapper.setReadTimeout(getConnectionTimeout());socketWrapper.setWriteTimeout(getConnectionTimeout());socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());// Poller 是 NioEndpoint 的内部类,它实现了 Runnable 接口poller.register(socketWrapper);return true;} catch (Throwable t) {...}// Tell to close the socket if neededreturn false;
}

Poller#register -> Poller#addEvent

  • 在 NioSocketWrapper 中设置感兴趣的事件是 SelectionKey.OP_READ,也就是读事件,此时还没有注册到 Selector 上
  • 将 NioSocketWrapper 封装成 PollerEvent,并添加到 Poller 持有的同步队列中
/*** Registers a newly created socket with the poller.** @param socketWrapper The socket wrapper*/
public void register(final NioSocketWrapper socketWrapper) {// 设置感兴趣的事件是读事件socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.// 将 NioSocketWrapper 封装成 PollerEventPollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);addEvent(pollerEvent);
}private void addEvent(PollerEvent event) {// 将 PollerEvent 添加到 Poller 中 Tomcat 自定义的同步队列中events.offer(event);if (wakeupCounter.incrementAndGet() == 0) {selector.wakeup();}
}

Poller#run

Poller 线程在一个死循环中,首先通过 events() 方法,将 PollerEvent 同步队列中的 socket 用持有的 Selector 注册感兴趣的事件。

然后获取准备好的通道, 每一个通道都传入 processKey 方法中

@Override
public void run() {// Loop until destroy() is calledwhile (true) {boolean hasEvents = false;try {if (!close) {hasEvents = events();if (wakeupCounter.getAndSet(-1) > 0) {keyCount = selector.selectNow();} else {keyCount = selector.select(selectorTimeout);}wakeupCounter.set(0);}if (close) {events();timeout(0, false);try {selector.close();} catch (IOException ioe) {log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);}break;}// Either we timed out or we woke up, process events firstif (keyCount == 0) {hasEvents = (hasEvents | events());}} catch (Throwable x) {ExceptionUtils.handleThrowable(x);log.error(sm.getString("endpoint.nio.selectorLoopError"), x);continue;}Iterator<SelectionKey> iterator =keyCount > 0 ? selector.selectedKeys().iterator() : null;// 如果 Selector 存在已经准备好的通道,则遍历就绪的通道集while (iterator != null && iterator.hasNext()) {SelectionKey sk = iterator.next();iterator.remove();NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();if (socketWrapper != null) {processKey(sk, socketWrapper);}}// Process timeoutstimeout(keyCount,hasEvents);}getStopLatch().countDown();
}

Poller#events

public boolean events() {boolean result = false;PollerEvent pe = null;for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {// 遍历存储 PollerEvent 的同步队列,如果队列大于零,则设置返回值 result 为 trueresult = true;NioSocketWrapper socketWrapper = pe.getSocketWrapper();SocketChannel sc = socketWrapper.getSocket().getIOChannel();int interestOps = pe.getInterestOps();if (sc == null) {log.warn(sm.getString("endpoint.nio.nullSocketChannel"));socketWrapper.close();} else if (interestOps == OP_REGISTER) {try {sc.register(getSelector(), SelectionKey.OP_READ, socketWrapper);} catch (Exception x) {log.error(sm.getString("endpoint.nio.registerFail"), x);}} else {// 当前 socketChannel 是否在 selector 中注册final SelectionKey key = sc.keyFor(getSelector());if (key == null) {socketWrapper.close();} else {final NioSocketWrapper attachment = (NioSocketWrapper) key.attachment();if (attachment != null) {try {int ops = key.interestOps() | interestOps;attachment.interestOps(ops);// 在 selector 上注册 READ 事件,每个 Poller 都会创建一个单独的 Selectorkey.interestOps(ops);} catch (CancelledKeyException ckx) {cancelledKey(key, socketWrapper);}} else {cancelledKey(key, socketWrapper);}}}if (running && eventCache != null) {// 重置 PollerEvent 并放回缓存 PollerEvent 的同步栈中pe.reset();eventCache.push(pe);}}return result;
}

Poller#processKey

该方法的核心逻辑是做条件判断,通过判断 socket 准备好的事件的类型,调用不同的方法来处理。如果是读事件,最终会走到 processSocket(socketWrapper, SocketEvent.OPEN_READ, true)

protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {try {if (close) {cancelledKey(sk, socketWrapper);} else if (sk.isValid()) {if (sk.isReadable() || sk.isWritable()) {if (socketWrapper.getSendfileData() != null) {processSendfile(sk, socketWrapper, false);} else {unreg(sk, socketWrapper, sk.readyOps());boolean closeSocket = false;// Read goes before writeif (sk.isReadable()) {if (socketWrapper.readOperation != null) {if (!socketWrapper.readOperation.process()) {closeSocket = true;}} else if (socketWrapper.readBlocking) {synchronized (socketWrapper.readLock) {socketWrapper.readBlocking = false;socketWrapper.readLock.notify();}} else if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) {closeSocket = true;}}if (!closeSocket && sk.isWritable()) {if (socketWrapper.writeOperation != null) {if (!socketWrapper.writeOperation.process()) {closeSocket = true;}} else if (socketWrapper.writeBlocking) {synchronized (socketWrapper.writeLock) {socketWrapper.writeBlocking = false;socketWrapper.writeLock.notify();}} else if (!processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)) {closeSocket = true;}}if (closeSocket) {cancelledKey(sk, socketWrapper);}}}} else {// Invalid keycancelledKey(sk, socketWrapper);}} catch (CancelledKeyException ckx) {cancelledKey(sk, socketWrapper);} catch (Throwable t) {ExceptionUtils.handleThrowable(t);log.error(sm.getString("endpoint.nio.keyProcessingError"), t);}
}

Poller#processSocket

该方法用来将 NioSocketWrapper 封装到 SocketProcessor 中,SocketProcessor 是 NioEndpoint 的内部类,它实现了 Runnable 接口,创建好 SocketProcessor 之后将它放到 executor 线程池中执行。

public boolean processSocket(SocketWrapperBase<S> socketWrapper,SocketEvent event, boolean dispatch) {try {if (socketWrapper == null) {return false;}SocketProcessorBase<S> sc = null;if (processorCache != null) {sc = processorCache.pop();}if (sc == null) {sc = createSocketProcessor(socketWrapper, event);} else {sc.reset(socketWrapper, event);}Executor executor = getExecutor();if (dispatch && executor != null) {executor.execute(sc);} else {sc.run();}} catch (RejectedExecutionException ree) {getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);return false;} catch (Throwable t) {ExceptionUtils.handleThrowable(t);// This means we got an OOM or similar creating a thread, or that// the pool and its queue are fullgetLog().error(sm.getString("endpoint.process.fail"), t);return false;}return true;
}

SocketProcessor#doRun

前面我们说过 Connector 组件中包含了 Endpoint、Processor 和 Adapt 三个组件,三个组件各司其职完成了 Socket 客户端的连接、网络字节流的解析和 Request 和 Response 对象的转换。

在 SocketProcessor 线程会调用 Processor 组件的 process 方法来根据不同的协议解析字节流。

@Override
protected void doRun() {boolean launch = false;try {SocketState state = SocketState.OPEN;// Process the request from this socketif (event == null) {state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);} else {state = getHandler().process(socketWrapper, event);}if (state == SocketState.CLOSED) {// Close socket and poolsocketWrapper.close();} else if (state == SocketState.UPGRADING) {launch = true;}} catch (VirtualMachineError vme) {ExceptionUtils.handleThrowable(vme);} catch (Throwable t) {log.error(sm.getString("endpoint.processing.fail"), t);if (socketWrapper != null) {((Nio2SocketWrapper) socketWrapper).close();}} finally {...}
}

AbstractProtocol#process

调用 AbstractProcessLight 类的 process 方法

@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {if (wrapper == null) {// Nothing to do. Socket has been closed.return SocketState.CLOSED;}S socket = wrapper.getSocket();Processor processor = (Processor) wrapper.takeCurrentProcessor();try {...processor.setSslSupport(wrapper.getSslSupport(getProtocol().getClientCertProvider()));SocketState state = SocketState.CLOSED;do {// 调用 AbstractProcessLight 类的 process 方法state = processor.process(wrapper, status);...} while ( state == SocketState.UPGRADING);...if (processor != null) {wrapper.setCurrentProcessor(processor);}return state;} catch(java.net.SocketException e) {...}// Make sure socket/processor is removed from the list of current// connectionsrelease(processor);return SocketState.CLOSED;
}

AbstractProcessLight#process

该方法主要做一些条件判断,如果我们的请求是一个简单的 GET 请求,则会执行到 state = service(socketWrapper); 这行代码。

@Override
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)throws IOException {SocketState state = SocketState.CLOSED;Iterator<DispatchType> dispatches = null;do {if (dispatches != null) {DispatchType nextDispatch = dispatches.next();if (getLog().isDebugEnabled()) {getLog().debug("Processing dispatch type: [" + nextDispatch + "]");}state = dispatch(nextDispatch.getSocketStatus());if (!dispatches.hasNext()) {state = checkForPipelinedData(state, socketWrapper);}} else if (status == SocketEvent.DISCONNECT) {// Do nothing here, just wait for it to get recycled} else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {state = dispatch(status);state = checkForPipelinedData(state, socketWrapper);} else if (status == SocketEvent.OPEN_WRITE) {// Extra write event likely after async, ignorestate = SocketState.LONG;} else if (status == SocketEvent.OPEN_READ) {// 读事件state = service(socketWrapper);} else if (status == SocketEvent.CONNECT_FAIL) {logAccess(socketWrapper);} else {state = SocketState.CLOSED;}...} while (state == SocketState.ASYNC_END ||dispatches != null && state != SocketState.CLOSED);return state;
}

Http11Processor#service

Http11Processor 类是 HTTP/1.1 协议的实现,这里会按照请求行、请求头、请求体的顺序解析字节流。

因为代码太多,这里不做展示,请求最终会调用 CoyoteAdapter#service,CoyoteAdapter 是 Adapter 的实现类,它属于 Connector 组件中的 Adapter 组件,用于完成 Request 和 Response 对象的适配工作,并调用 Container 的 Pipeline,至此请求进入到 Servlet 容器中。

@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)throws Exception {Request request = (Request) req.getNote(ADAPTER_NOTES);Response response = (Response) res.getNote(ADAPTER_NOTES);...try {// Parse and set Catalina and configuration specific// request parameterspostParseSuccess = postParseRequest(req, request, res, response);if (postParseSuccess) {//check valves if we support asyncrequest.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());// Calling the containerconnector.getService().getContainer().getPipeline().getFirst().invoke(request, response);}...} catch (IOException e) {// Ignore} finally {...}
}

总结

简单的梳理了 HTTP 请求进入 Tomcat 的代码调用栈之后,我们可以通过上面的流程画出请求的流程图:
在这里插入图片描述
Http11Processor 中主要是对 HTTP 协议的实现,相比于这部分,我对 Endpoint 中的处理更感兴趣,因为这部分更接近操作系统,这里我们只讨论了 Endpoint 在 NIO 模式下的处理流程,在使用 NIO 模式的时候,Tomcat 做了哪些设计和努力来让 Tomcat 能够支持高并发呢?

随着 Tomcat 版本的不断更新,每个版本都会对这部分做一些小优化,该篇文章我用的 Tomcat 是 8.5.84 的版本,其中 Endpoint 中的 Acceptor 和 Poller 都只开启了一个线程,在之后的版本中改成了可以开启多个线程,增加线程可以提高吞吐量。

Container

在上节讲解 Connector 组件的时候我们了解到,Connector 最终会调用到 CoyoteAdapter#service 方法,该方法会通过 Engine 的 Pipeline 将请求发送给 Servlet 容器。

CoyoteAdapter#service

@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)throws Exception {...try {// Parse and set Catalina and configuration specific// request parameterspostParseSuccess = postParseRequest(req, request, res, response);if (postParseSuccess) {//check valves if we support asyncrequest.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());// Calling the containerconnector.getService().getContainer().getPipeline().getFirst().invoke(request, response);}...} catch (IOException e) {// Ignore} finally {...}
}

在 Servlet 容器中,组件被组织成具有层次结构,大容器包小容器,最小的容器就是 Servlet,Engine -> Host -> Context -> Wrapper。每个容器组件都实现了 Container 接口,且都持有 Pipeline 对象,Pipeline 和 Valve 组合形成了责任链模式。

Pipeline 持有链路中第一个 First Valve 和最后一个 Basic Valve,每个 Valve 持有下一个 Valve 的应用,每个容器在初始化的时候会给 Pipeline 配置好 Basic Valve,每个 Pipeline 的 Basic Valve 会调用子容器 Pipeline 的 First Valve,于是请求就可以在容器中流转。

我们启动本地的 Tomcat,并发起一个 GET 请求,在 IDEA 中 debug 看在默认配置下各容器都持有哪些 Valve。

首先请求会到 StandardEngine,可以看出持有的是 StandardPipeline,它没有 First Valve,只有 Basic Valve,所以它只有一个 Valve,在该 Valve 中完成请求的转发。
在这里插入图片描述

之后请求到 StandardHost,它的 StandardPipeline 中既有 First Valve 也有 Basic Valve,Pipeline 中整个责任链如下:

  • NonLoginAuthenticator

  • StandardContextValve

在这里插入图片描述
之后请求到 StandardHost,它的 StandardPipeline 也只有一个 Basic Valve。

在这里插入图片描述
最后是 StandardWrapper,它的 StandardPipeline 也只有一个 Basic Valve。
在这里插入图片描述

StandardWrapperValve

有上可知,StandardWrapperValve 应该是最后一个 Valve 了,再之后就是 Servlet 了。这里单独拿出来细究是怎么到 Servlet 的 service 方法的。

@Override
public final void invoke(Request request, Response response)throws IOException, ServletException {...// Allocate a servlet instance to process this requesttry {if (!unavailable) {// 实例化 Servlet,Servlet 是延迟实例化的,只有用到了才会实例化,在实例化的时候会调用 Servlet 的 init 方法servlet = wrapper.allocate();}} catch (UnavailableException e) {...}...// Create the filter chain for this request// 为该请求创建 filterChainApplicationFilterChain filterChain =ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);// Call the filter chain for this request// NOTE: This also calls the servlet's service() methodContainer container = this.container;try {if ((servlet != null) && (filterChain != null)) {// Swallow output if neededif (context.getSwallowOutput()) {try {SystemLogHandler.startCapture();if (request.isAsyncDispatching()) {request.getAsyncContextInternal().doInternalDispatch();} else {// 触发过滤器链filterChain.doFilter(request.getRequest(),response.getResponse());}} finally {String log = SystemLogHandler.stopCapture();if (log != null && log.length() > 0) {context.getLogger().info(log);}}} else {if (request.isAsyncDispatching()) {request.getAsyncContextInternal().doInternalDispatch();} else {// 触发过滤器链filterChain.doFilter(request.getRequest(), response.getResponse());}}}} catch (ClientAbortException | CloseNowException e) {...} finally {// 释放资源...}
}

由代码可知会先创建一个 filterChain,servlet 就是在这里面被调用的,我们继续看创建 filterChain 的方法。

public static ApplicationFilterChain createFilterChain(ServletRequest request,Wrapper wrapper, Servlet servlet) {// If there is no servlet to execute, return nullif (servlet == null) {return null;}// Create and initialize a filter chain objectApplicationFilterChain filterChain = null;if (request instanceof Request) {Request req = (Request) request;if (Globals.IS_SECURITY_ENABLED) {// Security: Do not recyclefilterChain = new ApplicationFilterChain();} else {filterChain = (ApplicationFilterChain) req.getFilterChain();if (filterChain == null) {filterChain = new ApplicationFilterChain();req.setFilterChain(filterChain);}}} else {// Request dispatcher in usefilterChain = new ApplicationFilterChain();}filterChain.setServlet(servlet);filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());// Acquire the filter mappings for this ContextStandardContext context = (StandardContext) wrapper.getParent();FilterMap filterMaps[] = context.findFilterMaps();// If there are no filter mappings, we are doneif ((filterMaps == null) || (filterMaps.length == 0)) {return filterChain;}// Acquire the information we will need to match filter mappingsDispatcherType dispatcher =(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);String requestPath = null;Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);if (attribute != null){requestPath = attribute.toString();}String servletName = wrapper.getName();// Add the relevant path-mapped filters to this filter chainfor (FilterMap filterMap : filterMaps) {if (!matchDispatcher(filterMap, dispatcher)) {continue;}if (!matchFiltersURL(filterMap, requestPath)) {continue;}ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());if (filterConfig == null) {// FIXME - log configuration problemcontinue;}filterChain.addFilter(filterConfig);}// Add filters that match on servlet name secondfor (FilterMap filterMap : filterMaps) {if (!matchDispatcher(filterMap, dispatcher)) {continue;}if (!matchFiltersServlet(filterMap, servletName)) {continue;}ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());if (filterConfig == null) {// FIXME - log configuration problemcontinue;}filterChain.addFilter(filterConfig);}// Return the completed filter chainreturn filterChain;
}
  1. 通过 new 关键字创建 ApplicationFilterChain
  2. 获取 Context 中所有的 Filter 实例,并通过路径和 servlet name 筛选出匹配当前 Servlet 的 Filter 实例添加到 ApplicationFilterChain 中

创建成功后调用 filterChain.doFilter() 方法触发过滤器链。

@Override
public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {if( Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;try {java.security.AccessController.doPrivileged(new java.security.PrivilegedExceptionAction<Void>() {@Overridepublic Void run()throws ServletException, IOException {internalDoFilter(req,res);return null;}});} catch( PrivilegedActionException pe) {Exception e = pe.getException();if (e instanceof ServletException) {throw (ServletException) e;} else if (e instanceof IOException) {throw (IOException) e;} else if (e instanceof RuntimeException) {throw (RuntimeException) e;} else {throw new ServletException(e.getMessage(), e);}}} else {internalDoFilter(request,response);}
}private void internalDoFilter(ServletRequest request,ServletResponse response)throws IOException, ServletException {// Call the next filter if there is oneif (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];try {Filter filter = filterConfig.getFilter();if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);}if( Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal =((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res, this};SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);} else {filter.doFilter(request, response, this);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.filter"), e);}return;}// We fell off the end of the chain -- call the servlet instancetry {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(request);lastServicedResponse.set(response);}if (request.isAsyncSupported() && !servletSupportsAsync) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,Boolean.FALSE);}// Use potentially wrapped request from this pointif ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse) &&Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal =((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res};SecurityUtil.doAsPrivilege("service",servlet,classTypeUsedInService,args,principal);} else {servlet.service(request, response);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.servlet"), e);} finally {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(null);lastServicedResponse.set(null);}}
}

ApplicationFilterChain 中有一个 pos 变量来记录过滤器链执行位置,执行每次从 Filter 数组 filters 中获取 Filter 后 pos 就加一。等到 Filter 被调用完之后会执行 servlet.service(request, response);。由此可见 Filter 是在 Servlet init 方法执行之后,service 方法执行之前执行的。

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

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

相关文章

【LeetCode-中等题】49. 字母异位词分组

题目 题解一:排序哈希表 思路:由于互为字母异位词的两个字符串包含的字母相同&#xff0c;因此对两个字符串分别进行排序之后得到的字符串一定是相同的&#xff0c;故可以将排序之后的字符串作为哈希表的键。 核心api: //将字符串转换为字符数组char[] ch str.toCharArray();…

页面滑动到可视区域加载更多内容思维流程

页面滑动到可视区域加载更多内容思维流程

GEEMAP 中如何拉伸图像

图像拉伸是最基础的图像增强显示处理方法&#xff0c;主要用来改善图像显示的对比度&#xff0c;地物提取流程中往往首先要对图像进行拉伸处理。图像拉伸主要有三种方式&#xff1a;线性拉伸、直方图均衡化拉伸和直方图归一化拉伸。 GEE 中使用 .sldStyle() 的方法来进行图像的…

Android岗位技能实训室建设方案

一 、系统概述 Android岗位技能作为新一代信息技术的重点和促进信息消费的核心产业&#xff0c;已成为我国转变信息服务业的发展新热点&#xff1a;成为信息通信领域发展最快、市场潜力最大的业务领域。互联网尤其是移动互联网&#xff0c;以其巨大的信息交换能力和快速渗透能力…

机器学习编译系列---张量程序抽象

机器学习编译系列---张量程序抽象 1. 张量函数概念的引入与抽象的必要性 1. 张量函数概念的引入与抽象的必要性 在文章机器学习编译系列—概述中提到&#xff0c;机器学习编译的一个很重要操作是做等价变换来减少内存或者提高运行效率。变换是以“元张量函数”(private tensor …

Viewpager2+Fragment+指示器

Viewpager2Fragment指示器 效果展示&#xff1a; MainActivity.java package com.huawei.myviewpager;import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.viewpager2.widget.ViewPager2;import android.os.Bundle; …

小白到运维工程师自学之路 第七十五集 (Kubernetes 企业级高可用部署)2

8、添加master节点 在k8s-master2和k8s-master3节点创建文件夹 mkdir -p /etc/kubernetes/pki/etcd在k8s-master1节点执行 从k8s-master1复制密钥和相关文件到k8s-master2和k8s-master3 scp /etc/kubernetes/admin.conf root192.168.77.15:/etc/kubernetes scp /etc/kubernet…

jmap(Memory Map for Java)Java内存映像工具

jmap&#xff08;Memory Map for Java&#xff09;Java内存映像工具 jmap&#xff08;Memory Map for Java&#xff09;命令用于生成堆转储快照&#xff08;一般称为heapdump或dump文件&#xff09; 如果不使用jmap命令&#xff0c;要想获取Java堆转储快照也还有一些比较“暴…

RabbitMq:Topic exchange(主题交换机)的理解和使用

RabbitMq:Topic exchange(主题交换机)的理解和使用 在RabbitMq中&#xff0c;生产者的消息都是通过交换机来接收&#xff0c;然后再从交换机分发到不同的队列中去&#xff0c;在分发的过程中交换机类型会影响分发的逻辑&#xff0c;下面主要讲解一下主题交换机。 ​ 主题交换…

2023面试八股文 ——Java基础知识

Java基础知识 一.Java概述何为编程什么是Javajdk1.5之后的三大版本JVM、JRE和JDK的关系什么是跨平台性&#xff1f;原理是什么Java语言有哪些特点什么是字节码&#xff1f;采用字节码的大好处是什么什么是Java程序的主类&#xff1f;应用程序和小程序的主类有何不同&#xff1f…

数据结构之并查集

并查集 1. 并查集原理2. 并查集实现3. 并查集应用3.1 省份数量3.2 等式方程的可满足性 4. 并查集的优缺点及时间复杂度 1. 并查集原理 并查表原理是一种树型的数据结构&#xff0c;用于处理一些不相交集合的合并及查询问题。并查集的思想是用一个数组表示了整片森林&#xff0…

微服务中间件--微服务保护

微服务保护 微服务保护a.sentinelb.sentinel限流规则1) 流控模式1.a) 关联模式1.b) 链路模式 2) 流控效果2.a) 预热模式2.b) 排队等待 3) 热点参数限流 c.隔离和降级1) Feign整合Sentinel2) 线程隔离2.a) 线程隔离&#xff08;舱壁模式&#xff09; 3) 熔断降级3.a) 熔断策略-慢…

21-注意点说明:scoped样式冲突 / data

组件的三大组成部分 - 注意点说明 组件的样式冲突 scoped 默认情况:写在组件中的样式会 全局生效 -> 因此很容易造成多个组件之间的样式冲突问题 1.全局样式: 默认组件中的样式会作用到全局 2.局部样式: 可以给组件加上 scoped 属性,可以让样式只作用于当前组件 scoped原理…

构建去中心化微服务集群,满足高可用性和高并发需求的实践指南!

随着互联网技术的不断发展&#xff0c;微服务架构已经成为了开发和部署应用程序的一种主流方式。然而&#xff0c;当应用程序需要满足高可用性和高并发需求时&#xff0c;单一中心化的微服务架构可能无法满足性能和可靠性的要求。因此&#xff0c;构建一个去中心化的微服务集群…

岩土工程安全监测隧道中使用振弦采集仪注意要点?

岩土工程安全监测隧道中使用振弦采集仪注意要点&#xff1f; 岩土工程的安全监测是非常重要的&#xff0c;它可以帮助工程师及时发现可能存在的问题&#xff0c;并及时解决&#xff0c;保障施工进度以及施工质量&#xff0c;保障工程的安全运行。其中&#xff0c;振弦采集仪是…

五款拿来就能用的炫酷表白代码

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 五款炫酷表白代码 1、无限弹窗表白2、做我女朋友好吗&#xff0c;不同意就关机3、…

股票交易这个游戏玩法的本质

养家老师的“买在分歧&#xff0c;卖在一致”不用过度解读了&#xff0c;这句话也会是一个人入门标志&#xff0c;那就是这个市场是博弈的市场&#xff0c;预期打满没有任何分歧的话&#xff0c;那就没有继续博弈的价值了&#xff0c;也就只有最后一批接盘的人&#xff0c;而分…

jenkins gitlab 安装

目录 一 准备安装环境 二 安装gitlab软件 三 配置gitlab 四 重新加载配置启动gitlab 五 修改密码 五 创建用户组 一 准备安装环境 sudo yum update sudo yum install -y curl policycoreutils-python openssh-server安装 Postfix 邮件服务器&#xff0c;以便 Git…

调频连续波(FMCW)波形设计、真实道路场景仿真及汽车自适应巡航控制信号处理(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 以下是关于调频连续波&#xff08;FMCW&#xff09;波形设计、真实道路场景仿真以及汽车自适应巡航控制信号处理的概述&#x…

excel统计函数篇2之count系列

1、COUNT(value1,[value2],…):计算参数列表中数字的个数 2、COUNTA(value1,[value2],…)&#xff1a;计算参数列表中值的个数 联想在excel之数学函数、excel中的通配符一文中提到求和函数&#xff1a; SUMIF(range,ceriteria,[sum_range])&#xff1a;对范围内符合指定条件的…