最近學習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
最後終於啓動成功了。