Monday, 25 January 2016

Replacing web.xml with Java based configuration for Servlet 3.x Webapplications using Spring

Background



As we know if you want to deploy a web app in a container like tomcat you have a context file called web.xml that creates context necessary for you app to run. For eg. you can provide libraries that will be available to your web app. 

Your typical web.xml will look like - 


<web-app id="WebApp_ID" version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <display-name>Spring Web MVC Demo Application</display-name>

  <servlet>
      <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
     <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
  </servlet-mapping>
   

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
</web-app>


Lets see its Java equivalent

Java Configuration

  • For Java configuration you can write your own class that implements WebApplicationInitializer  interface and override it's
    onStartup() method.
  • WebApplicationInitializer is an interface provided by Spring MVC that ensures your implementation is detected and automatically used to initialize any Servlet 3 container.
  • An abstract base class implementation of WebApplicationInitializer named AbstractDispatcherServletInitializer makes it even easier to register the DispatcherServlet by simply overriding methods to specify the servlet mapping and the location of the DispatcherServlet configuration.  
  • This is from Spring 3.1+
Lets now see the Java config -


public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
    
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();

        rootContext.register(RootApplicationConfig.class);
       
        container.addListener(new ContextLoaderListener(rootContext));
       
        AnnotationConfigWebApplicationContext displacherContext = new AnnotationConfigWebApplicationContext();
        displacherContext.register(MyWebConfig.class);
    
    
        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(displacherContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
} 


Or as I mentioned before you can extend abstract class AbstractDispatcherServletInitializer.

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }


Now you don't need web.xml as part of your web application. Your container will startup your app using Java config provided. This classes are detected automatically.

