近来,以信息为中心的表述性状态转移(Representational State Transfer, REST)已成为替换传统 SOAP Web 服务的流行方案,Spring MVC 封装了对 REST 的良好支持。

当谈论REST时,有一种常见的错误就是将其视为“基于 URL 的 Web 服务”,将 REST 作为另一种类型的远程过程调用(RPC)机制。恰好相反,REST 与 RPC 几乎没有任何关系,RPC 是面向服务的,并关注于行为和动作,而 REST 是面向资源的,强调描述应用程序的事物和名词。REST 就是将资源的状态以最合适的形式从服务器端转移到客户端(或者反之)。

本节应用 @ResponseBody 注解实现简单的 RESTless URL,并与 JQuery AJAX 结合。

用户登录之后,转到 /u/USERNAME 页面,显示用户微博列表,每条微博后面有 Delete URL。使用RESTless URL and AJAX 实现添加新微博和删除旧微博。

需要添加 jackson 包,有 1.x 和 2.x 两种版本,1.x 已不在开发新功能,只维护 bug。这里使用 2.x 版本。

servlet.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- Configure to plugin JSON as request and response in method handler -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <ref bean="jsonMessageConverter"/>
        </list>
    </property>
</bean>

<!-- Configure bean to convert JSON to POJO and vice versa -->
<bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</bean>

微博条目 Spittle.java,Date 类型的序列化使用 jackson 2.x 版本新增的 JsonFormat 注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Entity
@Table(name="spittle_tbl")
public class Spittle implements Serializable {
    private static final long serialVersionUID = 1L;

    //omit getter and setter

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="spittle_id")
    private Long spittleId;

    @Column(name="text")
    private String text;

    @Column(name="push_time")
    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    private Date pushTime;

    @JoinColumn(name="user_id", referencedColumnName = "user_id")
    @ManyToOne(optional = false)
    private User user;

}

用户页面 Controller

 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Controller
@RequestMapping(value="/u/{username}")
public class SpitterController {
    @Autowired
    private HibSpittleService hibSpittleService;

    @RequestMapping(method = RequestMethod.GET)
    public ModelAndView userPage(@PathVariable String username, HttpServletRequest request, Model model) {
        User user = (User) request.getSession().getAttribute("user");
        if (user == null)
            return new ModelAndView("redirect:/");

        List<Spittle> spittles = hibSpittleService.getSpittlesForUser(user);
        model.addAttribute("spittle", new Spittle());
        model.addAttribute("spittles", spittles);

        return new ModelAndView("user_page");
    }

    @RequestMapping(method=RequestMethod.POST, produces = "application/json; charset=utf-8")
    @ResponseStatus(HttpStatus.CREATED)
    public @ResponseBody
    Spittle pushSpittle(@PathVariable String username, @RequestBody Spittle spittle,
                              HttpServletRequest request ) {
        User user = (User) request.getSession().getAttribute("user");

        spittle.setPushTime(new java.sql.Timestamp((new java.util.Date()).getTime()));
        spittle.setUser(user);
        hibSpittleService.addSpittle(spittle);

        return spittle;
    }

    @RequestMapping(value="/delete/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public Long delSpittleByID(@PathVariable String username, @PathVariable Long id) {
        hibSpittleService.delSpittleById(id);

        return id;
    }

    @RequestMapping(value="logout")
    public String logout(HttpServletRequest request) {
        request.getSession().invalidate();

        return "redirect:/";
    }
}

用户页面 jsp

  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
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>Success</title>
    <script src="/static/js/jquery-1.11.0.min.js"></script>

    <script type="text/javascript">

        $(document).ready(function() {
            $('#newSpittle').submit(function(event) {

                var mess = $('#text').val();
                var formData = { "text" : mess};

                $.ajax({
                    url: $("#newSpittle").attr( "action"),
                    type: "POST",
                    contentType: 'application/json; charset=utf-8',
                    dataType: 'json',
                    async: false,
                    data: JSON.stringify(formData),

                    success: function(spittle) {
                        var respContent = "";
                        var delLink = "/u/" + spittle.user.userName + "/delete/" + spittle.spittleId;

                        respContent += "<li>";
                        respContent += spittle.user.userName + ", ";
                        respContent += spittle.pushTime + ", " ;
                        respContent += spittle.text;
                        respContent += '<a href="' + delLink + '">Delete</a>';
                        respContent += "</li>";

                        $('#mblog').prepend(respContent);
                    },
                    error: function(jqXHR, textStatus, errorThrown) {
                        var respBody = $.parseJSON(jqXHR.responseText);
                        alert("Error: " + respBody.message);
                    }
                });

                event.preventDefault();
            });

            var deleteLink = $("a:contains('Delete')");

            $(deleteLink).click(function(event) {

                $.ajax({
                    url: $(event.target).attr("href"),
                    type: "DELETE",

                    success: function(id) {
                        alert("delete" + id);
                        var rowToDelete = $(event.target).closest("li");

                        rowToDelete.remove();
                    }
                });

                event.preventDefault();
            });

        });
    </script>

    <style type="text/css">
        html,body{margin:10px; padding: 10px;}
        .left,.right,.center { height: 600px; }

        .left {float:left; width: 200px; background-color: #ccc;}
        .right {float:right; width: 200px; background-color: #ccc;}
        .center { margin: 0 210px; background-color: #666;}

        ul.navbar {
            list-style-type: none;
            padding: 0;
            margin: 0 }
        ul.navbar li {
            background: white;
            margin: 0.5em 0;
            padding: 0.3em;
            border-left: 1em solid black }
        ul.navbar a {
            text-decoration: none }
        a:link {
            color: blue }
        a:visited {
            color: purple }

        ul.mblog li {
            background: #ccc;
            margin-bottom: 0.5em;
            margin-right: 1em;
        }

    </style>
</head>

<body>
    <h1>Welcome, <c:out value="${sessionScope.user.getUserName()}" /> </h1>
    <div class="left"></div>
    <div class="right">
        <span><c:out value="${sessionScope.user.getUserName()}" /></span>
        <img src="../static/img/default.jpg" />
        <!-- Site navigation menu -->
        <ul class="navbar">
            <li><a href="index.html">Home page</a>
            <li><a href="musings.html">Musings</a>
            <li><a href="town.html">My town</a>
            <li><a href="/u/${sessionScope.user.getUserName()}/logout">Logout</a>
        </ul>
    </div>
    <div class="center">
        <div>
            <fieldset>
            <sf:form id="newSpittle" method="post" modelAttribute="spittle">
                <sf:textarea id="text" path="text" rows="5" cols="100" />
                <br />
                <input name="commit" type="submit" value="Push">
            </sf:form>
            </fieldset>
        </div>
        <div id="newWeibo"></div>

        <div>
            <ul id="mblog" class="mblog">
            <c:forEach var="spittle" items="${spittles}">
            <li>
                ${spittle.user.userName}, ${spittle.pushTime}, ${spittle.text}
                <a href="/u/${sessionScope.user.getUserName()}/delete/${spittle.spittleId}">Delete</a>
            </li>
            </c:forEach>
            </ul>
        </div>
    </div>

</body>
</html>

参考

http://www.journaldev.com/2552/spring-restful-web-service-example-with-json-jackson-and-client-program

http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-in-java-with-jersey-and-spring/