SpringBoot集成Socket服务后打包(war包)启动时如何启动Socket服务(web应用外部tomcat启动)
//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(); } }