If you are not using Spring you will need to do -

  1. Create a custom class that implements ServletContainerInitializer (i.e. com.osfg.MyServletContainer
  2. Create a file in your META-INF/services folder named javax.servlet.ServletContainerInitializer which will contain the name of your implementation above (com.osfg.MyServletContainer)
In Spring ofcourse you don't need to do this.

Related Links

Friday, 8 January 2016

Making @PathVariable optional in Spring MVC

Background



A simple controller class would look like - 


    @RequestMapping(value="/home/{name}", method = RequestMethod.GET)
    public String welcome(Model model,  @PathVariable(value="name") String name, @RequestParam(value="surname") String surname) {
        model.addAttribute("test", "TestValue");
        System.out.println("Name : " + name);
        System.out.println("Surname : " + surname);
        return "welcome";
    }

This controller method basically expects a URL like 
  • http://localhost:8080/WebProject/home/aniket?surname=thakur
where aniket is the name (path variable) where as thakur is the surname (request param).

Here you cannot have name or surname blank. If you do you will get an error.  So URLs like -
  • http://localhost:8080/WebProject/home?surname=thakur OR

    You will get  HTTP Status 404 - Requested Resource is not available
  • http://localhost:8080/WebProject/home/aniket

    You will get - HTTP Status 400 - Required String parameter 'surname' is not present
 will not work!

 Only URL that will work as mentioned above is -

  • http://localhost:8080/WebProject/home/aniket?surname=thakur
For this URL you should see output as -

Name : aniket
Surname : thakur

However you can make @RequestParam option. Spring provides you this functionality. All you have to do is set it's required property as false i.e
  • @RequestParam(value="surname", required=false) String surname
Now you can hit the URL -
  •  http://localhost:8080/WebProject/home/aniket
and you should not see 404 error. Output would print -

Name : aniket
Surname : null

However there is no such parameter in @PathVariable. So you cannot truly make it optional. However there are alternative and we will look at them now.

 Making @PathVariable optional in Spring MVC

There are two way in which you can work - 

  1. Provide two paths in value - One with path param and one without. In Arguments take map of path params and check for null.


        @RequestMapping(value={"/home/{name}","/home"}, method = RequestMethod.GET)
        public String welcome(@PathVariable Map<String, String> pathVariablesMap) {
            if (pathVariablesMap.containsKey("name")) {
                //corresponds to path "/home/{name}"
                System.out.println("With Name : " + pathVariablesMap.get("name"));
            } else {
                //corresponds to path "/home"
                System.out.println("With No Name");
            }   
            return "welcome";
        }
    


    Here you are essentially saying this controller will map to both URLS - "/home/{name}" and "/home" and if you do get name in pathparams map then the URL was "/home/{name}" else it was "/home".

    And now if you hit http://localhost:8080/WebProject/home/aniket you should get output as - "With Name : aniket" and if you hit http://localhost:8080/WebProject/home you should see output - "With No Name".
  2.  Another way is to use java.util.Optional provided by Java8. So if you are using Spring 4.1 and Java 8 you can use java.util.Optional which is supported in @RequestParam, @PathVariable, @RequestHeader and @MatrixVariable in Spring MVC -

        @RequestMapping(value="/home/{name}", method = RequestMethod.GET)
        public String welcome(@PathVariable Optional<String> name) {
            if (name.get() != null) {
                //corresponds to path "/home/{name}"
                System.out.println("With Name : " + name.get());
            } else {
                //corresponds to path "/home"
                System.out.println("With No Name");
            }   
            return "welcome";
        }
    


    Repeat same test as point 1. You should get same result. This is just another alternate way.

So as we have seen pathvariables cannot truly be null but there are workarounds. This case should not typically arise as it is poor design. You should always have some value in path param. If you are certain it can be null better make it a @RequestPram and use requiref=false.


Related Links

Sunday, 3 January 2016

Using @ModelAttribute annotation in Spring MVC

Background

There are many cases in which you may need to populate your Model with some default data or bind the data in request in your model in controller/handler method. It are these cases when @ModelAttribute annotation comes in handy. So in this post we will see how exactly @ModelAttribute method works with some example.



Working of @ModelAttribute

  • Before starting I would like to assert your class should be a controller class for this to work which means your class should be annotated with @Controller annotation .
Next this  @ModelAttribute can be at
  1. Method level OR
  2. Method argument level.

We will come to each in a moment.  @ModelAttribute take an optional "value" that represents the name of this attribute. For eg. @ModelAttribute("test") Why we say it is optional? Because as per Springs "Convention over Configuration" principle it allows you to not to provide explicit value and in which case default name is assigned to the ModelAttribute. For eg. if you have a class called "Car" then the model attribute will be stored in the Model by name "car" (notice the small letter c).

The way this annotation works is as follows -

  1. Before invoking the handler method, Spring invokes all the methods that have @ModelAttribute annotation. So yeah a Controller annotated class can have multiple @ModelAttribute annotated methods. It adds the data returned by these methods to a temporary Map object. The data from this Map would be added to the final Model after the execution of the handler method.
  2. Then it prepares to invoke the the handler method. To invoke this method, it has to resolve the arguments. If the method has a parameter with @ModelAttribute, then it would search in the temporary Map object with the value of @ModelAttribute. If it finds, then the value from the Map is used for the handler method parameter.
  3. If it doesn't find it in the Map, then it checks if there is a SessionAttributes annotation applied on the controller with the given value. If the annotation is present, then the object is retrieved from the session and used for the handler method parameter. If the session doesn't contain the object despite of the @SessionAttributes, then an error is raised.
  4. If the object is not resolved through Map or @SessionAttribute, then it creates an instance of the parameter-type and passes it as the handler method parameter. Therefore, for it to create the instance, the parameter type should be a concrete-class-type (interfaces or abstract class types would again raise an error).
  5. Once the handler is executed, the parameters marked with @ModelAttributes are added to the Model.

Above is the general working of the annotation at method level or method argument level.


Styles for using ModelAttribute

You can use one of the following styles for adding ModelAttributes to Model - 

  1. Implicitly return variables which will get added to the Model


    @ModelAttribute("data")
    public String addData() {
        return "test";
    }
    

    NOTE : Above "test" String will be added to the Model as name "string"(small s) i.e string=test. If you do something like below -

    @ModelAttribute
        public List<String> addTest() {
            return Arrays.asList("newValue");
        }
    


    then name will be "stringList" i.e stringList=[newValue].
  2. OR get the Model object and explicitly add multiple data to it

    @ModelAttribute
    public void addData(Model model) {
        model.addAttribute("test");
        model.addAttribute("tes1");
        // add more ...
    }
    



    NOTE : If you add in model two attributes with same name the later one will override the 1st one. Consequently in above code "test1" will override "test" as value for name "string" which is constructed with convention i.e string=test1(After all it is a Map underneath and keys of Map are unique (if you remember keys returns set data structure)).

Examples

Now let us see examples that will help us understand above concepts even more.

Following is a simple Controller class - 

@Controller
public class WelcomeController {

    @RequestMapping(value="/home", method = RequestMethod.GET)
    public String welcome() {
        return "welcome";
    }
    
    @ModelAttribute("test")
    public String addTest() {
        return "testValue";
    }
    
}

Here is what is happening -  As we know before handler methods are executed all methods annotated with @ModelAttribute are execute. Consequently out addTest() method gets executed and adds a String with name "test" in the Model. You can access the same in your JSP called "welcome.jsp" as follows - 

  • <h1><c:out value='${test}' /></h1>
and here you will see  "testValue" is h1 heading size.

You can also access this ModelAttribute in your handler method as follows -

    @RequestMapping(value="/home", method = RequestMethod.GET)
    public String welcome(@ModelAttribute("test") String test) {
        System.out.println(test);
        return "welcome";
    }


and you should see "testValue" getting printed in your server console. You can even override the value of "test" ModelAttribute in your handler class.

    @RequestMapping(value="/home", method = RequestMethod.GET)
    public String welcome(@ModelAttribute("test") String test, Model model) {
        System.out.println(test);
        model.addAttribute("test", "newTestValue");
        return "welcome";
    }


and now your JSP should show "newTestValue" in h1 heading.

NOTE: When using controller interfaces (e.g. for AOP proxying), make sure to consistently put all your mapping annotations — such as @RequestMapping and @SessionAttributes — on the controller interface rather than on the implementation class.

NOTE : SessionStatus.setComplete() method will trigger cleaning of Session Attributes, but only those which Spring will find "actual session attribute". Suppose that you declare 3 session attributes, but use only 1 of them in your handler method parameters then only that will be cleared.

Related Links

Friday, 1 January 2016

Using interceptors in Spring MVC

Background

Interceptors as their name suggest intercepts request that are delegated to your controller by the dispacher setvlet. Why would we do that you ask? Well there are multiple possibilities. You can implement in these interceptors functionality that is common to multiple controllers. Like for eg - 
  • Add common model attributes
  • Set response header
  • Audit requests
  • Measure performance etc
In this post we will see how to implement those interceptors in Spring MVC.


HandlerInterceptor interface

HandlerIntercetor is an interface with following methods - 

  • boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler);
  • void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler. ModelAndView modelAndView);
  • void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler. Exception ex);
