Andres’ Tech Blog

Focusing on java technologies.

Servlet to Spring MVC Migration Guide

Introduction

I recently worked on a migration from good-old servlets web-services project to a REST using Spring MVC. The main motivation for the migration was testability. Servlets made very hard to create a good test suite, not good enough for continious integration. On the other hand Spring MVC not only makes testing posible but enjoyable. I’ll briefly layout my strategy and learnings from that project.

Step 1: Create High-level Http tests

This step wil save you a lot of time in the long run. They will serve as a verification step. For these tests I used RestAssured. It lets you test web-services from the client’s perspective. jsonPath makes it easy to verify responses.

Step 2: Transform Servlets to Controllers

Step 2a: Create a one Cotroller per Servlet

Copy and paste the content of get and post### In this step pass the HttpSevletRequest and HttpServletResponse as parameters

1
2
3
4
5
6
7
8
9
10
public class HelloWorld extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException{
        ...
    }
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException{
        ...
    }
}

Becomes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMqpping("hello")
@Controller
public class HelloWorldController {

    @RequestStatus(RequestStatus.OK)
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws Exception{
        ...
    }
    @RequestStatus(RequestStatus.OK)
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws Exception{
        ...
    }
}

Step 2b: Replace Dependencies to HttpServletRequest

Pass the parameter types specific to your service. This:

1
2
3
4
5
6
7
8
9
10
11
12
@RequestMqpping("hello")
@Controller
public class HelloWorldController {

    @RequestStatus(RequestStatus.OK)
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws Exception{
          String name = request.getParameter("name");
        ...
    }

}

Becomes:

1
2
3
4
5
6
7
8
9
10
@RequestMqpping("hello")
@Controller
public class HelloWorldController {

    @RequestStatus(RequestStatus.OK)
    public void doGet(@RequestParam String name, HttpServletResponse response)
        throws Exception{
        ...
    }
}

Step 2c: Create appropriate return types and remove dependency to HttpServletResponse

Split Methods and return the appropriate Object Types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RequestMqpping("hello")
@Controller
public class HelloWorldController {

    @RequestStatus(RequestStatus.OK)
    public void doGet(@RequestParam String name,@RequestParam String action, HttpServletResponse response)
        throws Exception{
        ...
        PrintWriter writer = response.getWriter();
        if("sayhello".equals(action)){
            writer.write("{message:\"Hello " + name + "\"}");
        }else if("list".equals(action)){
            //write a list to writter or with Gson
        }
        ...
    }
}

Becomes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RequestMqpping("hello")
@Controller
public class HelloWorldController {

    public static class HelloMessage{
      public String message;
    }

    @RequestMapping("" params="action=sayhello")
    @ResponseBody
    public HelloMessage sayHello(@RequestParam String name)
        throws Exception{
          HelloMessage helloMessage = new HelloMessage()
            helloMessage.message ="Hello " + name );
            return helloMessage;
    }
    @RequestMapping("" params="action=list")
    @ResponseBody
    public List<HelloMessage> list()
        throws Exception{
        List<HelloMessage> list = new ArrayList();
            //build list
        return list;
    }
}

Step 3: Create Unit Test for Controllers

Use @RunWithSpring

Step 4: Integrate Security

Step 5: Create Integration Tests

Use MockMvc

Objectify’s OnLoad Can Be Tricky

When creating a Ref<?> to another object can be tricky since, here is the scenario:

I have an Entity UserPost that has a reference to UserProfile like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Entity
public class UserPost {
    @Id
    public Long id;

    @JsonIgnore
    @Index
    @Load(All.class)
    private Ref<UserProfile> user;

    @Ignore
    public String userNickname;

    @OnLoad
    public void onLoad(){
        if(user!=null){
            UserProfile userProfile = user.get();
            if (userProfile != null) {
                this.userNickname = userProfile.userNickname;
            }
        }
    }
  
  public void setUserRef(Ref<UserProfile> userRef) {
        this.user = userRef;
    }

    public static class All {
    }

The problem then was that userNickname was null sometimes even when user was being set. After some trial and error I found this post Google Groups where they mention the fact that @OnLoad only gets called when the Entity is not found in the session.

My solution: call onLoad directly when setting the reference. That makes that instance to be usable right away. But at the same it will be loaded when needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Entity
public class UserPost {
    @Id
    public Long id;

    @JsonIgnore
    @Index
    @Load(All.class)
    private Ref<UserProfile> user;

    @Ignore
    public String userNickname;

    @OnLoad
    public void onLoad(){
        if(user!=null){
            UserProfile userProfile = user.get();
            if (userProfile != null) {
                this.userNickname = userProfile.userNickname;
            }
        }
    }
  
  public void setUserRef(Ref<UserProfile> userRef) {
        this.user = userRef;
      onLoad();
    }

    public static class All {
    }

Using Objectify Alongside JDO

When migrating from JDO to Objectify, It’s often the case where you need them to co-exist during the transition. Here is my case:

Suppose that you have an entity called UserProfile defined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
@PersistenceCapable
public class UserProfile implements Serializable {

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;
    @Persistent
    public String photoPath;
    @Persistent
    public String userNickname;
}

A pretty normal JDO-annotated entity. Now reading on-line people say that Ofy and JDO can co-exist. So I tried something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
@PersistenceCapable
public class UserProfile implements Serializable {
    @Ignore
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Id
    public Long oid;

    @Persistent
    public String photoPath;
    @Indexed
    @Persistent
    public String userNickname;
}

Notice that I’m explicitly ignoring the key. However I get the following vey cryptic exception:


java.lang.NoClassDefFoundError: Could not initialize class com.x.model.OfyService

The solution I found was to leave UserProfile intact and create a new class:

1
2
3
4
5
6
7
8
9
10
@Entity(name="UserProfile")
public class UserProfileOfy implements Serializable {
    @Id
    public Long oid;

    public String photoPath;
    @Indexed
    public String userNickname;
}

Notice the name parameter in the @Entity annotation.