Over a million developers have joined DZone.

Implementing Spring MVC with CDI and Java EE 6 part 1

· Java Zone

What every Java engineer should know about microservices: Reactive Microservices Architecture.  Brought to you in partnership with Lightbend.

In this second article on implementing Spring MVC in Java EE 6 we’ll take the metadata we extracted in part one and use it to invoke request mapped controller methods in response to web requests and then direct the user to a web page based on the result of the method.

We’ll be implementing the following pieces of code :

  • Write a servlet that will dispatch the requests to our MVC handler class.
  • In the MVC Handler, we’ll take a web request and invoke the appropriate controller method
  • Take the result from the request mapped method and resolve it to a view which the user is forwarded to

Our servlet code takes incoming requests and delegates them to the injected MvcHandler instance.

@WebServlet(urlPatterns = "/demo/*")
public class DelegatingServlet extends HttpServlet {

    @Inject
    private MvcHandler mvcHandler;

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doHandleRequest(req, resp, RequestMethod.GET);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doHandleRequest(req, resp, RequestMethod.POST);
    }

    private void doHandleRequest(HttpServletRequest request, HttpServletResponse response,RequestMethod requestMethod) {
        mvcHandler.handleRequest(request.getPathInfo(),requestMethod,request,response,getServletContext());
    }
}

