//jar包啟動入口 @SpringBootApplication public class HelloWorldApplcation { public static void main(String[] args) { SpringApplication.run(HelloWorldApplcation.class, args); } }
②、war包通常使用Tomcat進行部署啟動,在tomcat啟動war應用時,會先進行Servlet環境的初始化,之后才會進行到IOC容器的初始化,也就是說,在servlet初始化過程中是不能使用IOC依賴注入的。
public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(HelloWorldApplcation.class); } }
@SpringBootApplication public class HelloWorldApplcation { public static void main(String[] args) { ApplicationContext applicationContext = SpringApplication.run(HelloWorldApplcation.class, args); SocketServer socketServer =applicationContext.getBean(SocketServer.class); socketServer.start(); } }
2.2、war包情況下,由於是先進行Servlet環境的初始化,然后再進行IOC容器的創建,有了這個先后順序,即知道,是不可能在Servlet創建時拿到WebApplicationContext對象的,下面是我的驗證
package com.geniuses.sewage_zero_straight.listener; import com.geniuses.sewage_zero_straight.net.socket.SocketServer; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; @Slf4j @WebListener public class SocketListener implements ServletContextListener { @Autowired private SocketServer socketServer; @Override public void contextInitialized(ServletContextEvent sce) { log.info("准備啟動Socket服務..."); log.info("SocketServer:{}", socketServer); socketServer.start(); } @Override public void contextDestroyed(ServletContextEvent sce) { } }

package com.geniuses.sewage_zero_straight.config; import lombok.extern.slf4j.Slf4j; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; @Slf4j @HandlesTypes({WebApplicationInitializer.class}) public class SocketConfig implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext); log.info("application: {}", webApplicationContext); //log.info("SocketServer: {}", webApplicationContext.getBean(SocketServer.class)); } }

// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.boot.web.servlet.support; import java.util.Collections; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.builder.ParentContextApplicationContextInitializer; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.util.Assert; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ConfigurableWebEnvironment; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; public abstract class SpringBootServletInitializer implements WebApplicationInitializer { protected Log logger; private boolean registerErrorPageFilter = true; public SpringBootServletInitializer() { } protected final void setRegisterErrorPageFilter(boolean registerErrorPageFilter) { this.registerErrorPageFilter = registerErrorPageFilter; } public void onStartup(ServletContext servletContext) throws ServletException { this.logger = LogFactory.getLog(this.getClass()); //這里開始創建IOC容器 WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext); if (rootAppContext != null) { servletContext.addListener(new ContextLoaderListener(rootAppContext) { public void contextInitialized(ServletContextEvent event) { } }); } else { this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context"); } } //IOC容器創建方法 protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) { SpringApplicationBuilder builder = this.createSpringApplicationBuilder(); builder.main(this.getClass()); ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext); if (parent != null) { this.logger.info("Root context already created (using as parent)."); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null); builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)}); } builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)}); builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); builder = this.configure(builder); builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)}); SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) { application.addPrimarySources(Collections.singleton(this.getClass())); } Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation"); if (this.registerErrorPageFilter) { application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class)); } return this.run(application); } protected WebApplicationContext run(SpringApplication application) { return (WebApplicationContext)application.run(new String[0]); } } }
這個時候我想到,如果不將SocketServer交由IOC進行管理,那么可以在實現的WebApplicationInitializer自定義類中啟動SocketServer
package com.geniuses.sewage_zero_straight.config; import lombok.extern.slf4j.Slf4j; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; /** * */ @Slf4j @HandlesTypes({WebApplicationInitializer.class}) public class SocketConfig implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { new Thread(new SocketRunnable(new SocketServer())).start(); } }
這里為什么要用一個新的線程來啟動Socket服務,因為SocketServer服務端的實現中,使用while循環進行等待客戶端socket進行連接,啟動時便開始等待(阻塞),如果不使用一個新的線程啟動,那么整個war應用服務啟動線程便會在此處阻塞。這樣雖然可以解決Socket服務隨着應用啟動而啟動,但是又出現了一個新的問題,那就是SocketServer無法使用@Autowired了,因為SocketServer沒有被IOC進行管理,無法進行依賴的注入。如果你不需要依賴,那么此法可行。
然后我又開始思考了,如果我需要進行依賴注入,由IOC進行SocketServer的管理,那么我要怎么樣才能獲取到WebApplicationContext對象,然后再通過該對象拿到SocketServer實例,然后再啟動Socket服務。如果我繼承了SpringBootServletInitializer,並重寫onStartup方法,再重寫的onStartup方法中進行Socket服務的啟動會怎么樣,最后發現Socket服務啟動了,但是進行了兩次onStartup方法的調用,第一次是自定義的SpringBootServletInitializer的實現類,第二次便是SpringBootServletInitializer類。即便只會啟動一次,這樣的代碼也具有着極強的侵入性,便放棄了。
@Override public void onStartup(ServletContext servletContext) throws ServletException { this.logger = LogFactory.getLog(this.getClass()); //這里開始創建IOC容器 WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext); //在這里進行啟動Socket服務 new Thread(new SocketRunnable(rootAppContext.getBean(SocketServer.class))).start(); if (rootAppContext != null) { servletContext.addListener(new ContextLoaderListener(rootAppContext) { public void contextInitialized(ServletContextEvent event) { } }); } else { this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context"); } }
package com.geniuses.sewage_zero_straight.listener; import com.geniuses.sewage_zero_straight.net.socket.SocketServer; import com.geniuses.sewage_zero_straight.net.socket.SocketThread; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.annotation.WebListener; @Slf4j @WebListener public class MyContextLoaderListener extends ContextLoaderListener { @Override public void contextInitialized(ServletContextEvent event) { ServletContext servletContext = event.getServletContext(); WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext); log.info("webApplicationContext:{}",webApplicationContext); SocketThread socketThread = webApplicationContext.getBean(SocketThread.class); servletContext.setAttribute("SocketThread", socketThread);//在這里放進去之后......,好像也沒有必要放,可以從WebApplicationContext中獲取... log.info("socketThread:{}", socketThread); socketThread.start(); } }

在這里由SocketThread實現InitializingBean,實現afterPropertiesSet,這樣,在該類的依賴注入完畢之后,會自動調用afterPropertiesSet方法,這樣便可以在這里啟動socket服務。關於InitializingBean的介紹,參見上面的這個鏈接。
package com.geniuses.sewage_zero_straight.net.socket; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Slf4j @Component public class SocketThread extends Thread implements InitializingBean { @Autowired private SocketServer socketServer; @Override public void run() { log.info("當前線程名:{}", Thread.currentThread().getName()); log.info("由當前線程開始啟動Socket服務..."); socketServer.start(); } @Override public void afterPropertiesSet() throws Exception { start(); } }