Their call sequence is reflected in following picture -




NOTE : 

preHandle() returns either true or false -
  • true : continue doewn the interceptor chain
  • false : invoke controller and skip remaining interceptors
Does this also mean you can have multiple interceptors? Precisely Yes! You can chain interceptors and they will be called one after another.

As in most case you don't want to implement all 3 methods so Spring has a separate class called - HandlerInterceptorAdapter which you can easily extend and override those methods that you need.


Configuring Interceptors

- Always keep in mind interceptors are are HandlerMapping level!

Spring as you know has multiple components like
  • HandlerMapping
  • HandlerAdapter
  • ViewResolver
  • HandlerExceptionResolver
 and user define components like
  • Controllers/Handler
  • Interceptors
There are 3 ways to configure interceptors

  1. Define interceptor as a property of your HandlerMapping bean -

    <bean class="...DefaultAnnotationHandlerMapping">
        <preoperty name="interceptors">
            <list>
                <bean class=""/>
                <bean class=""/>
            </list>
        </property>
    </bean>
  2. OR use mvc namespace to define interceptors -

    <mvc:interceptors>
        <bean class="myPackage.Interceptor1"/>
        <bean class="myPackage.Interceptor2"/>
    </mvc:interceptors>

    NOTE : This will be applied to all HandlerMapping beans. If you want to restrict your interceptors to particular HandlerMapping use -
  3. mapping paramter. You can optionally give exclude paramter too (available from Spring 3.1+)
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/secure/*" />
            <mvc:exclude-mapping path="/secure/help" /> 
            <bean class="" />
        </mvc:interceptor>
    </mvc:interceptors>

Interceptor Example

Lets see an example of how to create an interceptor - 

Lets first create s simple servlet configuration to configure our dispacher Servlet - 

<?xml version="1.0" encoding="UTF-8"?>

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

  <servlet>
    <servlet-name>admin</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>
                /WEB-INF/spring/*.xml
            </param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>admin</servlet-name>
    <url-pattern>/admin/*</url-pattern>
  </servlet-mapping>

</web-app>

As you can see our dispacher servlet will server all requests that are of format 
  • http://localhost:8080/projectName/admin
 projectName is the name of Spring project you have provided.


Now lets create the Spring configuration. Create a file under /WEB-INF/spring with xml extension and add following contents to it -

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/mvc 
                        http://www.springframework.org/schema/mvc/spring-mvc.xsd
                        http://www.springframework.org/schema/context 
                        http://www.springframework.org/schema/context/spring-context.xsd">


    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <context:component-scan base-package="myPackage" />

    <mvc:annotation-driven/>

     
    <mvc:interceptors>
            <bean class="myPackage.TestInterceptor" />
    </mvc:interceptors>     
    

</beans>


If you notice we have configured an interceptor to intercept all requests (i.e applicable for all handler mappings)

If you want it to be for specific URL you can do something like - 

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**" />
            <bean class="myPackage.TestInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors> 

or

    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/home" />
            <bean class="myPackage.TestInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors> 


Finally lets create our Controller and interceptor -

WelcomeController.java

@Controller

public class WelcomeController {
    @RequestMapping(value="/home", method = RequestMethod.GET)
    public String welcome() {
        return "welcome";
    }
}    

and TestInterceptor.java

public class TestInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Request intercepted");
        return true;
    }
    
}

After you have done this setup you can hit -
  • http://localhost:8080/projectName/admin/home
and see  "Request intercepted" printed in the logs. Also you can define multiple such <mvc:interceptor> tags thereby chaining them.

Also not interceptors will be hit only if the path that you are hitting is valid. Si if you try /home/test then interceptor will not be hit. It will be hit only if the requested path forms a part of valid HandlerMapping (like /home in above case).

Also will remind you again interceptors are configured at HandlerMapping level. In this case it would be RequestMappingHandlerMapping (Spring 3.1+ with mvc:annotation-driven) or DefaultAnnotationHandlerMapping.


Related Links

Difference between "/*" and "/**" in Spring MVC paths

Background

There are many places in Spring configuration where we need to provide path. It might be a mapping or a resource path. For example an interceptor mapping - 

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/secure/**" />
        <mvc:exclude-mapping path="/secure/help" /> 
        <bean class="" />
    </mvc:interceptor>
</mvc:interceptors>

OR Servlet mapping in web.xml

  <servlet-mapping>
    <servlet-name>admin</servlet-name>
    <url-pattern>/admin/*</url-pattern>
  </servlet-mapping>

or even your resource chain

<mvc:resources mapping="/css/**" location="/css/">
    <mvc:resource-chain resource-cache="true" auto-registration="true">
        <mvc:resolvers>
            <mvc:version-resolver>
                <mvc:content-version-strategy patterns="/**"/>
            </mvc:version-resolver>
        </mvc:resolvers>
    </mvc:resource-chain>
</mvc:resources>

In all these place you can use  "/*" or "/**". But the million dollar question is what does each mean and when do we use what?


Difference between "/*" and "/**" in Spring MVC paths

  • An asterisk ('*') matches zero or more characters, up to the occurrence of a '/' character (which serves as a path separator). A string, such as "/abcd/docs/index.html", would not match successfully against the pattern '/*/*.index.html'. The first asterisk matches up to the first path separator only, resulting in the "abcd" string. A successful matching pattern would be '/*/*/*.html'.
  • A string containing two asterisks ('**') matches zero or more characters. This could include the path separator '/'. In this case, "/abcd/docs/index.html" would successfully match the '/**/*.html' pattern. The double asterisk, including the path separator, would match the "abcd/docs" string.

So to sum up "/**" takes into account even the subpaths that may include the "/" i.e the path separator.

AntPathMatcher

This is a path pattern that used in Apache ant, spring team implement it and use it throughout the framework.

The mapping matches URLs using the following rules:

  • ? matches one character
  • * matches zero or more characters
  • ** matches zero or more 'directories' in a path
Some examples:

  • com/t?st.jsp - matches com/test.jsp but also com/tast.jsp or com/txst.jsp
  • com/*.jsp - matches all .jsp files in the com directory
  • com/**/test.jsp - matches all test.jsp files underneath the com path
  • org/springframework/**/*.jsp - matches all .jsp files underneath the org/springframework path
  • org/**/servlet/bla.jsp - matches org/springframework/servlet/bla.jsp but also org/springframework/testing/servlet/bla.jsp and org/servlet/bla.jsp

 Docs

Related Links

t> UA-39527780-1 back to top