Blog Zenika

#CodeTheWorld

Java

Building a web framework on top of Spring MVC

The Spring Framework ships with its own web framework : Spring MVC. One can use Spring MVC as a full-blown web framework, to build web applications but Spring MVC can act also as a foundation for higher level and more focused application frameworks. For example, Spring Web Flow builds on top Spring MVC, as well as the Grails framework. This post aims at showing how to create a simple web framework with Spring MVC, with Groovy as the view technology. You’ll find it especially useful if you’re a Spring MVC developer and want to learn more about the framework internals or if you’re curious about how Spring Web Flow and Grails are implemented. But remember that this is not an article about using the annotation-based version of Spring MVC, it’s more about how it is implemented !

Spring MVC : the big picture

Before implementing our web framework, let’s see how Spring MVC works to see on which of its extensions we’ll have to plug on. Spring MVC is designed around the Model-View-Controller 2 pattern, so it means it provides a front controller, the DispatcherServlet which receives the incoming HTTP requests, does some processing before delegating the request to an application component, called the handler, and then renders the view. The handler is the Java class you write at the end, to deal with the HTTP parameters and call the appropriate business services, before choosing to forward to the view. The handler is also called the controller sometimes.
What do we mean when we say that “the DispatcherServlet does some processing” ? Mainly, dealing with the plumbing, by converting the HTTP request into something easier to deal with for the application controller, so that it does not depend on the Servlet API for example.
In Spring MVC, the DispatcherServlet delegates a lot to infrastructure components. This prevents the DispatcherServlet‘s code to be bloated and also provides a much better extensibility: these infrastructure components can be swapped out, using different implementations, to change the way controllers are called, views are rendered and so on. The following figure shows the main infrastructure components :
Spring MVC internals
To implement our own web framework, we’ll have to develop a specific implementation for each of them. Here is a description of all these components :

HandlerMapping: chooses the appropriate handler that should process the request. It can base its decision on the URL but also on HTTP parameters or headers (everything in the HTTP request actually)

HandlerAdapter: handles the delegation of the request to the handler. The HandlerAdapter is about calling the right method on the handler, with the parameters the handler expects.

ViewResolver: creates the View object depending on what the handler returned (usually, the logical name of the view, as a String)

View: renders the view (e.g. outputs generated HTML code to the HttpServletResponse)

The HandlerMapping, HandlerAdapter and the ViewResolver are infrastructure beans: they lie in the DispatcherSerlet‘s application context. As soon as you declare a bean of one these types, the DispatcherServlet picks it up and will use it. Note any infrastructure bean declared will replace the default ones. Note also that the DispatcherServlet maintains a list for each type of infrastructure beans and use them following a chain of responsability pattern, as the DispatcherServlet iterates over the list and stops as soon as an infrastructure bean handled the delegation. This allows to making several programming models cohabiting in the same DispatcherServlet for example.

The My MVC framework

We are going to create a very simple web framework, the My MVC framework, on top of Spring MVC. Controllers will need to implement the following interface :

  1. package com.zenika.mymvc;
  2. import java.util.Map;
  3. public interface MyMvcController {
  4. String handle(Map<String, String> parameters,Map<String,Object> model);
  5. String getPath();
  6. }

The parameters parameter in the handle method is a mapping of the incoming HTTP parameters and the model parameter is where the application developper will store data (domain objects for example) to push them to the view. The getPath method is about mapping the controller on an URL, relative to the owning DispatcherServlet. Here is an example of My MVC controller :

  1. public class ContactMyMvcController implements MyMvcController {
  2. @Autowired
  3. private ContactService contactService;
  4. @Override
  5. public String getPath() {
  6. return “/myfirst”;
  7. }
  8. @Override
  9. public String handle(Map<String, String> parameters, Map<String, Object> model) {
  10. model.put(“contact”,contactService.get(parameters.get(“id”)));
  11. return “contact”;
  12. }
  13. }

Such a controller will be declared as a Spring bean in the DispatcherServlet application context, either explictly (XML) or automatically (component scanning), it doesn’t matter. Let’s start the creation of our web framework by implementing the HandlerMapping.

Implementing the HandlerMapping

The HandlerMapping is about mapping our controllers to URL. It means the HandlerMapping must return a controller to the DispatcherServlet depending on the incoming request. The code of our MyMvcHandlerMapping uses some hooks of the Spring Framework but it’s quite straightforward :

  1. package com.zenika.mymvc;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import javax.servlet.http.HttpServletRequest;
  5. import org.springframework.beans.BeansException;
  6. import org.springframework.context.ApplicationContext;
  7. import org.springframework.context.ApplicationContextAware;
  8. import org.springframework.web.servlet.HandlerExecutionChain;
  9. import org.springframework.web.servlet.HandlerMapping;
  10. public class MyMvcHandlerMapping implements HandlerMapping,ApplicationContextAware {
  11. private Map<String, MyMvcController> _urlToControllers = new HashMap<String, MyMvcController>();
  12. @Override
  13. public HandlerExecutionChain getHandler(HttpServletRequest request)
  14. throws Exception {
  15. MyMvcController controller = _urlToControllers.get(request.getPathInfo());
  16. return controller == null ? null : new HandlerExecutionChain(controller);
  17. }
  18. @Override
  19. public void setApplicationContext(ApplicationContext ctx)
  20. throws BeansException {
  21. Map<String, MyMvcController> controllers = ctx.getBeansOfType(MyMvcController.class);
  22. for(MyMvcController controller : controllers.values()) {
  23. _urlToControllers.put(controller.getPath(),controller);
  24. }
  25. }
  26. }

