嵌入式码头服务器启动后立即初始化 servlet

Init servlet instantly after the embedded jetty server starts

我需要在 Jetty 嵌入式服务器启动后 运行 我自己的逻辑。由于 classloader 问题,我没有从主 class 启动它。一个理想的解决方案似乎是 运行 从 servlet 初始化中整合我的服务器逻辑。但是 init 函数和构造函数在码头服务器启动后不会被调用。在第一个 HTTP 请求期间正在创建 servlet 的实例。是否可以告诉 jetty 立即初始化我的 servlet,或者我真的需要用我的自定义 classloader 加载所有 classes 然后启动 jetty 服务器?

这是主要的class:

public class ServerLauncher {
    public static void main(String[] args) {
        JettyServerLauncher.launchHttp("target/server.war", "0.0.0.0", 8080);
        // Starting my own logic here is causing classloader issues, because WebSocket classes are loaded by other classloader than my classes, that is the reason why I moved it into the servlet
    }
}

这是我的 Jetty 嵌入式服务器启动器:

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.*;

import java.io.File;

public class JettyServerLauncher {
    private static boolean isHttps;
    private static File keyStoreFile;
    private static String warPath;
    private static String host;
    private static int httpPort;
    private static int httpsPort;
    private static String keyStorePath;
    private static String keyStorePass;
    private static boolean needClientAuth;

    public static void launchHttp(String warPath, String host, int httpPort) {
        JettyServerLauncher.isHttps = false;
        JettyServerLauncher.warPath = warPath;
        JettyServerLauncher.host = host;
        JettyServerLauncher.httpPort = httpPort;

        launch();
    }

    public static void launchHttps(String warPath, String host, String keyStorePath, String keyStorePass, int httpPort, int httpsPort, boolean needClientAuth) {
        JettyServerLauncher.isHttps = true;
        JettyServerLauncher.warPath = warPath;
        JettyServerLauncher.host = host;
        JettyServerLauncher.httpPort = httpPort;
        JettyServerLauncher.httpsPort = httpsPort;
        JettyServerLauncher.keyStorePath = keyStorePath;
        JettyServerLauncher.keyStorePass = keyStorePass;
        JettyServerLauncher.needClientAuth = needClientAuth;

        launch();
    }

    private static void launch() {
        Server server = null;

        try {
            System.out.println("Initializing jetty server...");

            if (isHttps) loadKeyStores(keyStorePath);

            // Create jetty server
            server = new Server(httpPort);

            // Setup connectors
            Connector httpConnector = createHttpConnector(server, host, httpPort, httpsPort);
            if (isHttps) {
                Connector httpsConnector = createHttpsConnector(server, host, httpsPort, keyStoreFile, keyStorePass, needClientAuth);
                server.setConnectors(new Connector[]{httpConnector, httpsConnector});
            } else {
                server.setConnectors(new Connector[]{httpConnector});
            }

            // Add handlers for requests to collection of handlers
            HandlerCollection handlers = new ContextHandlerCollection();
            //handlers.addHandler(new SecuredRedirectHandler());
            handlers.addHandler(createWebApp(warPath));

            server.setHandler(handlers);
            server.dump();

            System.out.println("Starting jetty websocket and web server...");
            server.start();
            server.join();
        } catch (Throwable t) {
            t.printStackTrace();
            System.err.println("Server initialization failed!");
            System.out.println("Stopping the server...");
            try {
                server.stop();
            } catch (Exception ignored) {}
        }
    }

    private static WebAppContext createWebApp(String warPath) {
        WebAppContext webApp = new WebAppContext();
        webApp.setContextPath("/");
        webApp.setWar(new File(warPath).getAbsolutePath());
        webApp.setThrowUnavailableOnStartupException(true);

        // Enable support for JSR-356 javax.websocket
        webApp.setAttribute("org.eclipse.jetty.websocket.jsr356", Boolean.TRUE);

        // Jetty will scan project for configuration files... This is very important for loading websocket endpoints by annotation automatically
        webApp.setConfigurations(new Configuration[] {
                new AnnotationConfiguration(),
                new WebInfConfiguration(),
                new WebXmlConfiguration(),
                new MetaInfConfiguration(),
                new FragmentConfiguration(),
                new EnvConfiguration(),
                new PlusConfiguration(),
                new JettyWebXmlConfiguration()
        });

        return webApp;
    }

