zoukankan      html  css  js  c++  java
  • SpringBoot集成Socket服务后打包(war包)启动时如何启动Socket服务(web应用外部tomcat启动)

     
    1、首先知道SpringBoot打包为jar和war包是不一样的(只讨论SpringBoot环境下web应用打包)
        1.1、jar和war包的打开方式不一样,虽然都依赖java环境,但是jar包只需要外部提供java环境,war需要外部提供java和servlet环境
        1.2、jar包和war包的启动流程不一样:
                ①、jar包的启动时直接由项目的主函数开始启动,此时会先初始化IOC容器,然后才会进行内置Servlet环境(一般为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);
        }
    
    }
    2、实现在springboot应用启动的时候启动Socket服务
        2.1、jar包情况下,将Socket服务交由IOC容器进行管理,然后在IOC容器创建完成后,获取到已经初始化的Socket服务实例,然后开始Socket服务。在这样的情况下,可以在Socket服务类上进行依赖注入,也就是说可以使用@Autowired
    @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));
        }
    }

    关于WebApplicationInitializer理解,参见:https://blog.csdn.net/zq17865815296/article/details/79464403
    以上是为测试Servlet环境中能否拿到IOC容器和能否注入Bean,发现并不能拿到对象
     
    我就开始想啊,要怎么才能拿到IOC容器对象嘞,随后发现SpringBootServletInitializer便是创建IOC容器的起始点
    //
    // 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");
            }
    
        }
    然后我又开始思考,既然有ServletContextListener 监听ServletContext对象的创建以及销毁,那么是不是有监听IOC容器的创建和销毁嘞,随后开始进行了百度
    .................................................
    是的,没错,这个真的有,详情参见:https://blog.csdn.net/qq_40693828/article/details/81448934
    感谢大佬
    其实在这里想到了Javaweb整合spring的时候,好像有一个初始化IOC容器的监听器来着,忘了,活该自己技术差......
    在这里将SocketServer注入到SocketThread中,这里还有一个疑问,在这里使用@Autowired注入SocketThread不是很稳定,猜测可能与IOC的初始化时间前后有关系,不知道是为何
    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();
        }
    }

    在百度的过程中,突然看见一博客,https://www.jianshu.com/p/90063de8cf9b,感谢这位大神

    在这里由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();
        }
    }

    至此,我是不想再想了。
    在这里感谢下面的几篇博客:
    自启动监听类(InitializingBean)介绍:https://www.jianshu.com/p/90063de8cf9b
    springboot中启动流程说明:https://www.jb51.net/article/142981.htm
    SpringBoot中WebApplicationInitializer的理解:https://blog.csdn.net/zq17865815296/article/details/79464403
    Spring中ContextLoaderListener作用:https://blog.csdn.net/qq_40693828/article/details/81448934
     
    最后说一下我的开发环境:win7_64旗舰版、jdk1.8、idea、tomcat8.5
  • 相关阅读:
    Jmeter与LoadRunner 测试Java项目的坑
    关于<forEach>的<if>混合使用显示数据
    无题。省
    无题。思
    767A Snacktower
    喵哈哈村的括号序列

    队列
    优先队列
    768A Oath of the Night's Watch
  • 原文地址:https://www.cnblogs.com/threadj/p/10553238.html
Copyright © 2011-2022 走看看