The MyMvcHandlerMapping will also be a Spring bean, so it can get a reference to the Spring application context by implementing the ApplicationContextAware interface. From the ApplicationContext, it can lookup all the declared MyMvcControllers and store them in a Map, using a mapping like [URL] => [MyMvcController]. When asked by the DispatcherServlet, it can take a look at the incoming request and try to find a controller which is mapped on the URL. The getHandler must return a HandlerExecutionChain, which is mainly about decorating the controller by interceptors (we won’t use this in My MVC).
That’s it for the HandlerMapping, let’s move on to the HandlerAdapter.

Implementing the HandlerAdapter

The MyMvcHandlerAdapter is there to convert the HTTP parameters into the Map that a MyMvcController expects, create an empty model and call the handle method on the MyMvcController:

  1. package com.zenika.mymvc;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import org.springframework.util.StringUtils;
  7. import org.springframework.web.servlet.HandlerAdapter;
  8. import org.springframework.web.servlet.ModelAndView;
  9. public class MyMvcHandlerAdapter implements HandlerAdapter {
  10. @Override
  11. public ModelAndView handle(HttpServletRequest request,
  12. HttpServletResponse response, Object handler) throws Exception {
  13. MyMvcController controller = (MyMvcController) handler;
  14. Map<String, String> parameters = new HashMap<String, String>();
  15. for(Object key : request.getParameterMap().keySet()) {
  16. parameters.put(key.toString(), extractParameter(request.getParameterValues(key.toString())));
  17. }
  18. Map<String, Object> model = new HashMap<String, Object>();
  19. String viewName = controller.handle(parameters, model);
  20. return new ModelAndView(viewName, model);
  21. }
  22. @Override
  23. public boolean supports(Object handler) {
  24. return handler instanceof MyMvcController;
  25. }
  26. @Override
  27. public long getLastModified(HttpServletRequest request, Object handler) {
  28. return1;
  29. }
  30. private String extractParameter(String [] values) {
  31. if(values == null || values.length == 0) {
  32. return “”;
  33. } else if(values.length == 1) {
  34. return values[0];
  35. } else {
  36. return StringUtils.arrayToCommaDelimitedString(values);
  37. }
  38. }
  39. }

Done, our MyMvcControllers will be correctly called, with the parameters they are expected. They will do their own business and return the logical name of view that will handled by our ViewResolver.

Implementing the ViewResolver and the Groovy-based View

A ViewResolver is about creating the correct view. As stated previously, we are going to use Groovy as our view technology. Why? Let’s say because it’s more fancy than using a template engine like Freemarker (especially because Spring MVC already provides such a ViewResolver). So here is the code of our Groovy-based View implementation :

  1. package com.zenika.mymvc;
  2. import groovy.lang.GroovyShell;
  3. import java.util.Map;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import org.springframework.core.io.Resource;
  7. import org.springframework.web.servlet.View;
  8. public class GroovyView implements View {
  9. private Resource resource;
  10. public GroovyView(Resource resource) {
  11. super();
  12. this.resource = resource;
  13. }
  14. @Override
  15. public String getContentType() {
  16. return “text/html”;
  17. }
  18. @Override
  19. public void render(Map<String, ?> model, HttpServletRequest request,
  20. HttpServletResponse response) throws Exception {
  21. GroovyShell shell = new GroovyShell();
  22. for(Map.Entry<String, ?> entry : model.entrySet()) {
  23. shell.setVariable(entry.getKey(), entry.getValue());
  24. }
  25. shell.setVariable(“model”, model);
  26. Object result = shell.evaluate(resource.getInputStream());
  27. response.setContentType(getContentType());
  28. response.getWriter().append(result.toString());
  29. }
  30. }

Our GroovyView uses the Groovy API to execute a Groovy script, so it means our My MVC views are Groovy scripts, meaning they should return the HTML code they want to send to the user.
The GroovyMyMvcViewResolver is about locating these Groovy scripts and creating the corresponding GroovyView objects. We can add some location logic, to be able the store our Groovy scripts in a dedicated directory :

  1. package com.zenika.mymvc;
  2. import java.util.Locale;
  3. import org.springframework.context.ResourceLoaderAware;
  4. import org.springframework.core.io.Resource;
  5. import org.springframework.core.io.ResourceLoader;
  6. import org.springframework.web.servlet.View;
  7. import org.springframework.web.servlet.ViewResolver;
  8. public class GroovyMyMvcViewResolver implements ViewResolver,ResourceLoaderAware {
  9. private String prefix,suffix;
  10. private ResourceLoader resourceLoader;
  11. @Override
  12. public View resolveViewName(String viewName, Locale locale)
  13. throws Exception {
  14. Resource resource = resourceLoader.getResource(prefix+viewName+suffix);
  15. if(resource.exists()) {
  16. return new GroovyView(resource);
  17. } else {
  18. return null;
  19. }
  20. }
  21. @Override
  22. public void setResourceLoader(ResourceLoader rl) {
  23. this.resourceLoader = rl;
  24. }
  25. public void setPrefix(String prefix) {
  26. this.prefix = prefix;
  27. }
  28. public void setSuffix(String suffix) {
  29. this.suffix = suffix;
  30. }
  31. }

