Implementing Spring MVC with CDI and Java EE 6 part 2

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.

In this article we’ll be implementing the following functions, the code for which is available for download :

  • 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

Implementing the Servlet

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 for each new piece of information needed. 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
}

Implementing the MVC Handler

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 controller method that best matches the request
		context.setMethod(matcher.findMatching(controllerInfo, context));
		
		if (context.getMethod() != null) {
			Object controller = locateController(context.getMethod().getControllerClass());
			context.setController(controller);
			// start executing the controller method
			executeControllerMethod(context);
			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 void executeControllerMethod(RequestContext context) {
       //executes the controller method on the controller
    }
}

We inject our instance of ControllerInfo which holds all the controller methods and the handleRequest() method is called from the servlet when receives a web request. In order to service the request it takes 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 object.

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.

Executing the Controller Method

We can just use reflection to execute the controller method instance for now. We assume that there are no params for now, that will come later.

private void executeControllerMethod(RequestContext context) {
	Method javaMethod = context.getMethod().getMethod();
	Object outcome = null;		
	try {
		outcome = javaMethod.invoke(context.getController());
	} catch (Exception e) {
		e.printStackTrace();
	}
	context.setOutcome(outcome);
}

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>View Person</title>
  </head>
  <body>
    View Person
  </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 returned from the controller method for the edit path with a POST request method.

You can download (mvcdi_part2.zip) the code for this as a maven project that can run on any Java EE 6 container and has been tested on both JBoss AS 7 and Glassfish 3.1.1.

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.

One thought on “Implementing Spring MVC with CDI and Java EE 6 part 2

  1. Ah, the dluboe-edge sword of convention-based APIs. It’s all good as long as the conventions are followed to the *letter*, and gets tricky when we stray from the convention. Things get extra frustrating when that *letter* wasn’t actually *written* anywhere! We should all take this experience to heart, and ensure that when we create conventions of our own (say for coworkers to follow on a project) we ensure that they’re both a) succinctly documented somewhere other than the original developer’s memory and b) verbose in their error messages when we inevitably miss a detail.