19
votes

I have an application with an embedded jetty server which I'm starting up like this (placed in main() and launched with eclipse):

Server server = new Server(port);

WebAppContext context = new WebAppContext();
context.setResourceBase("web/");
context.setDescriptor("web/WEB-INF/web.xml");
context.setConfigurations(new Configuration[]{
            new AnnotationConfiguration(), new WebXmlConfiguration(),
            new WebInfConfiguration(), new TagLibConfiguration(),
            new PlusConfiguration(), new MetaInfConfiguration(),
            new FragmentConfiguration(), new EnvConfiguration()});

context.setContextPath("/");
context.setParentLoaderPriority(true);
server.setHandler(context);
server.start();
server.join();

My web.xml looks like this (empty for now, I'm not sure if I can remove it completely):

<web-app 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    metadata-complete="false"
    version="3.0">
</web-app>

And I have a simple class set up like this:

import java.io.IOException;

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

@WebServlet(urlPatterns={"/test"})
public class TestServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException  {
        request.getRequestDispatcher("/WEB-INF/html/index.html").forward(request,response);
    }
}

My application works fine when I use traditional servlet mappings in web.xml. But when I remove the web.xml mappings and use annotations, I only get 404s. It doesn't look like it's scanning for annotations at all. The console looks like this:

2012-08-01 17:40:37.021:INFO:oejs.Server:jetty-8.1.5.v20120716
2012-08-01 17:40:37.227:INFO:oejpw.PlusConfiguration:No Transaction manager found - if your webapp requires one, please configure one.
2012-08-01 17:40:37.294:INFO:oejsh.ContextHandler:started o.e.j.w.WebAppContext{/,file:/Users/me/project/web/}
2012-08-01 17:40:37.547:INFO:oejsh.ContextHandler:started o.e.j.w.WebAppContext{/,file:/Users/me/project/web/}
2012-08-01 17:40:37.547:INFO:oejsh.ContextHandler:started o.e.j.w.WebAppContext{/,file:/Users/me/project/web/}
2012-08-01 17:40:37.547:INFO:oejsh.ContextHandler:started o.e.j.w.WebAppContext{/,file:/Users/me/project/web/}
2012-08-01 17:40:37.641:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:8080

Some things that I have checked already from my research:

  • servlet-3.0 jar is in the classpath
  • metadata-complete set to false in web.xml
  • I made sure to include AnnotationConfiguration in web app context

I've run out of ideas and am about to just revert back to the old web.xml, but it's killing me why I can't get this to work.

7
Did Joakim Erdfelt's answer actually work?ManRow
Not for me (Jetty 9.0). Same as @user1569803, and all dependencies in place. I'm investigating alternate routes.LSerni
Unfortunately I no longer have access to the project which had this issue, so I can't verify the answer for my situation. If some old co-workers try the solution and have success, I'll mark the answer for them.Ryan Silva

7 Answers

7
votes

Update: June 2015

The example project has been updated for Jetty 9 and Servlet 3.1

See: https://github.com/jetty-project/embedded-servlet-3.1

Original Answer:

From your description, and a sample project I whipped up using your code, you are doing everything correctly.

Sample project: https://github.com/jetty-project/embedded-servlet-3.0

In order for this to work, you'll want the following (only mentioning this as your question didn't include this detail)

  • JDK 1.6+
  • jetty-webapps jar (+ dependencies) from Jetty 8.1.x (or newer)
  • jetty-annotations jar (+ dependencies) from Jetty 8.1.x (or newer)

Just from these limited requirements you'll see the following list of dependencies present.

$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-webapp 1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ sample-webapp ---
[INFO] com.company.sample:sample-webapp:war:1-SNAPSHOT
[INFO] +- org.eclipse.jetty.orbit:javax.servlet:jar:3.0.0.v201112011016:provided
[INFO] +- org.eclipse.jetty:jetty-webapp:jar:8.1.5-SNAPSHOT:test
[INFO] |  +- org.eclipse.jetty:jetty-xml:jar:8.1.5-SNAPSHOT:test
[INFO] |  |  \- org.eclipse.jetty:jetty-util:jar:8.1.5-SNAPSHOT:test
[INFO] |  \- org.eclipse.jetty:jetty-servlet:jar:8.1.5-SNAPSHOT:test
[INFO] |     \- org.eclipse.jetty:jetty-security:jar:8.1.5-SNAPSHOT:test
[INFO] |        \- org.eclipse.jetty:jetty-server:jar:8.1.5-SNAPSHOT:test
[INFO] |           +- org.eclipse.jetty:jetty-continuation:jar:8.1.5-SNAPSHOT:test
[INFO] |           \- org.eclipse.jetty:jetty-http:jar:8.1.5-SNAPSHOT:test
[INFO] |              \- org.eclipse.jetty:jetty-io:jar:8.1.5-SNAPSHOT:test
[INFO] \- org.eclipse.jetty:jetty-annotations:jar:8.1.5-SNAPSHOT:test
[INFO]    +- org.eclipse.jetty:jetty-plus:jar:8.1.5-SNAPSHOT:test
[INFO]    |  +- org.eclipse.jetty.orbit:javax.transaction:jar:1.1.1.v201105210645:test
[INFO]    |  \- org.eclipse.jetty:jetty-jndi:jar:8.1.5-SNAPSHOT:test
[INFO]    |     \- org.eclipse.jetty.orbit:javax.mail.glassfish:jar:1.4.1.v201005082020:test
[INFO]    |        \- org.eclipse.jetty.orbit:javax.activation:jar:1.1.0.v201105071233:test
[INFO]    +- org.eclipse.jetty.orbit:javax.annotation:jar:1.1.0.v201108011116:test
[INFO]    \- org.eclipse.jetty.orbit:org.objectweb.asm:jar:3.1.0.v200803061910:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.771s
[INFO] Finished at: Fri Aug 10 18:17:46 MST 2012
[INFO] Final Memory: 6M/180M
[INFO] ------------------------------------------------------------------------

