tomcat-embed是开源Web服务器Tomcat在java中的内嵌版本,均由 Apache Tomcat官方发布。但网络上关于tomcat-embed的文章并不多,而且大多是基于8.x的版本,与最新版本在部分用法上有所不同。例如较新的tomcat-embed 10.x如果沿用8.x的代码,我们是无法在启动后正常的通过localhost
进行访问的,在这里与大家分享一下lz的经验。
旧版的配置
我们先使用一个非常简单的例子来演示8.x版本的tomcat-embed配置启动流程:
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("hello");
}
}
上面是用来测试的servlet,接着我们初始化tomcat:
// class TomcatInit
// path是为了方便演示,个人不推荐这种写法
private static String path=TomcatInit.class.getProtectionDomain().getCodeSource().getLocation().getPath();
public static void main(String[] args) throws LifecycleException {
Tomcat tomcat = new Tomcat();
// 配置tomcat基本信息
tomcat.setBaseDir(path+"static");
Context context=tomcat.addContext("",path+"static");
// 添加servlet
Tomcat.addServlet(context,"MyServlet",new MyServlet());
context.addServletMappingDecoded("/hello","MyServlet");
// 启动tomcat并保持状态,否则程序运行完将直接关闭
tomcat.start();
tomcat.getServer().await();
}
关于这部分的配置在这里不再多说,网上相关教程还是不少的。但是当我们在9.x、10.x或更高版本中使用这段代码运行时,在浏览器中输入localhost:8080/hello
会提示无法访问。
解决方法
此时,我们需要在tomcat.start()
前面加入一段代码,Tomcat才会监听端口:
...省略其它代码
// tomcat9.0以上必须调用getConnector()方法之后才会监听端口
tomcat.getConnector();
// 启动tomcat并保持状态,否则程序运行完将直接关闭
tomcat.start();
tomcat.getServer().await();
原因分析
但通过查阅getConnector
的源码注释,我们发现以下说明:
If there's no connector defined, it will create and add a default connector using the port and address
specified in this Tomcat instance, and return it for further customization.
如果没有定义连接器,它将使用此Tomcat实例中指定的端口和地址创建并添加一个默认连接器,并返回它以进行进一步定制。
根据上面的说法,Tomcat应该会自己创建一个默认的连接器,并没有解释为什么需要调用getConnector()
才会激活端口监听。通过源码,我们猜测出了一种可能:
public Connector getConnector() {
Service service = getService();
if (service.findConnectors().length > 0) {
return service.findConnectors()[0];
}
// The same as in standard Tomcat configuration.
// This creates an APR HTTP connector if AprLifecycleListener has been
// configured (created) and Tomcat Native library is available.
// Otherwise it creates a NIO HTTP connector.
Connector connector = new Connector("HTTP/1.1");
connector.setPort(port);
service.addConnector(connector);
return connector;
}
其中service.addConnector(connector)
是一个值得注意的关键点,这里方法创建了一个connector
,并将其加入了当前tomcat实例的service
中。那么我们猜测,导致需要额外调用getConnetor()
的原因,是因为注释中所说的默认connetor
连接器创建其实是在getConnetor()
中创建的,而外部并不会主动去调用它。因此想创建默认的连接器,需要手动调用getConnetor()
,否则将不会自动创建。
但是为什么Tomcat 8.x版本不需要呢?对比两个版本的源码,我们得出了答案:
// 8.5.32
public void start() throws LifecycleException {
getServer();
getConnector();
server.start();
}
// 10.0.0
public void start() throws LifecycleException {
getServer();
server.start();
}
我们发现,旧版tomcat-embed在启动时自动调用了getConnetor()
,而新版是没有的,这也解释了为什么在高版本中需要额外调用getConnetor
的原因。