Jetty: Pass object from main method to servlet

2019-01-15 11:10发布

问题:

I have two classes Server (with the main method, starting the server) and StartPageServlet with a Servlet.

The most important part of the code is:

public class Server {
    public static void main(String[] args) throws Exception {
        // some code

        // I want to pass "anObject" to every Servlet.
        Object anObject = new Object();

        Server server = new Server(4000);
        ServletContextHandler context = 
            new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.addServlet(StartPageServlet.class, "/");
        // more code
}

And the StartPageServlet:

public class StartPageServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
            throws ServletException, IOException
    {
        // Here I want to access "anObject"
    }

How do I do this?

回答1:

Embedded Jetty is so wonderful here.

You have a few common options:

  1. Direct instantiation of the servlet, use constructors or setters, then hand it off to Jetty via the ServletHolder (can be any value or object type)
  2. Add it to the ServletContext in your main, and then access it via the ServletContext in your application (can be any value or object type).

Examples:

package jetty;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class ObjectPassingExample
{
    public static void main(String args[]) throws Exception
    {
        Server server = new Server(8080);

        ServletContextHandler context = new ServletContextHandler();
        context.setContextPath("/");

        // Option 1: Direct servlet instantiation and ServletHolder
        HelloServlet hello = new HelloServlet("everyone");
        ServletHolder helloHolder = new ServletHolder(hello);
        context.addServlet(helloHolder, "/hello/*");

        // Option 2: Using ServletContext attribute
        context.setAttribute("my.greeting", "you");
        context.addServlet(GreetingServlet.class, "/greetings/*");

        server.setHandler(context);
        server.start();
        server.join();
    }

    public static class HelloServlet extends HttpServlet
    {
        private final String hello;

        public HelloServlet(String greeting)
        {
            this.hello = greeting;
        }

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            resp.setContentType("text/plain");
            resp.getWriter().println("Hello " + this.hello);
        }
    }

    public static class GreetingServlet extends HttpServlet
    {
        private String greeting;

        @Override
        public void init() throws ServletException
        {
            this.greeting = (String) getServletContext().getAttribute("my.greeting");
        }

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
        {
            resp.setContentType("text/plain");
            resp.getWriter().println("Greetings to " + this.greeting);
        }
    }
}


回答2:

Singleton

You want to pass the same single instance to each servlet?

Use the Singleton pattern to create a single instance that is available globally.

The simplest fool-proof way to do that in Java is through an Enum. See Oracle Tutorial. Also see this article and the book Effective Java: Programming Language Guide, Second Edition (ISBN 978-0-321-35668-0, 2008) by Dr. Joshua Bloch.

So no need to pass an object. Each servlet can access the same single instance through the enum.

Per web app

If you want to do some work when your web app is first launching but before any servlet in that web app has handled any request, write a class that implements the ServletContextListener interface.

Mark your class with the @WebListener annotation to have your web container automatically instantiate and invoke.



回答3:

I had a similar situation but needed to share a singleton with a servlet deployed via war with hot (re)deploy in a Jetty container. The accepted answer wasn't quite what I needed in my case since the servlet has a lifecycle and context managed by a deployer.

I ended up with a brute-force approach, adding the object to the server context, which persists for the life of the container, and then fetching the object from within the servlet(s). This required loading the class of the object in a parent (system) classloader so that the war webapp doesn't load its own version of the class into its own classloader, which would cause a cast exception as explained here.

Embedded Jetty server code:

    Server server = new Server(8090);

    // Add all classes related to the object(s) you want to share here.
    WebAppContext.addSystemClasses(server, "my.package.MyFineClass", ...);

    // Handler config
    ContextHandlerCollection contexts = new ContextHandlerCollection();
    HandlerCollection handlers = new HandlerCollection();
    handlers.setHandlers(new Handler[] { contexts });
    server.setHandler(handlers);

    // Deployer config (hot deploy)
    DeploymentManager deployer = new DeploymentManager();
    DebugListener debug = new DebugListener(System.err,true,true,true);
    server.addBean(debug);
    deployer.addLifeCycleBinding(new DebugListenerBinding(debug));
    deployer.setContexts(contexts);
    deployer.setContextAttribute(
            "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
            ".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$");

    WebAppProvider webapp_provider = new WebAppProvider();
    webapp_provider.setMonitoredDirName("/.../webapps");
    webapp_provider.setScanInterval(1);
    webapp_provider.setExtractWars(true);
    webapp_provider.setConfigurationManager(new PropertiesConfigurationManager());

    deployer.addAppProvider(webapp_provider);
    server.addBean(deployer);

    // Other config...

    // Tuck any objects/data you want into the root server object.
    server.setAttribute("my.package.MyFineClass", myFineSingleton);

    server.start();
    server.join();

Example servlet:

public class MyFineServlet extends HttpServlet
{
    MyFineClass myFineSingleton;

    @Override
    public void init() throws ServletException
    {
        // Sneak access to the root server object (non-portable).
        // Not possible to cast this to `Server` because of classloader restrictions in Jetty.
        Object server = request.getAttribute("org.eclipse.jetty.server.Server");

        // Because we cannot cast to `Server`, use reflection to access the object we tucked away there.
        try {
            myFineSingleton = (MyFineClass) server.getClass().getMethod("getAttribute", String.class).invoke(server, "my.package.MyFineClass");
        } catch (Exception ex) {
            throw new ServletException("Unable to reflect MyFineClass instance via Jetty Server", ex);
        }
    }

    @Override
    protected void doGet( HttpServletRequest request,
            HttpServletResponse response ) throws ServletException, IOException
    {
        response.setContentType("text/html");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println("<h1>Hello from MyFineServlet</h1>");
        response.getWriter().println("Here's: " + myFineSingleton.toString());
    }
}

My build file for the servlet (sbt) placed the my.package.MyFineClass dependency into the "provided" scope so it wouldn't get packaged into the war as it will already be loaded into the Jetty server.