The prefix and suffix helps configuring this location logic and the resource loading is done using Spring’s resource abstraction.
That’s it, we have all the pieces of the My MVC web framework! Let’s see how to configure them.

Configuring the My MVC infrastructure with Spring

The configuration is straightforward as it consists in declaring the HandlerMapping, HandlerAdapter and ViewResolver in the DispatcherServlet application context :

  1. <?xml version=“1.0” encoding=“UTF-8”?>
  2. <bean class=“com.zenika.mymvc.MyMvcHandlerMapping” />
  3. <bean class=“com.zenika.mymvc.MyMvcHandlerAdapter” />
  4. <bean class=“com.zenika.mymvc.GroovyMyMvcViewResolver”>
  5. <property name=“prefix” value=“/WEB-INF/views/” />
  6. <property name=“suffix” value=“.gy” />
  7. </bean>
  8. </beans>

The DispatcherServlet is configured the usual way, in the web.xml file :

  1. <?xml version=“1.0” encoding=“UTF-8”?>
  2. id=“mymvc” version=“2.5”>
  3. <display-name>My MVC</display-name>
  4. <servlet>
  5. <servlet-name>app</servlet-name>
  6. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  7. <load-on-startup>1</load-on-startup>
  8. </servlet>
  9. <servlet-mapping>
  10. <servlet-name>app</servlet-name>
  11. <url-pattern>/app/*</url-pattern>
  12. </servlet-mapping>
  13. </web-app>

We are now ready to implement our very first screen with My MVC.

Implementing a MyMVC controller and view

Let’s stick to something simple and echo the HTTP parameters on our My MVC screen. This is the implementation of the controller, which just forward all the incoming HTTP parameters into the model Map:

  1. package com.zenika.web;
  2. import java.util.Map;
  3. import com.zenika.mymvc.MyMvcController;
  4. public class MyFirstMyMvcController implements MyMvcController {
  5. @Override
  6. public String handle(Map<String, String> parameters, Map<String, Object> model) {
  7. model.putAll(parameters);
  8. return “myfirst”;
  9. }
  10. @Override
  11. public String getPath() {
  12. return “/myfirst”;
  13. }
  14. }

We must declare the MyFirstMyMvcController in the DispatcherServlet application context :

  1. <beans ()>
  2. (…)
  3. <bean class=“com.zenika.web.MyFirstMyMvcController” />
  4. </beans>

Regarding the GroovyMyMvcViewResolver configuration, our Groovy view should be in the src/main/webapp/WEB-INF/views directory and be called myfirst.gy. If we are sure that we’ll have a id parameter in the HTTP request, the view can look like the following :

  1. “${id}”

So, if you start the web container and go to the http://localhost:8080/mymvc/app/myfirst?id=acogoluegnes URL, you see acogoluegnes in your web browser. Ok, it works, but this is not enough. Let’s use some HTML for the layout and list the HTTP parameters :

  1. import groovy.xml.MarkupBuilder
  2. def writer = new StringWriter()
  3. def xml = new MarkupBuilder(writer)
  4. xml.html {
  5. head { title(‘My MVC’) }
  6. body {
  7. h1(‘HTTP parameters’)
  8. ul {
  9. model.each() {key, value ->
  10. li ( key+” = “+value )
  11. }
  12. }
  13. }
  14. }
  15. writer

If you go then to the http://localhost:8080/mymvc/app/myfirst?id=acogoluegnes&password=test URL, you should something like the following :
mymvc-screenshot.png
Congrats! You’ve just created a web framework on top of Spring MVC !

Summary

We’ve created a very simple web framework on top of Spring MVC. The My MVC framework is obviously not meant to be fully functionnal, it’s just a illustration of how to use Spring MVC as a technical foundation. Spring MVC comes also with a full-blown web framework, implemented using the same extension points we used for My MVC. This web framework is sometimes called Spring @MVC, because it heavily relies on annotations for its configuration, making it easy to write POJO controllers. As of Spring 3.0, @MVC also has a support for REST: you can easily write RESTful web controllers and also embrace the REST philosophy on the view part, to achieve content negociation for example (using the same controller for several representations of the data, depending on what the client accepts). All these mechanisms can be subtile to grab and knowing more about Spring MVC internals thanks to the little My MVC framework will hopefully help you to configure existing Spring MVC components or implement your own.

Auteur/Autrice

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.