最近学习Tomcat源码,平时新建maven项目的时候,使用springboot内嵌tomcat启动,现在需要使用外部tomcat启动,以便于学习tomcat和spring代码之间的执行关系。 添加完外部tomcat后突然发现无法将 maven项目的web项目添加到tomcat中。
就是通过下图添加maven的 web项目,如过在JavaEE窗口下,项目中没有Deployment Descriptor: 的标记可能你的项目是无法添加到tomcat中的。
因此你想让你的项目可以添加到Tomcat中,需要按照下面的步骤来操作自己的项目。
1.首先 鼠标右键项目属性 properties。
2.选择 Project Facets 进行添加Dynamic Web Module选项,你可以选择其中的版本信息。
3.应用到项目,最后你就可以添加了。
按照上面的步骤可能你就可以将项目添加到Tomcat中了,但是你不一定能运行成功,因为还需要在你的pom文件中添加一些引用:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.1.RELEASE</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
以及在启动类中做一些改变:
加一个@ServletComponentScan 注解,以及继承 SpringBootServletInitializer 类,然后重写 configure(SpringApplicationBuilder springApplicationBuilder)方法。关于为什么需要继承SpringBootServletInitializer 后续可能做出进一步的分析。
看一下 SpringBootServletInitializer 的具体实现。这个类实现了WebApplicationInitializer 接口。
此处你只需要知道:
在tomcat启动的时候会通过java 的SPI机制 加载Spring-web jar目录下的文件然后加载类
SpringServletContainerInitializer ,然后此类会在tomcat启动时掉用他的onStartup 方法。
StandardContex初始化时会将实现了 WebApplicationInitializer 接口的类加载到 Context中,后面会调用 SpringServletContainerInitializer 类的onStartup方法时,做为参数Set<Class<?>> webAppInitializerClasses传入。后面会调用
WebApplicationInitializer实现类的的方法:
onStartup(ServletContext servletContext)。
想进一步了解其中原理可以看我的另一篇文章:
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
protected Log logger; // Don't initialize early
private boolean registerErrorPageFilter = true;
/**
* Set if the {@link ErrorPageFilter} should be registered. Set to {@code false} if
* error page mappings should be handled via the server and not Spring Boot.
* @param registerErrorPageFilter if the {@link ErrorPageFilter} should be registered.
*/
protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) {
this.registerErrorPageFilter = registerErrorPageFilter;
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
else {
this.logger.debug("No ContextLoaderListener registered, as "
+ "createRootApplicationContext() did not "
+ "return an application context");
}
}
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
SpringApplicationBuilder builder = createSpringApplicationBuilder();
builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(getClass()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
return run(application);
}
/**
* Returns the {@code SpringApplicationBuilder} that is used to configure and create
* the {@link SpringApplication}. The default implementation returns a new
* {@code SpringApplicationBuilder} in its default state.
* @return the {@code SpringApplicationBuilder}.
* @since 1.3.0
*/
protected SpringApplicationBuilder createSpringApplicationBuilder() {
return new SpringApplicationBuilder();
}
/**
* Called to run a fully configured {@link SpringApplication}.
* @param application the application to run
* @return the {@link WebApplicationContext}
*/
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run();
}
private ApplicationContext getExistingRootWebApplicationContext(
ServletContext servletContext) {
Object context = servletContext.getAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (context instanceof ApplicationContext) {
return (ApplicationContext) context;
}
return null;
}
/**
* Configure the application. Normally all you would need to do is to add sources
* (e.g. config classes) because other settings have sensible defaults. You might
* choose (for instance) to add default command line arguments, or set an active
* Spring profile.
* @param builder a builder for the application context
* @return the application builder
* @see SpringApplicationBuilder
*/
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder;
}
private static final class WebEnvironmentPropertySourceInitializer
implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
private final ServletContext servletContext;
private WebEnvironmentPropertySourceInitializer(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (environment instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) environment)
.initPropertySources(this.servletContext, null);
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
}
代码如下:
@SpringBootApplication(scanBasePackages = {"com.hcgao","com.boot"})
//@ComponentScan(basePackages = {"com.hcgao.*","com.boot.**"})
//@EnableAsync
@ServletComponentScan
public class Application extends SpringBootServletInitializer{
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder springApplicationBuilder){
return springApplicationBuilder.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
添加完成后,如果你通过tomcat启动成功,那么祝贺你你成功了。
但是我在这个地方又遇到了一点小问题。
下面看一下日志,tomcat虽然启动了,但是没有启动springBoot项目中的信息。
说明我们的springBoot项目并没有启动成功。
很明显是因为tomcat启动时没有扫描到 Maven中的jar,也没有加载到 spring-web的包
所以你需要通过下面的操作,将maven扫描包加载到项目中:
最后启动结果:
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: Server.服务器版本: Apache Tomcat/9.0.34
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 服务器构建: Apr 3 2020 12:02:52 UTC
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 服务器版本号(:9.0.34.0
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: OS Name: Windows 10
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: OS.版本: 10.0
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 架构: amd64
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: Java 环境变量: H:\Program Files\Java\jdk1.8.0_211\jre
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: JVM 版本: 1.8.0_211-b12
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: JVM.供应商: Oracle Corporation
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: CATALINA_BASE:[D:\gaohaichneg_jiagou\.metadata\.plugins\org.eclipse.wst.server.core\tmp0]
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: CATALINA_HOME: G:\apache-tomcat-9.0.34
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:[-Dcatalina.base=D:\gaohaichneg_jiagou\.metadata\.plugins\org.eclipse.wst.server.core\tmp0]
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:[-Dcatalina.home=G:\apache-tomcat-9.0.34]
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:[-Dwtp.deploy=D:\gaohaichneg_jiagou\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps]
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:[-Djava.endorsed.dirs=G:\apache-tomcat-9.0.34\endorsed]
五月 02, 2020 3:42:15 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:[-Dfile.encoding=GBK]
五月 02, 2020 3:42:15 下午 org.apache.catalina.core.AprLifecycleListener lifecycleEvent
信息: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [H:\Program Files\Java\jdk1.8.0_211\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;H:/Program Files/Java/jre1.8.0_211/bin/server;H:/Program Files/Java/jre1.8.0_211/bin;H:/Program Files/Java/jre1.8.0_211/lib/amd64;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Windows\System32\wbem;H:\Program Files\Java\jdk1.8.0_211\bin;H:\Program Files\Java\jdk1.8.0_211\jre\bin;H:\Program Files\mysql-5.7.16-winx64\bin;E:\zookeeper-3.4.6\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;F:\oxygenEnvir\apache-maven-3.3.9\bin;F:\oxygenEnvir\nodeJs\;F:\oxygenEnvir\nodeJs\node_global;H:\Program Files\Git\cmd;H:\Program Files\Anaconda311111;C:\WINDOWS\System32\OpenSSH\;H:\AppData\Local\Programs\Python\Python37;D:\huanjing\gradle-6.0\bin;H:\Program Files\erl10.3\bin;H:\Program Files\RabbitMQ Server\rabbitmq_server-3.8.1\sbin;C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit\;H:\Program Files\MATLAB\R2018a\runtime\win64;H:\Program Files\MATLAB\R2018a\bin;C:\Users\高海成\AppData\Local\Programs\Python\Python36\Scripts\;C:\Users\高海成\AppData\Local\Programs\Python\Python36\;H:\AppData\Local\Programs\Python\Python37\Scripts\;H:\AppData\Local\Programs\Python\Python37\;H:\AppData\Local\Programs\Python\Python36\Scripts\;H:\AppData\Local\Programs\Python\Python36\;C:\Users\高海成\AppData\Local\Microsoft\WindowsApps;C:\Users\高海成\AppData\Local\GitHubDesktop\bin;C:\Users\高海成\AppData\Roaming\npm;F:\oxygenEnvir\VS Code\bin;C:\Users\高海成\AppData\Local\Microsoft\WindowsApps;C:\Users\高海成\AppData\Local\BypassRuntm;h:\Program Files\JetBrains\PyCharm Community Edition 2019.3.1\bin;;D:\Users\YUNWEN\eclipse;;.]
五月 02, 2020 3:42:16 下午 org.apache.coyote.AbstractProtocol init
信息: 初始化协议处理器 ["http-nio-8080"]
五月 02, 2020 3:42:16 下午 org.apache.catalina.startup.Catalina load
信息: 服务器在[1,485]毫秒内初始化
五月 02, 2020 3:42:16 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service [Catalina]
五月 02, 2020 3:42:16 下午 org.apache.catalina.core.StandardEngine startInternal
信息: 正在启动 Servlet 引擎:[Apache Tomcat/9.0.34]
五月 02, 2020 3:42:23 下午 org.apache.jasper.servlet.TldScanner scanJars
信息: 至少有一个JAR被扫描用于TLD但尚未包含TLD。 为此记录器启用调试日志记录,以获取已扫描但未在其中找到TLD的完整JAR列表。 在扫描期间跳过不需要的JAR可以缩短启动时间和JSP编译时间。
五月 02, 2020 3:42:23 下午 org.apache.catalina.core.ApplicationContext log
信息: 2 Spring WebApplicationInitializers detected on classpath
Application Version: ${ruoyi.version}
Spring Boot Version: 2.1.1.RELEASE
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永不宕机 永无BUG //
2020-05-02 15:42:25,587 [main] INFO com.boot.Application - Starting Application on DESKTOP-1H6IM7E with PID 2300 (D:\gaohaichneg_jiagou\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\hcgao-web\WEB-INF\classes started by 高海成 in D:\Users\YUNWEN\eclipse)
2020-05-02 15:42:25,597 [main] INFO com.boot.Application - No active profile set, falling back to default profiles: default
2020-05-02 15:42:27,808 [main] INFO o.s.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'asyncConfig' of type [com.boot.config.AsyncConfig$$EnhancerBySpringCGLIB$$79d742b2] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-05-02 15:42:28,017 [main] INFO org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/hcgao-web] - Initializing Spring embedded WebApplicationContext
2020-05-02 15:42:28,021 [main] INFO org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 2350 ms
2020-05-02 15:42:30,176 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Initializing ExecutorService 'applicationTaskExecutor'
2020-05-02 15:42:31,316 [main] WARN org.thymeleaf.templatemode.TemplateMode - [THYMELEAF][main] Template Mode 'HTML5' is deprecated. Using Template Mode 'HTML' instead.
2020-05-02 15:42:31,406 [main] INFO org.springframework.boot.autoconfigure.web.servlet.WelcomePageHandlerMapping - Adding welcome page template: index
2020-05-02 15:42:31,684 [main] INFO com.boot.Application - Started Application in 7.905 seconds (JVM running for 16.59)
2020-05-02 15:42:31,706 [main] INFO org.apache.coyote.http11.Http11NioProtocol - 开始协议处理句柄["http-nio-8080"]
2020-05-02 15:42:31,716 [main] INFO org.apache.catalina.startup.Catalina - Server startup in [14,903] milliseconds
最后终于启动成功了。