Our servlet uses the @WebServlet annotation to register the servlet for the /demo/* url path. It injects the instance of our MvcHandler class and uses it to handle the GET and POST requests. When we call the MVC handler, we have to pass in multiple objects that will be used by the handler. Looking ahead, we can see this is going to grow since we have controller methods, model values, outcomes and view names to pass around so we’ll create a new object called a RequestContext that will keep a hold of all these things and we can pass all those items around as a single object. It makes our method calls look nicer with fewer parameters and we don’t have to keep adding parameters to methods as we realize that the method needs a new piece of information.

Instead, we can add an attribute to the request context and make it available anywhere in the pipeline. Working with a single context means we can break the handler down to a specific set of steps with the product of each method (i.e. fetch model values) being held in the context and used in the next method. It also means we can later convert it to an interface and/or abstract some of the information available to provide different implementations (i.e portal version). For now, we just need a basic version with a servlet context, request, response objects. We’ll also need to store the controller, the controller method and the outcome from that method.

public class RequestContext {

    private final ServletContext servletContext;
    private final HttpServletRequest request;
    private final HttpServletResponse response;
    private final RequestMethod requestMethod;
    private Object controller;
    private ControllerMethod method;
    private Object outcome;

    public RequestContext(ServletContext servletContext,
            HttpServletRequest request, HttpServletResponse response,
            RequestMethod requestMethod) {
        this.servletContext = servletContext;
        this.request = request;
        this.response = response;
        this.requestMethod = requestMethod;
    }

    //getters and setters omitted
}

Going back to our Mvc Handler, the code in this class pulls everything together and orchestrates things as it receives calls from the servlet.

public class MvcHandler {

    @Inject
    private ControllerInfo controllerInfo;

    @Inject
    private ControllerMethodMatcher matcher;

    @Inject
    private BeanManager beanManager;    

    public void handleRequest(RequestContext context) {
        //find the method matching this request
        context.setMethod(matcher.findMatching(controllerInfo, context));
        if (context.getMethod() != null) {
            context.setController(locateController(context.getMethod().getControllerClass()));
            Object outcome = execute(controller,context.getMethod().getMethod(),null);
            context.setOutcome(outcome);
            handleResponse(context);
        } else {
            throw new RuntimeException("Unable to find method for " + context.getRequestMethod() + " request with url " + context.getPath());
        }
    }

    private void handleResponse(RequestContext context) {
        if (context.getOutcome() instanceof String) {
            String outcome = (String) context.getOutcome();
            String view = "/"+outcome+".jsp";
            context.forwardTo(view);
        }
    }

    private Object locateController(Class<?> controllerClass) {
          //returns a bean of type controllerClass from CDI
    }

    private Object execute(Object instance,Method javaMethod, Object[] params) {
       //executes the java method on this instance with these params
    }
}

We inject our instance of ControllerInfo which holds all the controller methods and we have defined our handleRequest() method that receives a web request from the servlet and performs the following steps :

  • Find a matching controller method for this request.
  • Locate the controller
  • Execute the controller method on that controller instance saving the outcome returned from the method.
  • Handle the correct response back to the user.

For now, we are just assuming the controller method returns a string that indicates the view name which we forward to. I added a method to handle forwarding on the request context.

Matching Methods

The ControllerMethodMatcher is an interface that can be used to locate a controller method in the list of controller methods that matches the incoming request info held in the request context

public interface ControllerMethodMatcher {
    ControllerMethod findMatching(ControllerInfo info,RequestContext context);
}

Our default implementation of this is really simple for now. We iterate through our list of controller methods and check that the request type matches the request method of the incoming request. If it does, then as long as the url path starts with the controller level prefix and ends with the method level suffix, then it is a match. Because we already sorted the controller methods in order of the larger expressions first, we know that the first match we come across is the best for now.

public class DefaultControllerMatcher implements ControllerMethodMatcher {

    public ControllerMethod findMatching(ControllerInfo info,RequestContext context) {
        for (ControllerMethod method : info.getControllerMethods()) {
            if (matches(method, context)) {
                return method;
            }
        }
        return null;
    }

    protected boolean matches(ControllerMethod methodToTest, RequestContext context) {
        String path = context.getPath();
        boolean result = methodToTest.matchesRequestMethod(context.getRequestMethod())
            && (methodToTest.getPrefix() == null || path.startsWith(methodToTest.getPrefix()))
            && (methodToTest.getSuffix() == null || path.endsWith(methodToTest.getSuffix()));
        return result;
    }
}

We can override this class, and re-implement the matches method later on and since we pass in the RequestContext we will have all sorts of information available to us to determine the best match. One example of the use of the request context is when we pass parameters in the URL (i.e. people\{id}\), which will need to be accounted for in the match and also possibly extracted and saved in the request context.

Executing the Controller Method

For now, we can just use reflection to execute the controller method instance. The params value passed in is currently fixes as null.

private Object execute(Object instance,Method javaMethod, Object[] params) {
    return javaMethod.invoke(instance,params);
}

Looking ahead, we will need to implement parameter injection so we can inject models, http requests and other objects as parameters to our controller methods. We will also have other types of methods that we will need to call with parameters injection. For example, ModelAttribute annotated methods will be called to retrieve model values and may have values injected into it. As it is though, we are keeping it nice and simple.

Seeing it in action

Finally we can put it all together so we can see it in action in a web application. For now our MVC code is in our web project to make integrating it easier. We can later extract the MVC code to a stand-alone module to use as a library in other projects. We’ve already set up a controller and our servlet, now we just need to create a couple of pages based on the string returned from the mapped methods. If we run the application now and go to a url such as http://localhost:8080/mvcdi/demo/person/list we will get an error message because listPeople.jsp doesn’t exist. We’ll create the following simple jsp pages so we can display something in the browser. Each page just consists of the boilerplate structure and some text that indicates which page we are on.

<html>
    <head>
        <title>List People</title>
    </head>
    <body>
        List People (this text changes per page)
    </body>
</html>

We do this for the following pages in the root of the web directory.

  • viewPerson.jsp
  • listPeople.jsp
  • updatedPerson.jsp
  • editPerson.jsp (see below for additional changes)

The one different page is the editPerson.jsp page where we want to put a form containing only a button so we can issue a POST request which maps to the method on the controller that handles the POST request from the editPerson.jsp page and navigates to the updatedPerson.jsp page in response.

<html>
    <head>
        <title>Editing Person</title>
    </head>
    <body>
        Editing Person
        <form method="post">
            Some Form Here<br/>
            <input type="submit" value="Update" />
        </form>
    </body>
</html>

If we go to http://localhost:8080/mvcdi/demo/person/edit and click the submit button, we get sent to the page that tells us we just updated someone because that is the page reference returned from the controller method for the edit path with a POST request method.

This wraps up the second installment of this series on implementing Spring MVC in Java EE 6 and CDI and covers the bulk of the request mapping so we can direct our web requests to controller methods and ultimately to specific pages. Next time we’ll look at providing model data to the pages.
In this second article on implementing Spring MVC in Java EE 6 we’ll take the metadata we extracted in part one and use it to invoke request mapped controller methods in response to web requests and then direct the user to a web page based on the result of the method. [more]

We’ll be implementing the following pieces of code :

  • Write a servlet that will dispatch the requests to our MVC handler class.
  • In the MVC Handler, we’ll take a web request and invoke the appropriate controller method
  • Take the result from the request mapped method and resolve it to a view which the user is forwarded to

Our servlet code takes incoming requests and delegates them to the injected MvcHandler instance.

@WebServlet(urlPatterns = "/demo/*")
public class DelegatingServlet extends HttpServlet {

    @Inject
    private MvcHandler mvcHandler;

    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doHandleRequest(req, resp, RequestMethod.GET);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doHandleRequest(req, resp, RequestMethod.POST);
    }

    private void doHandleRequest(HttpServletRequest request, HttpServletResponse response,RequestMethod requestMethod) {
        mvcHandler.handleRequest(request.getPathInfo(),requestMethod,request,response,getServletContext());
    }
}

Our servlet uses the @WebServlet annotation to register the servlet for the /demo/* url path. It injects the instance of our MvcHandler class and uses it to handle the GET and POST requests. When we call the MVC handler, we have to pass in multiple objects that will be used by the handler. Looking ahead, we can see this is going to grow since we have controller methods, model values, outcomes and view names to pass around so we’ll create a new object called a RequestContext that will keep a hold of all these things and we can pass all those items around as a single object.

It makes our method calls look nicer with fewer parameters and we don’t have to keep adding parameters to methods as we realize that the method needs a new piece of information. Instead, we can add an attribute to the request context and make it available anywhere in the pipeline. Working with a single context means we can break the handler down to a specific set of steps with the product of each method (i.e. fetch model values) being held in the context and used in the next method. It also means we can convert it to an interface and/or abstract some of the information available to provide different implementations (i.e portal version). For now, we just need a basic version with a servlet context, request, response objects. We’ll also need to store the controller, the controller method and the outcome from that method.

public class RequestContext {

    private final ServletContext servletContext;
    private final HttpServletRequest request;
    private final HttpServletResponse response;
    private final RequestMethod requestMethod;
    private Object controller;
    private ControllerMethod method;
    private Object outcome;

    public RequestContext(ServletContext servletContext,
            HttpServletRequest request, HttpServletResponse response,
            RequestMethod requestMethod) {
        this.servletContext = servletContext;
        this.request = request;
        this.response = response;
        this.requestMethod = requestMethod;
    }

    //getters and setters omitted
}

Going back to our Mvc Handler, the code in this class pulls everything together and orchestrates things as it receives calls from the servlet.

public class MvcHandler {

    @Inject
    private ControllerInfo controllerInfo;

    @Inject
    private ControllerMethodMatcher matcher;

    @Inject
    private BeanManager beanManager;    

    public void handleRequest(RequestContext context) {
        //find the method matching this request
        context.setMethod(matcher.findMatching(controllerInfo, context));
        if (context.getMethod() != null) {
            context.setController(locateController(context.getMethod().getControllerClass()));
            Object outcome = execute(controller,context.getMethod().getMethod(),null);
            context.setOutcome(outcome);
            handleResponse(context);
        } else {
            throw new RuntimeException("Unable to find method for " + context.getRequestMethod() + " request with url " + context.getPath());
        }
    }

    private void handleResponse(RequestContext context) {
        if (context.getOutcome() instanceof String) {
            String outcome = (String) context.getOutcome();
            String view = "/"+outcome+".jsp";
            context.forwardTo(view);
        }
    }

    private Object locateController(Class<?> controllerClass) {
          //returns a bean of type controllerClass from CDI
    }

    private Object execute(Object instance,Method javaMethod, Object[] params) {
       //executes the java method on this instance with these params
    }
}

We inject our instance of ControllerInfo which holds all the controller methods and we have defined our handleRequest() method that receives a web request from the servlet and performs the following steps :

  • Find a matching controller method for this request.
  • Locate the controller
  • Execute the controller method on that controller instance saving the outcome returned from the method.
  • Handle the correct response back to the user.

For now, we are just assuming the controller method returns a string that indicates the view name which we forward to. I added a method to handle forwarding on the request context.

Matching Methods

The ControllerMethodMatcher is an interface that can be used to locate a controller method in the list of controller methods that matches the incoming request info held in the request context

public interface ControllerMethodMatcher {
    ControllerMethod findMatching(ControllerInfo info,RequestContext context);
}

Our default implementation of this is really simple for now. We iterate through our list of controller methods and check that the request type matches the request method of the incoming request. If it does, then as long as the url path starts with the controller level prefix and ends with the method level suffix, then it is a match. Because we already sorted the controller methods in order of the larger expressions first, we know that the first match we come across is the best for now.

public class DefaultControllerMatcher implements ControllerMethodMatcher {

    public ControllerMethod findMatching(ControllerInfo info,RequestContext context) {
        for (ControllerMethod method : info.getControllerMethods()) {
            if (matches(method, context)) {
                return method;
            }
        }
        return null;
    }

    protected boolean matches(ControllerMethod methodToTest, RequestContext context) {
        String path = context.getPath();
        boolean result = methodToTest.matchesRequestMethod(context.getRequestMethod())
            && (methodToTest.getPrefix() == null || path.startsWith(methodToTest.getPrefix()))
            && (methodToTest.getSuffix() == null || path.endsWith(methodToTest.getSuffix()));
        return result;
    }
}

We can override this class, and re-implement the matches method later on and since we pass in the RequestContext we will have all sorts of information available to us to determine the best match. One example of the use of the request context is when we pass parameters in the URL (i.e. people\{id}\), which will need to be accounted for in the match and also possibly extracted and saved in the request context.

Executing the Controller Method

For now, we can just use reflection to execute the controller method instance. The params value passed in is currently fixes as null.

private Object execute(Object instance,Method javaMethod, Object[] params) {
    return javaMethod.invoke(instance,params);
}

Looking ahead, we will need to implement parameter injection so we can inject models, http requests and other objects as parameters to our controller methods. We will also have other types of methods that we will need to call with parameters injection. For example, ModelAttribute annotated methods will be called to retrieve model values and may have values injected into it. As it is though, we are keeping it nice and simple.

Seeing it in action

Finally we can put it all together so we can see it in action in a web application. For now our MVC code is in our web project to make integrating it easier. We can later extract the MVC code to a stand-alone module to use as a library in other projects. We’ve already set up a controller and our servlet, now we just need to create a couple of pages based on the string returned from the mapped methods. If we run the application now and go to a url such as http://localhost:8080/mvcdi/demo/person/list we will get an error message because listPeople.jsp doesn’t exist. We’ll create the following simple jsp pages so we can display something in the browser. Each page just consists of the boilerplate structure and some text that indicates which page we are on.

<html>
    <head>
        <title>List People</title>
    </head>
    <body>
        List People (this text changes per page)
    </body>
</html>

We do this for the following pages in the root of the web directory.

  • viewPerson.jsp
  • listPeople.jsp
  • updatedPerson.jsp
  • editPerson.jsp (see below for additional changes)

The one different page is the editPerson.jsp page where we want to put a form containing only a button so we can issue a POST request which maps to the method on the controller that handles the POST request from the editPerson.jsp page and navigates to the updatedPerson.jsp page in response.

<html>
    <head>
        <title>Editing Person</title>
    </head>
    <body>
        Editing Person
        <form method="post">
            Some Form Here<br/>
            <input type="submit" value="Update" />
        </form>
    </body>
</html>

If we go to http://localhost:8080/mvcdi/demo/person/edit and click the submit button, we get sent to the page that tells us we just updated someone because that is the page reference returned from the controller method for the edit path with a POST request method.

This wraps up the second installment of this series on implementing Spring MVC in Java EE 6 and CDI and covers the bulk of the request mapping so we can direct our web requests to controller methods and ultimately to specific pages. Next time we’ll look at providing model data to the pages.

 

From http://www.andygibson.net/blog/article/implementing-spring-mvc-with-cdi-and-java-ee-6/

Microservices for Java, explained. Revitalize your legacy systems (and your career) with Reactive Microservices Architecture, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}