JavaWeb中的Servlet
Servlet
一、互联网中的资源
Web资源:所谓的Web资源常见的是图片、文件等,通过http协议以及拼接url能够来进行访问的。
Web服务器的目的就是为了帮助我们把资源映射成URL,通过URL就可以访问到Web服务器上的资源。
那么Web服务器是如何知道怎么将资源映射成URL呢?客户端又是怎么通过URL访问得到资源呢?
通过Servlet来做到的。
二、Servlet
Servlet:在springmvc中最重要的是DispatcherServlet,而在Web中动态资源的处理是交给Servlet来进行处理的。
所谓的动态资源和静态资源:
静态资源是在任何时间、任何地点,请求访问的都是一样的信息内容。
动态资源是在不同时间、不同地点访问得到的信息可能不一样。
2.1、Servlet的作用
Tomcat容器是Servlet容器,Servlet规范就是用来定义如何将资源定义成URL的。Servlet规范是JavaWeb中的核心规范。
HTTP/HTTPS定义了数据传输格式,只需要服务器软件解析具体的内容格式,就可以得到请求报文内容。
2.2、Servlet执行流程
Servlet做的事情就是以下三步:
1、接收请求------>统一解析内容------>解析成ServletRequest对象;
2、处理请求------>Tomcat没有办法给我们来做,不同的业务会有不同的实现,需要开发自己进行实现;
3、响应请求------>响应内容------>创建ServletResponse对象;
Tomcat作为Web服务器+Servlet容器,已经将第一步和第三步都给做好了,解析报文内容和相应报文内容Tomcat都已经做好了。
那么Tomcat既然做好了这件事情,将客户端请求信息包装了一个ServletRequest,那么对于开发者来说,又该如何获取得到呢?
Servlet规范中有了具体的规范定义,既然有了规范定义,那么在Java中就会是一个接口,接口中定义了Servlet具体要做的事情。
2.3、Servlet生命周期
根据Servlet源码中的注释说明:
方法 | 方法说明 | 调用时机 | |
---|---|---|---|
init(ServletConfig) |
Tomcat创建执行; ServletConfig中定义了当前Servlet的author, version, and copyright等信息 |
||
service(ServletRequest、ServletResponse) |
Tomcat封装的请求对象和响应对象, 且Tomcat在针对请求要调用的方法 |
||
destroy | Tomcat销毁Servlet对象之前,调用 | Tomcat销毁Servlet对象之前,调用销毁方法 |
2.4、Servlet的继承体系由来
一:GenericServlet
在之前写Servlet的时候,针对每个功能不同的就需要去写一个Servlet,去重写Servlet中的方法,而且对于程序员来说不知道怎么去使用里面的参数信息或者是用不到对应的信息,所以官方又提供了一个GenericServlet抽象类来简化开发者的开发实现。
模板模式:都多时候定义接口的同时,都会定义一个抽象类,来对里面的一些不重要的事情来做一些模板方法处理。
在Servlet中ServletConfig作为一个方法对象,属于局部变量,在外部是获取不到的;但是对于GenericServlet类来说,将ServletConfig作为了一个属性。那么在当前类或者是子类中都可以获取得到ServletConfig对象。也就是说,将局部变量变成了全局变量。
GenericServlet就做了这一件事情,GenericServlet就是中间做了一些公共的东西。
二:HttpServlet
但是Tomcat针对指定的协议,又在原来的基础上开发了针对不同协议的Servlet。
如:HttpServlet,专门针对Http协议定制的Servlet,那么看看是如何来进行处理的。
http中的请求方式有很多种,那么Tomcat如何知道是哪种请求方式呢?是get/post/delete还是其他方式呢?
下面看看从HttpServlet源代码中摘抄出来的一段源码注释:
Provides an abstract class to be subclassed to create an HTTP Servlet suitable for a Web site. A subclass of HttpServlet must override at least one method, usually one of these:
doGet, if the Servlet supports HTTP GET requests
doPost, for HTTP POST requests
doPut, for HTTP PUT requests
doDelete, for HTTP DELETE requests
init and destroy, to manage resources that are held for the life of the Servlet
getServletInfo, which the Servlet uses to provide information about itself
There's almost no reason to override the service method. service handles standard HTTP requests by dispatching them to the handler methods for each HTTP request type (the doMethod methods listed above).
Likewise, there's almost no reason to override the doOptions and doTrace methods.
Servlets typically run on multithreaded servers, so be aware that a Servlet must handle concurrent requests and be careful to synchronize access to shared resources. Shared resources include in-memory data such as instance or class variables and external objects such as files, database connections, and network connections. See the Java Tutorial on Multithreaded Programming for more information on handling multiple threads in a Java program.
提供要子类化的抽象类,以创建适合Web站点的HTTPServlet。HttpServlet的子类必须重写至少一个方法,通常是以下方法之一:
方法 | 说明 |
---|---|
doGet | 用于HTTP GET请求 |
doPost | 用于HTTP POST请求 |
doPut | 用于HTTP PUT请求 |
doDelete | 用于HTTP DELETE请求 |
init和destroy | 管理在Servlet生命周期中保留的资源 |
getServletInfo | Servlet使用它来提供有关自身的信息 |
同样的,在HttpServlet源码中也有一段说明:
几乎没有理由重写service方法。服务通过将标准HTTP请求分派给每个HTTP请求类型的处理程序方法(上面列出的doMethod方法)来处理标准HTTP请求。
同样,几乎没有理由重写doOptions和doTrace方法。
从上面可以看到,在service中方法中,会根据请求方式在service方法中调用:doGet、doPost、doPut、doDelete方法。如果重写了HttpServlet中的service方法,那么HttpServlet这个类的存在就没有任何意义。那么重写什么方法呢?
上面唯独没有提到:doGet、doPost、doPut、doDelete,那么就可以参考着下面这些方法:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_post_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
通过protected修饰的可以看到,这里应该是可以来进行重写的。
2.5、默认Servlet以及路径问题
Tomcat默认给开发人员配置了两个Servlet
1、DefaultServlet:处理其他servlet处理不了的请求。如:找不到servlet来进行处理时,返回404页面。
2、JspServlet:专门用来处理JSP页面的;
3、普通Servlet:开发人员拦截指定的请求自行来进行处理;
查看DefaultServlet和JspServlet所在的位置,可以去看下Tomcat的配置文件,来到conf目录下的Web.xml文件中来:
<Servlet>
<Servlet-name>default</Servlet-name>
<Servlet-class>org.apache.catalina.Servlets.DefaultServlet</Servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</Servlet>
<!-- The mapping for the default Servlet -->
<Servlet-mapping>
<Servlet-name>default</Servlet-name>
<url-pattern>/</url-pattern>
</Servlet-mapping>
<!--================================================================-->
<!--================================================================-->
<!--================================================================-->
<Servlet>
<Servlet-name>jsp</Servlet-name>
<Servlet-class>org.apache.jasper.Servlet.JspServlet</Servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</Servlet>
<!-- The mappings for the JSP Servlet -->
<Servlet-mapping>
<Servlet-name>jsp</Servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</Servlet-mapping>
默认的/是由DefaultServlet来进行处理的,而对于JSP页面的请求是由JspServlet来进行处理的。
路径 | 拦截请求 | 属于哪个Servlet | 作用 |
---|---|---|---|
/ | 拦截的是所有请求 | DefaultServlet | 所有的Servlet都无法处理时,由DefaultServlet进行处理 兜底操作 |
/* | 拦截的是所有请求,但是/*的优先级高于/ | 自定义Serlvet(不推荐这样配置) | 拦截所有请求,优先级最高。 |
*.do | 只拦截.do的结尾的请求 | 自定义Serlvet | 拦截自定义的请求进行访问, 不干扰DefaultServlet和JspServlet请求的拦截 |
jsp 星号.jsp *.jspx |
<br /只拦截的是请求jsp页面的 |
JspServlet |
2.6、classpath和classpath*的区别
classpath:实际上只会到当前class路径下找文件,不找jar包;
classpath*:实际上会到当前class路径下找jar包文件;
三、Servlet的init和service方法如何被调用
源码:
init方法是如何执行的?
service方法是如何来执行的?
3.1、Tomcat整个架构设计
直接打开Tomcat安装目录下的conf/server.xml文件,可以看一下对应的结构文档:
Server:
---Service:server的子容器,子结点;
------Connector:连接器,为了完成http请求的,为了监听socket。把请求信息拿到
---------Engine:引擎
------------------context:部署容器,真正意义上的Tomcat容器
上面都是一层父子容器关系,所以需要在创建出来的时候维护好对应的关系。
3.2、从源码入手:
// 创建Tomcat实例对象
Tomcat tomcat = new Tomcat();
tomcat.setPort(8088);
// 添加Tomcat,完成监听
tomcat.addWebapp("/","D://tomcat");
// 启动Tomcat
tomcat.start();
// 阻塞!socket监听
tomcat.getServer().await();
tomcat.addWebapp代码
public Context addWebapp(String contextPath, String docBase) {
return addWebapp(getHost(), contextPath, docBase);
}
首先会去调用getHost()方法,然后看下getHost()方法做了什么事情:
public Host getHost() {
// 首先获取得到Engine
Engine engine = getEngine();
// 然后检查Engine的子容器Host,可能会有多个子容器
if (engine.findChildren().length > 0) {
return (Host) engine.findChildren()[0];
}
// 创建子容器,添加到Engine中
Host host = new StandardHost();
host.setName(hostname);
getEngine().addChild(host);
return host;
}
那么看一下如何获取得到Engine的
public Engine getEngine() {
// 首先获取得到Server中的Service,返回server中的第一个service
Service service = getServer().findServices()[0];
if (service.getContainer() != null) {
// 如果有容器,那么就直接返回
return service.getContainer();
}
// 创建Engine对象
Engine engine = new StandardEngine();
engine.setName( "Tomcat" );
engine.setDefaultHost(hostname);
engine.setRealm(createDefaultRealm());
// 在service中设置Engine
service.setContainer(engine);
return engine;
}
那么看下如何得到server的
public Server getServer() {
// 如果存在,直接返回
if (server != null) {
return server;
}
System.setProperty("catalina.useNaming", "false");
// 如果不存在,那么创建Server对象
server = new StandardServer();
initBaseDir();
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
server.setPort( -1 );
// 创建service设置给Server中去
Service service = new StandardService();
service.setName("Tomcat");
server.addService(service);
return server;
}
然后看一下addWebapp方法
public Context addWebapp(Host host, String contextPath, String docBase) {
LifecycleListener listener = null;
try {
// 通过反射来创建监听器对象
Class<?> clazz = Class.forName(getHost().getConfigClass());
listener = (LifecycleListener) clazz.getConstructor().newInstance();
} catch (ReflectiveOperationException e) {
// Wrap in IAE since we can't easily change the method signature to
// to throw the specific checked exceptions
throw new IllegalArgumentException(e);
}
return addWebapp(host, contextPath, docBase, listener);
}
默认监听器
然后看下默认的监听器:
private String configClass ="org.apache.catalina.startup.ContextConfig";
加载LifecycleListener这个监听器的实现类(监听器中的方法在哪里执行的还不知道,但是既然添加了,那么后续在触发的时候就一定会来执行),然后继续调用addWebapp方法。
public Context addWebapp(Host host, String contextPath, String docBase,
LifecycleListener config) {
silence(host, contextPath);
// 创建容器结构中的eContext对象
Context ctx = createContext(host, contextPath);
// 设置属性
ctx.setPath(contextPath);
ctx.setDocBase(docBase);
// 添加监听器
if (addDefaultWebXmlToWebapp) {
ctx.addLifecycleListener(getDefaultWebXmlListener());
}
ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));
// 将监听器放入到Context容器中来
ctx.addLifecycleListener(config);
if (addDefaultWebXmlToWebapp && (config instanceof ContextConfig)) {
// prevent it from looking ( if it finds one - it'll have dup error )
((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
}
// 将context添加到Host容器中来
if (host == null) {
getHost().addChild(ctx);
} else {
host.addChild(ctx);
}
return ctx;
}
不妨来看一下addChild方法,直接来到org.apache.catalina.core.ContainerBase#addChild方法中来:
当前child参数是:Context
public void addChild(Container child) {
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction<Void> dp =
new PrivilegedAddChild(child);
AccessController.doPrivileged(dp);
} else {
// 调用方法
addChildInternal(child);
}
}
当前child参数是:Context,当前children为Engine,将Context添加到Engine中来
private void addChildInternal(Container child) {
if (log.isDebugEnabled()) {
log.debug("Add child " + child + " " + this);
}
synchronized(children) {
if (children.get(child.getName()) != null)
throw new IllegalArgumentException(
sm.getString("containerBase.child.notUnique", child.getName()));
child.setParent(this); // May throw IAE
children.put(child.getName(), child);
}
fireContainerEvent(ADD_CHILD_EVENT, child);
// Start child
// Don't do this inside sync block - start can be a slow process and
// locking the children object can cause problems elsewhere
try {
if ((getState().isAvailable() ||
LifecycleState.STARTING_PREP.equals(getState())) &&
startChildren) {
child.start();
}
} catch (LifecycleException e) {
throw new IllegalStateException(sm.getString("containerBase.child.start"), e);
}
}
private Context createContext(Host host, String url) {
String defaultContextClass = StandardContext.class.getName();
String contextClass = StandardContext.class.getName();
if (host == null) {
host = this.getHost();
}
if (host instanceof StandardHost) {
contextClass = ((StandardHost) host).getContextClass();
}
try {
if (defaultContextClass.equals(contextClass)) {
return new StandardContext();
} else {
// 通过反射来加载StandardContext对应的对象--->代表Context容器
return (Context) Class.forName(contextClass).getConstructor()
.newInstance();
}
} catch (ReflectiveOperationException | IllegalArgumentException | SecurityException e) {
throw new IllegalArgumentException(sm.getString("tomcat.noContextClass", contextClass, host, url), e);
}
}
首先加载LifiCycleListener:ContextConfig监听器,然后加载StandardContext:也就是Tomcat容器本身
StandardServer--->StandardService--->StandardEngine--->StandardHost
逐层创建对象,上层对象持有下层对象的属性,非常类似于一个链条结构。
start方法
看一下start方法(核心方法)
public void start() throws LifecycleException {
// 获取得到创建好的Server,并调用对应的start方法
getServer();
server.start();
}
下面这个方法调用了两次,为什么呢?可能addWebApp方法不需要调用,也可以来进行启动。
public Server getServer() {
if (server != null) {
return server;
}
System.setProperty("catalina.useNaming", "false");
server = new StandardServer();
initBaseDir();
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
server.setPort( -1 );
Service service = new StandardService();
service.setName("Tomcat");
server.addService(service);
return server;
}
public void start() throws LifecycleException {
getServer();
// 以前这里有一行代码,getConnector,但是现在没有了,很奇怪
server.start();
}
直接来到org.apache.catalina.util.LifecycleBase#start中,Tomcat中最核心的一个类。作为一个模板类使用,后续的几个StandXXX都会来调用换个方法
当前是StandServer(是LifecycleBase的一个子类对象)对象来调用start方法,会来到父类LifecycleBase中的方法中来:
public final synchronized void start() throws LifecycleException {
if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
}
return;
}
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
这里最核心的方法就是startInternal(),表示的是容器启动方法。
在StandardServer.startInternal()--调用--->StandardService.startInternal()--->调用-->StandardEngine.startInternal()--调用-->StandardHost.startInternal()
一层调用一层,逐层引爆,知道将Tomcat中所有的容器都启动起来。
LifeCycleBase:最关键的一个类,模板模式
因为StandServer、StandService、StandEngine都是LifeCycleBase的子类,利用模板模式,重写了里面大量的操作方式,子类来完成各自的事情。
首先来到StandardServer容器中来:
protected void startInternal() throws LifecycleException {
// 发布事件,但是可以看到data为null
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// 启动所有的service
// Start our defined Services
synchronized (servicesLock) {
for (Service service : services) {
service.start();
}
}
if (periodicEventDelay > 0) {
monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
new Runnable() {
@Override
public void run() {
startPeriodicLifecycleEvent();
}
}, 0, 60, TimeUnit.SECONDS);
}
}
所以接下来就是service.start()方法,然后来到org.apache.catalina.core.StandardService#startInternal等等。
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING);
// 开始来调用对应的engine,没有的话就不来进行调用
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
mapperListener.start();
// 来到Connector的执行方法中来
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
// 继续调用
connector.start();
}
}
}
}
然后来到org.apache.catalina.connector.Connector#startInternal中来:
protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPortWithOffset() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset())));
}
setState(LifecycleState.STARTING);
try {
// 调用方法
protocolHandler.start();
} catch (Exception e) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
}
}
public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
logPortOffset();
}
endpoint.start();
monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
new Runnable() {
@Override
public void run() {
if (!isPaused()) {
startAsyncTimeout();
}
}
}, 0, 60, TimeUnit.SECONDS);
}
然后调用来到:
Tomcat默认支持的几种模型使用,在Tomcat8之后,Tomcat默认选择使用的是NioEndpoint模型来进行监听。
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 collection
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
// Start poller thread
// 开启线程来进行监听
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
// Tomcat为什么可以监听8080端口,在这里可以看到
startAcceptorThread();
}
}
然后来到Poller类中的run方法中来,看一下对应的实现过程:
public void run() {
// Loop until destroy() is called
while (true) {
boolean hasEvents = false;
try {
if (!close) {
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {
// If we are here, means we have other stuff to do
// Do a non blocking select
keyCount = selector.selectNow();
} else {
// select方法触发监听
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 first
if (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;
// Walk through the collection of ready keys and dispatch
// any active event.
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (socketWrapper != null) {
// 处理来自新的请求
processKey(sk, socketWrapper);
}
}
// Process timeouts
timeout(keyCount,hasEvents);
}
getStopLatch().countDown();
}
我们可以在org.apache.tomcat.util.net.Acceptor#run可以看到Tomcat为什么可以来监听端口(Acceptor是Runnable接口的实现类)
public void run() {
int errorDelay = 0;
try {
// Loop until we receive a shutdown command
while (!stopCalled) {
// Loop if endpoint is paused
while (endpoint.isPaused() && !stopCalled) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (stopCalled) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
endpoint.countUpOrAwaitConnection();
// Endpoint might have been paused while waiting for latch
// If that is the case, don't accept new connections
if (endpoint.isPaused()) {
continue;
}
U socket = null;
try {
// Accept the next incoming connection from the server
// socket
// 接受来自服务器的下一个传入连接
socket = endpoint.serverSocketAccept();
} catch (Exception ioe) {
// We didn't get a socket
endpoint.countDownConnection();
if (endpoint.isRunning()) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
} else {
break;
}
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (!stopCalled && !endpoint.isPaused()) {
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
if (!endpoint.setSocketOptions(socket)) {
endpoint.closeSocket(socket);
}
} else {
endpoint.destroySocket(socket);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
String msg = sm.getString("endpoint.accept.fail");
// APR specific.
// Could push this down but not sure it is worth the trouble.
if (t instanceof Error) {
Error e = (Error) t;
if (e.getError() == 233) {
// Not an error on HP-UX so log as a warning
// so it can be filtered out on that platform
// See bug 50273
log.warn(msg, t);
} else {
log.error(msg, t);
}
} else {
log.error(msg, t);
}
}
}
} finally {
stopLatch.countDown();
}
state = AcceptorState.ENDED;
}
那么接下来来看一下Servlet的容器,也就是context是如何来调用对应的startInternal方法的
org.apache.catalina.core.ContainerBase#startInternal
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// Start our child containers, if any
// 找到子容器,如果有多个子容器,那么异步执行每个child(context容器)
// 一般来说,当前只有一个,会执行里面的方法
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
setState(LifecycleState.STARTING);
// Start our thread
if (backgroundProcessorDelay > 0) {
monitorFuture = Container.getService(ContainerBase.this).getServer()
.getUtilityExecutor().scheduleWithFixedDelay(
new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
}
}
然后来看下对应类中的方法
private static class StartChild implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
child.start();
return null;
}
}
所以看一下对应的start方法:
protected synchronized void startInternal() throws LifecycleException {
if(log.isDebugEnabled())
log.debug("Starting " + getBaseName());
// Send j2ee.state.starting notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.state.starting",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
setConfigured(false);
boolean ok = true;
// Currently this is effectively a NO-OP but needs to be called to
// ensure the NamingResources follows the correct lifecycle
if (namingResources != null) {
namingResources.start();
}
// Post work directory
postWorkDirectory();
// Add missing components as necessary
if (getResources() == null) { // (1) Required by Loader
if (log.isDebugEnabled())
log.debug("Configuring default Resources");
try {
setResources(new StandardRoot(this));
} catch (IllegalArgumentException e) {
log.error(sm.getString("standardContext.resourcesInit"), e);
ok = false;
}
}
if (ok) {
resourcesStart();
}
if (getLoader() == null) {
WebappLoader WebappLoader = new WebappLoader();
WebappLoader.setDelegate(getDelegate());
setLoader(WebappLoader);
}
// An explicit cookie processor hasn't been specified; use the default
if (cookieProcessor == null) {
cookieProcessor = new Rfc6265CookieProcessor();
}
// Initialize character set mapper
getCharsetMapper();
// Validate required extensions
boolean dependencyCheck = true;
try {
dependencyCheck = ExtensionValidator.validateApplication
(getResources(), this);
} catch (IOException ioe) {
log.error(sm.getString("standardContext.extensionValidationError"), ioe);
dependencyCheck = false;
}
if (!dependencyCheck) {
// do not make application available if dependency check fails
ok = false;
}
// Reading the "catalina.useNaming" environment variable
String useNamingProperty = System.getProperty("catalina.useNaming");
if ((useNamingProperty != null)
&& (useNamingProperty.equals("false"))) {
useNaming = false;
}
if (ok && isUseNaming()) {
if (getNamingContextListener() == null) {
NamingContextListener ncl = new NamingContextListener();
ncl.setName(getNamingContextName());
ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
addLifecycleListener(ncl);
setNamingContextListener(ncl);
}
}
// Standard container startup
if (log.isDebugEnabled())
log.debug("Processing standard container startup");
// Binding thread
ClassLoader oldCCL = bindThread();
try {
if (ok) {
// Start our subordinate components, if any
Loader loader = getLoader();
if (loader instanceof Lifecycle) {
((Lifecycle) loader).start();
}
// since the loader just started, the Webapp classloader is now
// created.
setClassLoaderProperty("clearReferencesRmiTargets",
getClearReferencesRmiTargets());
setClassLoaderProperty("clearReferencesStopThreads",
getClearReferencesStopThreads());
setClassLoaderProperty("clearReferencesStopTimerThreads",
getClearReferencesStopTimerThreads());
setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
getClearReferencesHttpClientKeepAliveThread());
setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
getClearReferencesObjectStreamClassCaches());
setClassLoaderProperty("clearReferencesObjectStreamClassCaches",
getClearReferencesObjectStreamClassCaches());
setClassLoaderProperty("clearReferencesThreadLocals",
getClearReferencesThreadLocals());
// By calling unbindThread and bindThread in a row, we setup the
// current Thread CCL to be the Webapp classloader
unbindThread(oldCCL);
oldCCL = bindThread();
// Initialize logger again. Other components might have used it
// too early, so it should be reset.
logger = null;
getLogger();
Realm realm = getRealmInternal();
if(null != realm) {
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// Place the CredentialHandler into the ServletContext so
// applications can have access to it. Wrap it in a "safe"
// handler so application's can't modify it.
CredentialHandler safeHandler = new CredentialHandler() {
@Override
public boolean matches(String inputCredentials, String storedCredentials) {
return getRealmInternal().getCredentialHandler().matches(inputCredentials, storedCredentials);
}
@Override
public String mutate(String inputCredentials) {
return getRealmInternal().getCredentialHandler().mutate(inputCredentials);
}
};
context.setAttribute(Globals.CREDENTIAL_HANDLER, safeHandler);
}
// 触发监听器
// Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// Start our child containers, if not already started
for (Container child : findChildren()) {
if (!child.getState().isAvailable()) {
child.start();
}
}
// Start the Valves in our pipeline (including the basic),
// if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
// Acquire clustered manager
Manager contextManager = null;
Manager manager = getManager();
if (manager == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.cluster.noManager",
Boolean.valueOf((getCluster() != null)),
Boolean.valueOf(distributable)));
}
if ((getCluster() != null) && distributable) {
try {
contextManager = getCluster().createManager(getName());
} catch (Exception ex) {
log.error(sm.getString("standardContext.cluster.managerError"), ex);
ok = false;
}
} else {
contextManager = new StandardManager();
}
}
// Configure default manager if none was specified
if (contextManager != null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("standardContext.manager",
contextManager.getClass().getName()));
}
setManager(contextManager);
}
if (manager!=null && (getCluster() != null) && distributable) {
//let the cluster know that there is a context that is distributable
//and that it has its own manager
getCluster().registerManager(manager);
}
}
if (!getConfigured()) {
log.error(sm.getString("standardContext.configurationFail"));
ok = false;
}
// We put the resources into the Servlet context
if (ok) {
getServletContext().setAttribute
(Globals.RESOURCES_ATTR, getResources());
if (getInstanceManager() == null) {
setInstanceManager(createInstanceManager());
}
getServletContext().setAttribute(
InstanceManager.class.getName(), getInstanceManager());
InstanceManagerBindings.bind(getLoader().getClassLoader(), getInstanceManager());
// Create context attributes that will be required
getServletContext().setAttribute(
JarScanner.class.getName(), getJarScanner());
// Make the version info available
getServletContext().setAttribute(Globals.WebAPP_VERSION, getWebappVersion());
}
// Set up the context init params
mergeParameters();
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
}
try {
// Start manager
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
}
// Configure and call application filters
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" Servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.ServletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
} finally {
// Unbinding thread
unbindThread(oldCCL);
}
// Set available status depending upon startup success
if (ok) {
if (log.isDebugEnabled())
log.debug("Starting completed");
} else {
log.error(sm.getString("standardContext.startFailed", getName()));
}
startTime=System.currentTimeMillis();
// Send j2ee.state.running notification
if (ok && (this.getObjectName() != null)) {
Notification notification =
new Notification("j2ee.state.running", this.getObjectName(),
sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
// The WebResources implementation caches references to JAR files. On
// some platforms these references may lock the JAR files. Since Web
// application start is likely to have read from lots of JARs, trigger
// a clean-up now.
getResources().gc();
// Reinitializing if something went wrong
if (!ok) {
setState(LifecycleState.FAILED);
// Send j2ee.object.failed notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.object.failed",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
} else {
setState(LifecycleState.STARTING);
}
}
org.apache.catalina.startup.ContextConfig#lifecycleEvent监听器的代码:(非常核心的一件事情)
public void lifecycleEvent(LifecycleEvent event) {
// Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
// 最核心的地方!做Web.xml的解析
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
configureStart();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if (originalDocBase != null) {
context.setDocBase(originalDocBase);
}
} else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
configureStop();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
destroy();
}
}
看看Web.xml是如何来进行解析的
protected synchronized void configureStart() {
// Called from StandardContext.start()
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.start"));
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.xmlSettings",
context.getName(),
Boolean.valueOf(context.getXmlValidation()),
Boolean.valueOf(context.getXmlNamespaceAware())));
}
// 解析Web.xml
WebConfig();
if (!context.getIgnoreAnnotations()) {
applicationAnnotationsConfig();
}
if (ok) {
validateSecurityRoles();
}
// Configure an authenticator if we need one
if (ok) {
authenticatorConfig();
}
// Dump the contents of this pipeline if requested
if (log.isDebugEnabled()) {
log.debug("Pipeline Configuration:");
Pipeline pipeline = context.getPipeline();
Valve valves[] = null;
if (pipeline != null) {
valves = pipeline.getValves();
}
if (valves != null) {
for (Valve valve : valves) {
log.debug(" " + valve.getClass().getName());
}
}
log.debug("======================");
}
// Make our application available if no problems were encountered
if (ok) {
context.setConfigured(true);
} else {
log.error(sm.getString("contextConfig.unavailable"));
context.setConfigured(false);
}
}
看一下解析资源的位置:
// Parse context level Web.xml
InputSource contextWebXml = getContextWebXmlSource();
protected InputSource getContextWebXmlSource() {
InputStream stream = null;
InputSource source = null;
URL url = null;
String altDDName = null;
// Open the application Web.xml file, if it exists
ServletContext ServletContext = context.getServletContext();
try {
if (ServletContext != null) {
altDDName = (String)ServletContext.getAttribute(Globals.ALT_DD_ATTR);
if (altDDName != null) {
try {
stream = new FileInputStream(altDDName);
url = new File(altDDName).toURI().toURL();
} catch (FileNotFoundException e) {
log.error(sm.getString("contextConfig.altDDNotFound",
altDDName));
} catch (MalformedURLException e) {
log.error(sm.getString("contextConfig.applicationUrl"));
}
} else {
// 默认从/Web-INF/Web.xml开始进行读取对应的配置文件
stream = ServletContext.getResourceAsStream
(Constants.ApplicationWebXml);
try {
url = ServletContext.getResource(
Constants.ApplicationWebXml);
} catch (MalformedURLException e) {
log.error(sm.getString("contextConfig.applicationUrl"));
}
}
}
if (stream == null || url == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("contextConfig.applicationMissing") + " " + context);
}
} else {
source = new InputSource(url.toExternalForm());
source.setByteStream(stream);
}
} finally {
if (source == null && stream != null) {
try {
stream.close();
} catch (IOException e) {
// Ignore
}
}
}
return source;
}
将Web.xml中的信息读取完成之后,封装成对象,然后再调用org.apache.catalina.startup.ContextConfig#configureContext方法,将Web.xml中的信息添加到ServletContext对象中来
private void configureContext(WebXml Webxml) {
// As far as possible, process in alphabetical order so it is easy to
// check everything is present
// Some validation depends on correct public ID
context.setPublicId(Webxml.getPublicId());
// Everything else in order
context.setEffectiveMajorVersion(Webxml.getMajorVersion());
context.setEffectiveMinorVersion(Webxml.getMinorVersion());
for (Entry<String, String> entry : Webxml.getContextParams().entrySet()) {
context.addParameter(entry.getKey(), entry.getValue());
}
context.setDenyUncoveredHttpMethods(
Webxml.getDenyUncoveredHttpMethods());
context.setDisplayName(Webxml.getDisplayName());
context.setDistributable(Webxml.isDistributable());
for (ContextLocalEjb ejbLocalRef : Webxml.getEjbLocalRefs().values()) {
context.getNamingResources().addLocalEjb(ejbLocalRef);
}
for (ContextEjb ejbRef : Webxml.getEjbRefs().values()) {
context.getNamingResources().addEjb(ejbRef);
}
for (ContextEnvironment environment : Webxml.getEnvEntries().values()) {
context.getNamingResources().addEnvironment(environment);
}
for (ErrorPage errorPage : Webxml.getErrorPages().values()) {
context.addErrorPage(errorPage);
}
for (FilterDef filter : Webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
for (FilterMap filterMap : Webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
context.setJspConfigDescriptor(Webxml.getJspConfigDescriptor());
for (String listener : Webxml.getListeners()) {
context.addApplicationListener(listener);
}
for (Entry<String, String> entry :
Webxml.getLocaleEncodingMappings().entrySet()) {
context.addLocaleEncodingMappingParameter(entry.getKey(),
entry.getValue());
}
// Prevents IAE
if (Webxml.getLoginConfig() != null) {
context.setLoginConfig(Webxml.getLoginConfig());
}
for (MessageDestinationRef mdr :
Webxml.getMessageDestinationRefs().values()) {
context.getNamingResources().addMessageDestinationRef(mdr);
}
// messageDestinations were ignored in Tomcat 6, so ignore here
context.setIgnoreAnnotations(Webxml.isMetadataComplete());
for (Entry<String, String> entry :
Webxml.getMimeMappings().entrySet()) {
context.addMimeMapping(entry.getKey(), entry.getValue());
}
context.setRequestCharacterEncoding(Webxml.getRequestCharacterEncoding());
// Name is just used for ordering
for (ContextResourceEnvRef resource :
Webxml.getResourceEnvRefs().values()) {
context.getNamingResources().addResourceEnvRef(resource);
}
for (ContextResource resource : Webxml.getResourceRefs().values()) {
context.getNamingResources().addResource(resource);
}
context.setResponseCharacterEncoding(Webxml.getResponseCharacterEncoding());
boolean allAuthenticatedUsersIsAppRole =
Webxml.getSecurityRoles().contains(
SecurityConstraint.ROLE_ALL_AUTHENTICATED_USERS);
for (SecurityConstraint constraint : Webxml.getSecurityConstraints()) {
if (allAuthenticatedUsersIsAppRole) {
constraint.treatAllAuthenticatedUsersAsApplicationRole();
}
context.addConstraint(constraint);
}
for (String role : Webxml.getSecurityRoles()) {
context.addSecurityRole(role);
}
for (ContextService service : Webxml.getServiceRefs().values()) {
context.getNamingResources().addService(service);
}
// 开始遍历Servlet中的方法
for (ServletDef Servlet : Webxml.getServlets().values()) {
// Wrapper是对Servlet的封装,下面将Servlet中的属性填充到Wrapper中
Wrapper wrapper = context.createWrapper();
// jsp-file gets passed to the JSP Servlet as an init-param
if (Servlet.getLoadOnStartup() != null) {
// 初始化值
wrapper.setLoadOnStartup(Servlet.getLoadOnStartup().intValue());
}
if (Servlet.getEnabled() != null) {
wrapper.setEnabled(Servlet.getEnabled().booleanValue());
}
wrapper.setName(Servlet.getServletName());
Map<String,String> params = Servlet.getParameterMap();
for (Entry<String, String> entry : params.entrySet()) {
wrapper.addInitParameter(entry.getKey(), entry.getValue());
}
wrapper.setRunAs(Servlet.getRunAs());
Set<SecurityRoleRef> roleRefs = Servlet.getSecurityRoleRefs();
for (SecurityRoleRef roleRef : roleRefs) {
wrapper.addSecurityReference(
roleRef.getName(), roleRef.getLink());
}
wrapper.setServletClass(Servlet.getServletClass());
MultipartDef multipartdef = Servlet.getMultipartDef();
if (multipartdef != null) {
long maxFileSize = -1;
long maxRequestSize = -1;
int fileSizeThreshold = 0;
// 设置文件上传阶段中的一些属性值
if(null != multipartdef.getMaxFileSize()) {
maxFileSize = Long.parseLong(multipartdef.getMaxFileSize());
}
if(null != multipartdef.getMaxRequestSize()) {
maxRequestSize = Long.parseLong(multipartdef.getMaxRequestSize());
}
if(null != multipartdef.getFileSizeThreshold()) {
fileSizeThreshold = Integer.parseInt(multipartdef.getFileSizeThreshold());
}
wrapper.setMultipartConfigElement(new MultipartConfigElement(
multipartdef.getLocation(),
maxFileSize,
maxRequestSize,
fileSizeThreshold));
}
if (Servlet.getAsyncSupported() != null) {
wrapper.setAsyncSupported(
Servlet.getAsyncSupported().booleanValue());
}
wrapper.setOverridable(Servlet.isOverridable());
// 将Servlet放入到context容器中来
context.addChild(wrapper);
}
for (Entry<String, String> entry :
Webxml.getServletMappings().entrySet()) {
context.addServletMappingDecoded(entry.getKey(), entry.getValue());
}
// session的配置
SessionConfig sessionConfig = Webxml.getSessionConfig();
if (sessionConfig != null) {
if (sessionConfig.getSessionTimeout() != null) {
context.setSessionTimeout(
sessionConfig.getSessionTimeout().intValue());
}
SessionCookieConfig scc =
context.getServletContext().getSessionCookieConfig();
scc.setName(sessionConfig.getCookieName());
scc.setDomain(sessionConfig.getCookieDomain());
scc.setPath(sessionConfig.getCookiePath());
scc.setComment(sessionConfig.getCookieComment());
if (sessionConfig.getCookieHttpOnly() != null) {
scc.setHttpOnly(sessionConfig.getCookieHttpOnly().booleanValue());
}
if (sessionConfig.getCookieSecure() != null) {
scc.setSecure(sessionConfig.getCookieSecure().booleanValue());
}
if (sessionConfig.getCookieMaxAge() != null) {
scc.setMaxAge(sessionConfig.getCookieMaxAge().intValue());
}
if (sessionConfig.getSessionTrackingModes().size() > 0) {
context.getServletContext().setSessionTrackingModes(
sessionConfig.getSessionTrackingModes());
}
}
// Context doesn't use version directly
for (String welcomeFile : Webxml.getWelcomeFiles()) {
/*
* The following will result in a welcome file of "" so don't add
* that to the context
* <welcome-file-list>
* <welcome-file/>
* </welcome-file-list>
*/
if (welcomeFile != null && welcomeFile.length() > 0) {
context.addWelcomeFile(welcomeFile);
}
}
// Do this last as it depends on Servlets
for (JspPropertyGroup jspPropertyGroup :
Webxml.getJspPropertyGroups()) {
String jspServletName = context.findServletMapping("*.jsp");
if (jspServletName == null) {
jspServletName = "jsp";
}
if (context.findChild(jspServletName) != null) {
for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
context.addServletMappingDecoded(urlPattern, jspServletName, true);
}
} else {
if(log.isDebugEnabled()) {
for (String urlPattern : jspPropertyGroup.getUrlPatterns()) {
log.debug("Skipping " + urlPattern + " , no Servlet " +
jspServletName);
}
}
}
}
for (Entry<String, String> entry :
Webxml.getPostConstructMethods().entrySet()) {
context.addPostConstructMethod(entry.getKey(), entry.getValue());
}
for (Entry<String, String> entry :
Webxml.getPreDestroyMethods().entrySet()) {
context.addPreDestroyMethod(entry.getKey(), entry.getValue());
}
}
下面看看Servlet的init方法在哪里调用的:
// Load and initialize all "load on startup" Servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.ServletFail"));
ok = false;
}
}
看下具体的实现逻辑:
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" Servlets that need to be initialized
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (Container child : children) {
// 将Servlet进行强制类型转换
Wrapper wrapper = (Wrapper) child;
// 获取得到对应的值
// 如果为负数,那么在启动的时候执行;如果为整数,那么开始执行下面的逻辑
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0) {
continue;
}
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" Servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
// 调用load方法!!执行对应的init方法
wrapper.load();
} catch (ServletException e) {
getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
// NOTE: load errors (including a Servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if(getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
那么来看下init方法的调用
public synchronized void load() throws ServletException {
// 获取得到对应的Servlet类
instance = loadServlet();
// 初始化时期开始初始化Servlet
if (!instanceInitialized) {
initServlet(instance);
}
if (isJspServlet) {
StringBuilder oname = new StringBuilder(getDomain());
oname.append(":type=JspMonitor");
oname.append(getWebModuleKeyProperties());
oname.append(",name=");
oname.append(getName());
oname.append(getJ2EEKeyProperties());
try {
jspMonitorON = new ObjectName(oname.toString());
Registry.getRegistry(null, null).registerComponent(instance, jspMonitorON, null);
} catch (Exception ex) {
log.warn(sm.getString("standardWrapper.jspMonitorError", instance));
}
}
}
private synchronized void initServlet(Servlet Servlet)
throws ServletException {
if (instanceInitialized && !singleThreadModel) return;
// Call the initialization method of this Servlet
try {
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
SecurityUtil.doAsPrivilege("init",
Servlet,
classType,
args);
success = true;
} finally {
if (!success) {
// destroy() will not be called, thus clear the reference now
SecurityUtil.remove(Servlet);
}
}
} else {
// 调用Servlet执行对应的init方法
// facade:The facade associated with this wrapper.
Servlet.init(facade);
}
instanceInitialized = true;
} catch (UnavailableException f) {
unavailable(f);
throw f;
} catch (ServletException f) {
// If the Servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw f;
} catch (Throwable f) {
ExceptionUtils.handleThrowable(f);
getServletContext().log(sm.getString("standardWrapper.initException", getName()), f);
// If the Servlet wanted to be unavailable it would have
// said so, so do not call unavailable(null).
throw new ServletException
(sm.getString("standardWrapper.initException", getName()), f);
}
}
Wrapper设计模式:也就是所谓的门面模式:就是将一系列的操作封装成一个方法。 非常类似于模板方法,但是和模板方法区别在于子类每个实现自己的需求,而门面方式主要的是做大量的封装,而不是说个别的来进行调用。(单个单个的调用,封装成一个wrapper,来进行一个一个的进行调用,对应着Java中的体系就是封装)
Servlet中的service方法的调用在于socket监听过程中:while(true)死循环中进行监听
那么来到Connector中的org.apache.catalina.connector.Connector#startInternal方法中来,然后执行到org.apache.coyote.AbstractProtocol#start中来
然后又再次执行到org.apache.tomcat.util.net.NioEndpoint#startInternal方法中来
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 collection
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
// Start poller thread
poller = new Poller();
Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
startAcceptorThread();
}
}
直接来到Poller类中的run方法
public void run() {
// Loop until destroy() is called
// socket请求接到了一个请求之后,不能够中断,需要进行下一个请求操作
// 所以使用的是while(true)
while (true) {
boolean hasEvents = false;
try {
if (!close) {
hasEvents = events();
if (wakeupCounter.getAndSet(-1) > 0) {
// If we are here, means we have other stuff to do
// Do a non blocking select
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 first
if (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;
// Walk through the collection of ready keys and dispatch
// any active event.
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
iterator.remove();
NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (socketWrapper != null) {
// 在这里回来调用service方法
processKey(sk, socketWrapper);
}
}
// Process timeouts
timeout(keyCount,hasEvents);
}
getStopLatch().countDown();
}
四、总结图
JavaWeb中的Servlet的更多相关文章
- 在javaweb中通过servlet类和普通类读取资源文件
javaweb有两种方式读取资源文件 在Servlet中读取,可以使用servletContext,servletContext可以拿到web所有的资源文件,然后随便读,但是这种方法不常用,尽量少在S ...
- 专攻知识小点——回顾JavaWeb中的servlet(二)
续前篇... ServletConfig对象 Servlet的配置对象,ServletConfig对象作用域只能在一个Servlet类中使用.每个Servlet类都维护一个ServletConfig对 ...
- 在javaweb中从servlet端向jsp端传递数据的方法
1.servlet端: request.setAttribute("student", student)://向请求域中添加名称为student,内容为student中内容的数据( ...
- 专攻知识小点——回顾JavaWeb中的servlet(三)
HttpSession基本概述 ** ** 1.HttpSession:是服务器端的技术.和Cookie一样也是服务器和客户端的会话.获得该对象是通过HTTPServletRequest的方法getS ...
- JavaWeb中servlet读取配置文件的方式
我们在JavaWeb中常常要涉及到一些文件的操作,比如读取配置文件,下载图片等等操作.那我们能不能采用我们以前在Java工程中读取文件的方式呢?废话不多说我们来看看下我们以前在Java工程中读取文件是 ...
- javaWeb中 servlet 、request 、response
1.Servlet (1)Servlet是JavaEE的一个动态web资源开发技 术,就是在服务器上运行的小程序,这个小程序是由服务器调用的,服务器为了能调用这个小程序,就要求这样的程序必须实现一个S ...
- javaWeb中servlet开发(3)——Servlet生命周期
生命周期:是一个程序的存在周期,servlet由于是受容器的管理,所以容器来决定其生命周期 1.servlet生命周期 2.servlet生命周期对应的方法 3.servlet生命周期代码 publi ...
- javaWeb中servlet开发(1)——helloworld
1.servlet 1.1 servlet简介 1.2 servlet流程 不管是servlet还是jsp,所有的程序都是在服务器端处理的,所以必须了解一个servlet基本流程 servlet和JS ...
- JavaWeb项目中的Servlet
1.创建Servlet 2.在jsp中用ajax调用 $.post("<%=request.getContextPath()%>/AjaxValidationServlet&qu ...
- JavaWeb中的关于html、jsp、servlet下的路径问题
1 前言 本文将对近期项目练习中出现的关于文件路径的问题进行分析和总结,主要涉及html页面中的href及ajax指向路径.jsp页面中href指向路径及servlet转发或重定向路径等内容,本文的分 ...
随机推荐
- 一文聊透Apache Hudi的索引设计与应用
Hudi索引在数据读和写的过程中都有应用.读的过程主要是查询引擎利用MetaDataTable使用索引进行Data Skipping以提高查找速度;写的过程主要应用在upsert写上,即利用索引查找该 ...
- 前端使用工具规范commit信息
前言 通过工具规范git提交信息也是工程化的一部分,在前端领域有一些工具为我们提供了相关功能,在这里做一下使用总结. commitlint commitlint是什么? 就像eslint用来检查js代 ...
- Go语言与其他高级语言的区别
概述: go语言与其他语言相比,go语言的关键字非常少,只有25个,c语言有37个,c++有84个,python有33个,java有53个. 差异1:go语言不允许隐式转换,别名和原有类型也不能进行隐 ...
- 低代码开发平台YonBuilder移动开发,开发阅读APP教程
设计实现效果如下图: 主要包括书架,阅读,收藏功能. 经过分析,我们可以先实现底部导航功能,和书架列表页面. 1. 使用 tabLayout 高级窗口实现底部导航 . 使用tabLayout 有两 ...
- .Net 7 的AOT的程序比托管代码更容易破解?
楔子 .Net 7的一个重要功能是把托管的源码编译成Native Code,也就是二进制文件.此举看似增加了程序反编译难度,实际上是减少了程序的破解难度.本篇在不触及整个程序架构的前提下,以简单的例子 ...
- [seaborn] seaborn学习笔记5-小提琴图VIOLINPLOT
文章目录 5 小提琴图Violinplot 1. 基础小提琴图绘制 Basic violinplot 2. 小提琴图样式自定义 Custom seaborn violinplot 3. 小提琴图颜色自 ...
- [编程基础] Python对象的浅拷贝与深拷贝笔记
Python中的赋值语句不创建对象的副本,它们只将名称绑定到对象.对于不可变的对象,这通常没有什么区别.但是对于处理可变对象或可变对象的集合,您可能需要寻找一种方法来创建这些对象的"真实副本 ...
- 【深入浅出Spring原理及实战】「源码调试分析」结合DataSourceRegister深入分析ImportBeanDefinitionRegistrar的源码运作流程
每日一句 人的一生中不可能会一帆风顺,总会遇到一些挫折,当你对生活失去了信心的时候,仔细的看一看.好好回想一下你所遇到的最美好的事情吧,那会让你感觉到生活的美好. 注入案例代码 如何通过实现Sprin ...
- LRU 缓存
力扣题目 146. LRU 缓存 实现 LRU 缓存需要用到哈希链表 LinkedHashMap. LinkedHashMap 是由哈希表和双链表结合而成的,它的结构如下所示. 用自带的 Linked ...
- Linux操作系统导学专栏(一)——专栏要讲些什么?
该专栏是为Linux内核开发编程做铺垫,如果你对操作系统很熟悉,想了解一些Linux内核发开的知识,请直接跳转至<Linux内核编程专栏>,如果你对Linux内核编程也很熟悉,想了解L ...