    private static Connector createHttpConnector(Server server, String host, int httpPort, int httpsPort) {
        HttpConfiguration httpConf = new HttpConfiguration();
        httpConf.setSendServerVersion(false);
        if (isHttps) httpConf.setSecurePort(httpsPort);
        ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConf));
        connector.setPort(httpPort);
        connector.setHost(host);

        return connector;
    }

    private static Connector createHttpsConnector(Server server, String host, int httpsPort, File keyStoreFile, String keyStorePass, boolean needClientAuth) {
        SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
        sslContextFactory.setKeyStorePath(keyStoreFile.getAbsolutePath());
        sslContextFactory.setKeyStorePassword(keyStorePass);
        sslContextFactory.setNeedClientAuth(needClientAuth);

        // Setup HTTPS Configuration
        HttpConfiguration httpsConf = new HttpConfiguration();
        httpsConf.setSendServerVersion(false);
        httpsConf.setSecureScheme("https");
        httpsConf.setSecurePort(httpsPort);
        httpsConf.setOutputBufferSize(32768);
        httpsConf.setRequestHeaderSize(8192);
        httpsConf.setResponseHeaderSize(8192);
        httpsConf.addCustomizer(new SecureRequestCustomizer()); // adds ssl info to request object

        // Establish the HTTPS ServerConnector
        ServerConnector httpsConnector = new ServerConnector(server, new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()), new HttpConnectionFactory(httpsConf));
        httpsConnector.setPort(httpsPort);
        httpsConnector.setHost(host);

        return httpsConnector;
    }

    private static void loadKeyStores(String keyStorePath) {
        keyStoreFile = new File(keyStorePath);
        if (!keyStoreFile.exists()) {
            throw new RuntimeException("Key store file does not exist on path '"+keyStoreFile.getAbsolutePath()+"'");
        }
    }
}

这是我的 servlet:

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(displayName = "MyServlet", urlPatterns = { "/*" })
public class MyServlet extends HttpServlet {
    @Override
    public void init() {
        // start new Thread with my server logic here (avoid classloader issues)
        // but at least one HTTP request is needed to start it from this place
    }

    @Override
    public void destroy() {}

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        // handle http requests
    }
}

我在 google 上找到了这个,但我不知道如何在我的案例中使用它。 https://www.eclipse.org/lists/jetty-users/msg02109.html

感谢您的帮助。

如果你只是想让 servlet 在启动时初始化,那么使用注释 ...

@WebServlet(
    displayName = "MyServlet", 
    urlPatterns = { "/*" },
    loadOnStartup = 1
)

或者,您可以注册一个 javax.servlet.ServletContextListener 来执行您需要的 contextInitialized(ServletContextEvent sce) 行为。

提示:如果您为嵌入使用定义自定义 ServletContextListener,您可以将它从您正在使用的 WAR 外部添加到 WebAppContext

示例:

webApp.getServletHandler()
    .addListener(new ListenerHolder(MyContextListener.class));

此外,此代码块是错误的,它向您显示 copy/pasted 来自旧代码片段(此技术大约来自 Jetty 9.0.0 至 9.2.16)

        webApp.setConfigurations(new Configuration[] {
                new AnnotationConfiguration(),
                new WebInfConfiguration(),
                new WebXmlConfiguration(),
                new MetaInfConfiguration(),
                new FragmentConfiguration(),
                new EnvConfiguration(),
                new PlusConfiguration(),
                new JettyWebXmlConfiguration()
        });

在 Jetty 9.4.x 中,您永远不会像那样直接配置 webApp.setConfigurations(),而是使用服务器上定义的 Configuration.ClassList ...

发件人:9.4.44.v20210927 - embedded/LikeJettyXml.java

Configuration.ClassList classlist = Configuration.ClassList
    .setServerDefault(server);
classlist.addAfter(
    "org.eclipse.jetty.webapp.FragmentConfiguration",
    "org.eclipse.jetty.plus.webapp.EnvConfiguration",
    "org.eclipse.jetty.plus.webapp.PlusConfiguration");
classlist.addBefore(
    "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
    "org.eclipse.jetty.annotations.AnnotationConfiguration");

从 Jetty 10.0.0 开始,您永远不会指定配置 类 或它们的顺序,因为支持 JAR 的存在就足够了,并且在 Jetty 10 内部顺序被正确解析。

但是如果您需要添加配置(由于 non-standard 部署问题 Java ServiceLoader 不起作用),那么您仍然需要在服务器上配置额外的配置对象(但不用担心这些配置的正确顺序)

来自10.0.7 - embedded/demos/LikeJettyXml.java

Configurations.setServerDefault(server).add(
    new EnvConfiguration(), 
    new PlusConfiguration(), 
    new AnnotationConfiguration()
);