7

Context

I work on a web-app (using Play Framework) and I'm trying to migrate to traditional Servlet model with Spring MVC. I'd like to run in an embedded Jetty container alongs an already existing one (netty).

Problem

I'm trying to re-use a created Spring context (that contains all application beans, including newly-added Spring MVC controllers), but the request mappings aren't picked up.

I debugged Spring's Dispatcher Servlet and there were no mappings registered indeed (so it could handle no path).

Attempted solution

Here's the manual Jetty setup code:

@RequiredArgsConstructor
public class EmbeddedJetty {

    private final int port;
    private final AnnotationConfigWebApplicationContext existingContext;

    @SneakyThrows
    public void start() {
        Assert.notNull(existingContext.getBean(UserController.class));

        val server = new Server(port);
        ServletContextHandler handler = new ServletContextHandler();
        ServletHolder servlet = new ServletHolder(new DispatcherServlet(existingContext));
        handler.addServlet(servlet, "/");
        handler.addEventListener(new ContextLoaderListener(existingContext));
        server.setHandler(handler);

        server.start();
        log.info("Server started at port {}", port);
    }

}

And here's the controller being ignored:

@Controller
public class UserController {

    @GetMapping("/users/{userId}")
    public ResponseEntity<?> getUser(@PathVariable("userId") long userId) {
        return ResponseEntity.ok("I work");
    }

}

Question

What do I needed to do to make my embedded jetty setup pick up the existing controller beans and serve the mappings?

Xorty
  • 18,367
  • 27
  • 104
  • 155
  • I am actually at a loss here, I feed directly the webApplicationContext to the call starting my jetty, and the jetty starts with the mapping and everything correctly. (see my edit) My last question would be what are the version of the components you use? – Adonis Jul 21 '17 at 12:02
  • Do you happen to have that code on Github or somewhere? I'm on Spring 4.3.6 and Jetty 9.4.6 – Xorty Jul 21 '17 at 15:47
  • Actually it is on my work computer, so I had to recreate it from almost scratch (plus what's here), let me know what differs from yours: https://github.com/asettouf/SpringMVCTemplate – Adonis Jul 21 '17 at 16:55
  • So did you find a way to solve your issue? The only thing I can think of is that you start the Jetty with a not fully initialized SpringContext, and so we would need more details regarding this part to help you – Adonis Jul 25 '17 at 17:17
  • Sorry it took me so long, I posted the answer. – Xorty Aug 09 '17 at 11:48

4 Answers4

2

I believe you are missing the MVC Java Config that handles request to @RequestMapping inside your Controller.

So basically what you would need to add is a WebMVC config class like:

package my.spring.config;
//necessary imported packages avoided for shortening the example
@EnableWebMvc 
@Configuration
@ComponentScan({ "my.jetty.test" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {

}

Then you would need to indicate to your AnnotationConfigWebApplicationContextwhere the config is located, add to your startmethod this:

webAppContext.setConfigLocation("my.spring.config");

And voila, the below (very simple) Controller serves request on my localhost:

package my.jetty.test;
//necessary imported packages avoided for shortening the example
@Controller
public class HelloController {

    @GetMapping(value = "/")
    @ResponseBody
    public String printWelcome(HttpServletRequest request) {
        return "hello";
    }

    @GetMapping(value = "/hello/{name:.+}")
    @ResponseBody
    public String hello(@PathVariable("name") String name) {
        return "hello " + name;
    }
}

If needed, I can give the full example. Several links that helped me:

Edit: The repo with my code working

Adonis
  • 4,670
  • 3
  • 37
  • 57
  • Thank you, unfortunately I am getting the very same error. This works well if you're starting an app using the `start` method above. My problem is that the Spring Context already exists and the controller bean has been instantiated before I try to start Jetty. I am looking for a way to plug Jetty in to an existing Spring app, not the opposite. – Xorty Jul 18 '17 at 10:00
  • @Xorty I see, there is still one thing I'd like to understand, as it is not obvious for me, why can't you inject the SpringConfig location to your `existingContext` when you `start` the embedded Jetty? Because from my understanding that's the component which handle the processing of requests via the `@XMapping` annotation. – Adonis Jul 18 '17 at 10:49
  • If you mean setting the config location - I can do that! Still getting the same error though. – Xorty Jul 18 '17 at 11:02
  • The app has a different "main" class that creates Spring context with Netty server. Here I am trying to bootstrap Jetty server after Spring/Netty have been already created. – Xorty Jul 18 '17 at 11:02
  • @Xorty Ok I'll try to reproduce your environment, so that I can check more thoroughly. Any hint regarding your config would be appreciated – Adonis Jul 18 '17 at 11:14
  • I really appreciate you diving into this. Imagine the environment as follows: 1. main method that creates, refreshes, and starts a Spring context 1.1 UserController is part of the context created at 1) 2. Start Jetty server and try to route requests to the existing Controller created in the previously defined context – Xorty Jul 18 '17 at 12:38
  • @Xorty What I just did: deploy a MVC Spring application on a wildfly server (that would be the "main"), inside a controller, make a call to start an embedded Jetty, and it worked without a single error. So I'm really wondering, what did I got wrong from your config... Wait, from your Jetty, do you want to serve requests to the existing controller (i.e., the one inside the wildfly server in my example)? – Adonis Jul 20 '17 at 10:59
  • Yes, precisely that. – Xorty Jul 20 '17 at 12:32
1

Static Structure

If you are going to migrate to servlet model, you may would like to get familiar with the normal structure:

    .
    ├── pom.xml
    ├── README
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   ├── resources
    │   │   │   ├── spring
    │   │   └── webapp
    │   │       └── WEB-INF
    │   │           └── web.xml
    │   └── test
    │       ├── java
    │       └── resources

The web.xml is the core descriptor that j2ee server used to deploy the app. There exists some important components in a app which is defined in web.xml:

  • filter
  • servlet
  • listener

Sever Start

When server starts, it will setup listener for monitoring the lifecycle of whole app or a request; it will setup filter to filter requests; it will setup servlet to handle request.

Spring Way

Spring is a very light-weight way to integrate many convenient method/utility into our applications. It is light weight because it is attached into our projects only by two things:

  • Define spring's listener in web.xml to initialize
  • Direct all requests to spring (for Spring MVC)

Suggestions

So, back to our problem.

  • Your jetty server start with a ServletContextHandler which is just related to mapping, but not listener (i.e. no spring config will be init). You should start with WebAppContext;
  • You at least should add web.xml to pick up the existing controller beans and serve the mappings;

Ref

Tony
  • 5,972
  • 2
  • 39
  • 58
1

This ended up being a bit of pain and re-work, but the final solution was making sure that existingContext is NOT started and refreshed prior to starting the DispatcherServlet.

Xorty
  • 18,367
  • 27
  • 104
  • 155
-1

Try this in your Web configuration file

@ComponentScan(basePackages = {"com.our.controller"})
SkyWalker
  • 28,384
  • 14
  • 74
  • 132
AbdusSalam
  • 485
  • 5
  • 13
  • As you can see the `UserController` bean already exists, see Assert.notNull at the very first line – Xorty Jul 19 '17 at 08:31