It is quite likely that you are just missing a dependency or JDK requirement.

6
votes

AnntationConfiguration class scans annotations via its scanForAnnotations(WebAppContext) method. In the method AnnotationConfiguration class scans following path.

  • container jars
  • WEB-INF/classes
  • WEB-INF/libs

So if you want your servlet classes in your production code(i.e. the sources in the directory src/main/java) to be scanned, add your production code into the WebAppContext's metadata as WEB-INF/classes.

Try the code bellow, for adding your code into WebAppContext's metadata.

URL classes = getClass()
        .getProtectionDomain()
        .getCodeSource()
        .getLocation();

WebAppContext context = new WebAppContext();
context.getMetaData()
    .setWebInfClassesDirs(
        Arrays.asList(Resource.newResource(classes)));
5
votes

I had this same problem, but after many reads I got the solution! IMPORTANT: remember to have in your build path the following jars:

jetty-all-9.0.6.v20130930.jar

jetty-annotations-9.0.6.v20130930.jar

org.objectweb.asm-3.1.0.v200803061910.jar

javax.servlet-api-3.0.1.jar

jetty-plus-9.0.6.v20130930.jar

public class Main {

   public static void main(String[] args) throws Exception {

       //Create the server
       Server server = new Server(8080);

       ClassList clist = ClassList.setServerDefault(server);

       //clist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration", "org.eclipse.jetty.plus.webapp.EnvConfiguration",    "org.eclipse.jetty.plus.webapp.PlusConfiguration");
       clist.addBefore(JettyWebXmlConfiguration.class.getName(), AnnotationConfiguration.class.getName());

       //Here is the trick to scan current classpath!
       webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", 
            ".*/build/classes/");

       webapp.setContextPath("/");
       webapp.setResourceBase("./WebContent");


       server.setHandler(webapp);

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

   }

}
1
votes

Based on the previous example provided by Joakim, I've uploaded a modified version that supports annotations in an embedded jetty without packaging the project into a war file. This project is ready to deploy to Heroku just running:

$java -cp target/classes:"target/dependency/*" com.example.Launcher

If you are interested in the details, the important lines are these ones from com.example.Launcher:

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

// Important! make sure Jetty scans all classes under ./classes looking for annotations. 
//'Classes' directory is generated running 'mvn package'
context.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",".*/classes/.*");

You can change accordingly the JarPattern depending on the classes you want to scan for annotations.

Here you have the full example: https://github.com/pablormier/embedded-jetty-annotations-example

1
votes

I have come up with a different approach rather specific to Jetty & Spring. Instead of letting Jetty scan for classes, I manually called initializer classes in startContext() method of a custom Jetty ServletContextHandler. Code is as follows:

public class CustomServletContextHandler extends ServletContextHandler {

   @Override
   protected void startContext() throws Exception {

       SpringServletContainerInitializer initer = new SpringServletContainerInitializer();

        HashSet<Class<?>> classes = new HashSet<>();
        // Add annotated classes here such as
        //classes.add(SpringSecurityInitializer.class);
        //classes.add(SpringWebInitilializer.class);

        try {
            initer.onStartup(classes, this.getServletContext());
        }catch(Exception e)
        {
            e.printStackTrace();
        }

        super.startContext();
    }
}

And then when creating context, use custom handler:

 ServletContextHandler ctx = new CustomServletContextHandler();
 ctx.setContextPath("/");
 ...
0
votes

In our case these lines helped in Jetty startup code:

    ClassList cl = Configuration.ClassList.setServerDefault(server);
    cl.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration");
0
votes

You can

  1. provide EmptyResource.INSTANCE if you do not need static resources

  2. add AnnotationConfiguration and override scanForAnnotations method, adding classpath resource to WebAppContext Metadata, possibly with path to root package for a scan

    WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        webapp.setBaseResource(EmptyResource.INSTANCE);
        webapp.setConfigurations(new Configuration[]{
                new AnnotationConfiguration(){
                    @Override
                    protected void scanForAnnotations(WebAppContext context) throws Exception {
                        Resource classPathResource =    Resource.newResource(EmbeddedServerApp.class.getResource("/my/learn/jetty/servlet").toURI());
                        context.getMetaData().addContainerResource(classPathResource);
                        super.scanForAnnotations(context);
                    }
                },
        });
        server.setHandler(webapp);
        server.start();