简单使用可以参考这篇文章:
https://blog.csdn.net/the_one_and_only/article/details/105177506
基本原理就是将Tomcat对象中的Host和Connector中的port设置成相应的host:port,配置Tomcat上下文(环境配置),最后加入Servlet,然后跑起来。
Tomcat是这样的:
Tomcat源码注释:
/**
* Minimal tomcat starter for embedding/unit tests.
*
* <p>
* Tomcat supports multiple styles of configuration and
* startup - the most common and stable is server.xml-based,
* implemented in org.apache.catalina.startup.Bootstrap.
*
* <p>
* This class is for use in apps that embed tomcat.
*
* <p>
* Requirements:
* <ul>
* <li>all tomcat classes and possibly servlets are in the classpath.
* (for example all is in one big jar, or in eclipse CP, or in
* any other combination)</li>
*
* <li>we need one temporary directory for work files</li>
*
* <li>no config file is required. This class provides methods to
* use if you have a webapp with a web.xml file, but it is
* optional - you can use your own servlets.</li>
* </ul>
*
* <p>
* There are a variety of 'add' methods to configure servlets and webapps. These
* methods, by default, create a simple in-memory security realm and apply it.
* If you need more complex security processing, you can define a subclass of
* this class.
*
* <p>
* This class provides a set of convenience methods for configuring web
* application contexts; all overloads of the method <code>addWebapp()</code>.
* These methods are equivalent to adding a web application to the Host's
* appBase (normally the webapps directory). These methods create a Context,
* configure it with the equivalent of the defaults provided by
* <code>conf/web.xml</code> (see {@link #initWebappDefaults(String)} for
* details) and add the Context to a Host. These methods do not use a global
* default web.xml; rather, they add a {@link LifecycleListener} to configure
* the defaults. Any WEB-INF/web.xml and META-INF/context.xml packaged with the
* application will be processed normally. Normal web fragment and
* {@link javax.servlet.ServletContainerInitializer} processing will be applied.
*
* <p>
* In complex cases, you may prefer to use the ordinary Tomcat API to create
* webapp contexts; for example, you might need to install a custom Loader
* before the call to {@link Host#addChild(Container)}. To replicate the basic
* behavior of the <code>addWebapp</code> methods, you may want to call two
* methods of this class: {@link #noDefaultWebXmlPath()} and
* {@link #getDefaultWebXmlListener()}.
*
* <p>
* {@link #getDefaultWebXmlListener()} returns a {@link LifecycleListener} that
* adds the standard DefaultServlet, JSP processing, and welcome files. If you
* add this listener, you must prevent Tomcat from applying any standard global
* web.xml with ...
*
* <p>
* {@link #noDefaultWebXmlPath()} returns a dummy pathname to configure to
* prevent {@link ContextConfig} from trying to apply a global web.xml file.
*
* <p>
* This class provides a main() and few simple CLI arguments,
* see setters for doc. It can be used for simple tests and
* demo.
*
* @see <a href="https://gitbox.apache.org/repos/asf?p=tomcat.git;a=blob;f=test/org/apache/catalina/startup/TestTomcat.java">TestTomcat</a>
* @author Costin Manolache
*/
翻译:
用于嵌入/单元测试的最小tomcat启动器。
Tomcat支持多种类型的配置和startup,最常见和稳定的是基于server.xml的,在org.apache.catalina.startup.Bootstrap中实现。
这个类用于嵌入tomcat的应用程序。
所有tomcat类(可能还有servlet)都在类路径中。(例如所有的都在一个大的罐子里,或在eclipse CP里,或在任何其他组合)
我们需要一个临时目录来存放工作文件
不需要配置文件。该类提供方法如果你有一个带有web.xml文件的webapp,但它确实是optional,你可以使用你自己的servlet。
有各种各样的“添加”方法来配置servlet和webapps。这些方法,默认情况下,创建一个简单的内存安全域并应用它。如果你需要更复杂的安全处理,你可以定义这个类。
这个类提供了main()和一些简单的CLI参数,参见设置文件。它可以用于简单的测试和演示。
start是这样启动的:
/**
* Start the server.
*
* @throws LifecycleException Start error
*/
public void start() throws LifecycleException {
getServer();
server.start();
}
/**
* Get the server object. You can add listeners and few more
* customizations. JNDI is disabled by default.
* @return The Server
*/
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;
}
要注意这个StandardService和StandardServer
public class StandardService extends LifecycleMBeanBase implements Service
public final class StandardServer extends LifecycleMBeanBase implements Server
我们从LifecycleMBeanBase 字面上可以看出,这是一个跟Tomcat组件生命周期有关的类
详情可以参考:https://blog.csdn.net/wojiushiwo945you/article/details/73331057、
也就是说,到这里,我们已经知道Tomcat如何启动了。但是Tomcat里边有什么呢?
Tomcat是Servlet和JSP支持的容器,我们不管是传统的javaweb还是springboot都离不开Servlet。
在一开始的例子中,我们可以看到这两行代码:
Wrapper wrapper = tomcat.addServlet("/", "DemoServlet", new EasyServlet());
wrapper.addMapping("/embeddedTomcat");
其实就是在tomcat容器中丢入Servlet,并且配置路径映射。Servlet一般继承自HttpServlet。
HttpServlet:
/**
* Provides an abstract class to be subclassed to create
* an HTTP servlet suitable for a Web site. A subclass of
* <code>HttpServlet</code> must override at least
* one method, usually one of these:
*
* <ul>
* <li> <code>doGet</code>, if the servlet supports HTTP GET requests
* <li> <code>doPost</code>, for HTTP POST requests
* <li> <code>doPut</code>, for HTTP PUT requests
* <li> <code>doDelete</code>, for HTTP DELETE requests
* <li> <code>init</code> and <code>destroy</code>,
* to manage resources that are held for the life of the servlet
* <li> <code>getServletInfo</code>, which the servlet uses to
* provide information about itself
* </ul>
*
* <p>There's almost no reason to override the <code>service</code>
* method. <code>service</code> handles standard HTTP
* requests by dispatching them to the handler methods
* for each HTTP request type (the <code>do</code><i>Method</i>
* methods listed above).
*
* <p>Likewise, there's almost no reason to override the
* <code>doOptions</code> and <code>doTrace</code> methods.
*
* <p>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
* <a href="http://java.sun.com/Series/Tutorial/java/threads/multithreaded.html">
* Java Tutorial on Multithreaded Programming</a> for more
* information on handling multiple threads in a Java program.
*/
意思就是说,这是一个用来创建Http站点的类的抽象。一般要至少重写里边的一些方法,如doGet、doPost、doPut、doDelete等方法。
servlet通常运行在多线程服务器上,所以要知道servlet必须处理并发请求,并注意同步访问共享资源。共享资源包括内存中的数据(如实例或类变量)和外部对象(例如文件、数据库连接和网络连接)。
我们来看其中一个doGet方法,如果我们不重写它,那么就是这样:
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
注释中有这么一段:
/**
* <p>Overriding this method to support a GET request also
* automatically supports an HTTP HEAD request. A HEAD
* request is a GET request that returns no body in the
* response, only the request header fields.
* **/
翻译:
覆盖此方法以支持GET请求,也自动支持HTTP HEAD请求(HEAD就是一个GET请求,但是在响应报文中,只有请求报头字段)。
查看其子类:
随便看一个DefaultServlet:大多数web应用默认的资源服务servlet,用于提供静态资源,如HTML页面和图像。
/**
* Process a GET request for the specified resource.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet-specified error occurs
*/
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// Serve the requested resource, including the data content
serveResource(request, response, true, fileEncoding);
}
那我们探究了Tomcat、Servlet,那么Servlet放入Tomcat的具体流程呢?
重新来看这两行代码:
Wrapper wrapper = tomcat.addServlet("/", "DemoServlet", new EasyServlet());
wrapper.addMapping("/embeddedTomcat");
addServlet的源码是这样的:
/**
* Add an existing Servlet to the context with no class.forName or
* initialisation.
* @param contextPath Context to add Servlet to
* @param servletName Servlet name (used in mappings)
* @param servlet The Servlet to add
* @return The wrapper for the servlet
*/
public Wrapper addServlet(String contextPath,
String servletName,
Servlet servlet) {
Container ctx = getHost().findChild(contextPath);
return addServlet((Context) ctx, servletName, servlet);
}
/**
* Static version of {@link #addServlet(String, String, Servlet)}.
* @param ctx Context to add Servlet to
* @param servletName Servlet name (used in mappings)
* @param servlet The Servlet to add
* @return The wrapper for the servlet
*/
public static Wrapper addServlet(Context ctx,
String servletName,
Servlet servlet) {
// will do class for name and set init params
Wrapper sw = new ExistingStandardWrapper(servlet);
sw.setName(servletName);
ctx.addChild(sw);
return sw;
}
tomcat在同一个Host下创建了子容器ctx ,而这个容器ctx将Servlet的包装ExistingStandardWrapper给add进来,也就是tomcat容器中放进了Servlet。就如上诉那些图讲诉的那样。
至于addMapping,就是添加映射:
/**
* Add a mapping associated with the Wrapper.
*
* @param mapping The new wrapper mapping
*/
@Override
public void addMapping(String mapping) {
mappingsLock.writeLock().lock();
try {
mappings.add(mapping);
} finally {
mappingsLock.writeLock().unlock();
}
if(parent.getState().equals(LifecycleState.STARTED)) {
fireContainerEvent(ADD_MAPPING_EVENT, mapping);
}
}
mappingsLock是ReentrantReadWriteLock(可重入读写锁)
mappings是ArrayList<String>
/**
* Notify all container event listeners that a particular event has
* occurred for this Container. The default implementation performs
* this notification synchronously using the calling thread.
*
* @param type Event type
* @param data Event data
*/
@Override
public void fireContainerEvent(String type, Object data) {
if (listeners.size() < 1) {
return;
}
ContainerEvent event = new ContainerEvent(this, type, data);
// Note for each uses an iterator internally so this is safe
for (ContainerListener listener : listeners) {
listener.containerEvent(event);
}
}
通知所有容器事件监听器,说某个特定事件的在此容器发生。默认实现执行使用调用线程同步通知。
listeners是CopyOnWriteArrayList<ContainerListener>();
也就是写入时复制的ArrayList,存的是ContainerListener。
/**
* Interface defining a listener for significant Container generated events.
* Note that "container start" and "container stop" events are normally
* LifecycleEvents, not ContainerEvents.
*
* @author Craig R. McClanahan
*/
public interface ContainerListener {
/**
* Acknowledge the occurrence of the specified event.
*
* @param event ContainerEvent that has occurred
*/
public void containerEvent(ContainerEvent event);
}
所有的ContainerListener通过以下方法加进来:
/**
* Add a container event listener to this component.
*
* @param listener The listener to add
*/
@Override
public void addContainerListener(ContainerListener listener) {
listeners.add(listener);
}
所以说我们这些监听器从哪里加过来,又监听哪些东西呢?
我们从Tomcat.addServlet一路追踪下去,看到了这么一个包装器的类,这也是我们之前所用到的Wrapper的最终实现。
/**
* Helper class for wrapping existing servlets. This disables servlet
* lifecycle and normal reloading, but also reduces overhead and provide
* more direct control over the servlet.
*/
public static class ExistingStandardWrapper extends StandardWrapper {
private final Servlet existing;
@SuppressWarnings("deprecation")
public ExistingStandardWrapper( Servlet existing ) {
this.existing = existing;
if (existing instanceof javax.servlet.SingleThreadModel) {
singleThreadModel = true;
instancePool = new Stack<>();
}
this.asyncSupported = hasAsync(existing);
}
private static boolean hasAsync(Servlet existing) {
boolean result = false;
Class<?> clazz = existing.getClass();
WebServlet ws = clazz.getAnnotation(WebServlet.class);
if (ws != null) {
result = ws.asyncSupported();
}
return result;
}
@SuppressWarnings("deprecation")
@Override
public synchronized Servlet loadServlet() throws ServletException {
if (singleThreadModel) {
Servlet instance;
try {
instance = existing.getClass().getConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new ServletException(e);
}
instance.init(facade);
return instance;
} else {
if (!instanceInitialized) {
existing.init(facade);
instanceInitialized = true;
}
return existing;
}
}
@Override
public long getAvailable() {
return 0;
}
@Override
public boolean isUnavailable() {
return false;
}
@Override
public Servlet getServlet() {
return existing;
}
@Override
public String getServletClass() {
return existing.getClass().getName();
}
}
首先,我们传给它我们之前创建的Servlet,然后它会判断是否是singleThreadModel(单线程模型),是的话把instancePool这个Stack<Servlet>,然后判断是否支持异步。
loadServlet(),看名字就是加载Servlet,用的是instance.init(facade);这行代码。
我们可以猜出,就是使用的设计模式中的Facade模式(包装器模式)。
facade是StandardWrapperFacade类对象,用来包装this,提供一些配置。
那么init方法呢?
/**
* Called by the servlet container to indicate to a servlet that the servlet
* is being placed into service. See {@link Servlet#init}.
* <p>
* This implementation stores the {@link ServletConfig} object it receives
* from the servlet container for later use. When overriding this form of
* the method, call <code>super.init(config)</code>.
*
* @param config
* the <code>ServletConfig</code> object that contains
* configuration information for this servlet
* @exception ServletException
* if an exception occurs that interrupts the servlet's
* normal operation
* @see UnavailableException
*/
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
/**
* A convenience method which can be overridden so that there's no need to
* call <code>super.init(config)</code>.
* <p>
* Instead of overriding {@link #init(ServletConfig)}, simply override this
* method and it will be called by
* <code>GenericServlet.init(ServletConfig config)</code>. The
* <code>ServletConfig</code> object can still be retrieved via
* {@link #getServletConfig}.
*
* @exception ServletException
* if an exception occurs that interrupts the servlet's
* normal operation
*/
public void init() throws ServletException {
// NOOP by default
}
也就是说,这里并不提供Servlet的初始化(毕竟它也只是个抽象类)
那我们依旧来看DefaultServlet。其中init方法太多内容,大多数的任务就是将对象中的属性赋值。
我们选其中重要的一行分析:
// Load the web resources
resources = (WebResourceRoot) getServletContext().getAttribute(Globals.RESOURCES_ATTR);
就是将上下文中的org.apache.catalina.resources域中的相关部分提取到resources中。
那么这个resources什么时候被用到呢?
doGet方法,定位到serveResource方法,有这么几行:
protected void serveResource(HttpServletRequest request,
HttpServletResponse response,
boolean content,
String inputEncoding) {
// ...
// Identify the requested resource path
String path = getRelativePath(request, true);
// ...
WebResource resource = resources.getResource(path);
// ...
ostream = response.getOutputStream();
// ...
writer = response.getWriter();
// ...
copy(...)
资源只有接口类型,不知道使用哪个实现类的方法,就用instanceOf来识别,以调用同样接口的不同解决方法。
很粗略,细节不继续探究。
我们回想一下,一开始的Context context = tomcat.addContext(host, “/”, classpath),或许也变成了后来DefaultServlet的资源路径上下文(有待考究)。
@_@
既然来了,觉得不错的话,点个赞或者关注呗