原文链接:http://sarin.iteye.com/blog/829738
Spring的MVC模块是一种简洁的Web应用框架,实现了MVC模式来处理HTTP请求和响应。相比于Struts系列,SpringMVC的MVC更加明显,将控制器和视图的定义完全分离,它们不需要在一个命名空间下了。它有Spring的全部优点,bean的配置更加舒服。而Spring 3的注解配置使得代码编写更加优雅。本例结合Spring MVC和Security框架进行小小整合,仅做功能说明,不详细探究其原理。
首先是建立项目,做一个简单的消息发布功能,代码结构如下,使用Maven可以很好的管理依赖:
采用了分层结构,但是没有使用到数据库操作,仅仅做个简短的说明,数据库操作用在后面Security框架验证用户时。下面来看看依赖关系,这样能对Spring的层次结构了解更加清晰:
先来看最基本的web部署描述文件web.xml,将用到的配置写好,Spring 3使用DispatcherServlet派发请求,而Security框架串接过滤器的机制来进行安全处理。配置很简单,如下即可,web请求使用.htm形式:
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
|
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
web-app
version
=
"2.5"
xmlns
=
"http://java.sun.com/xml/ns/javaee"
xmlns:xsi
=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<
context-param
>
<
param-value
>
/WEB-INF/board-service.xml
/WEB-INF/board-security.xml
</
param-value
>
</
context-param
>
<
listener
>
<
listener-class
>org.springframework.web.context.ContextLoaderListener</
listener-class
>
</
listener
>
<
filter
>
<
filter-name
>springSecurityFilterChain</
filter-name
>
<
filter-class
>org.springframework.web.filter.DelegatingFilterProxy</
filter-class
>
</
filter
>
<
filter-mapping
>
<
filter-name
>springSecurityFilterChain</
filter-name
>
<
url-pattern
>/*</
url-pattern
>
</
filter-mapping
>
<
servlet
>
<
servlet-name
>board</
servlet-name
>
<
servlet-class
>org.springframework.web.servlet.DispatcherServlet</
servlet-class
>
</
servlet
>
<
servlet-mapping
>
<
servlet-name
>board</
servlet-name
>
<
url-pattern
>*.htm</
url-pattern
>
</
servlet-mapping
>
</
web-app
>
|
下面是Servlet的配置文件,因为我们使用了注解,这里仅需对视图文件进行一下说明即可,而又配合后面的Security框架,在这里对Security框架的方法拦截注解也声明了一下,这里说明一点,要拦截Controller的方法,必须将Security的声明和Servlet放在一个文件内,否则拦截是没有作用的:
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
|
<?
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:security
=
"http://www.springframework.org/schema/security"
xmlns:context
=
"http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<
context:component-scan
base-package
=
"org.ourpioneer.board.web"
/>
<
bean
class
=
"org.springframework.web.servlet.view.InternalResourceViewResolver"
>
<
property
name
=
"prefix"
value
=
"/WEB-INF/jsp/"
/>
<
property
name
=
"suffix"
value
=
".jsp"
/>
</
bean
>
<
security:global-method-security
jsr250-annotations
=
"enabled"
secured-annotations
=
"enabled"
/>
</
beans
>
|
其中对org.ourpioneer.board.web包进行组件扫描,就会发现我们注解声明的控制器了,下面是对视图解析的说明,我们把视图文件写在/WEB-INF/jsp/下,后缀名为.jsp的文件就是视图文件,为什么把前缀和后缀都声明好了?因为程序里面我们直接写文件名就可以了,非常灵活,它不关心是不是和请求路径是相同的。下面是对Controller方法拦截的Security框架的配置。
配置好Servlet相关内容,剩下就是Service内容了,这个很简单了,声明一个bean就是了,为了配合Security框架连接数据库验证用户身份,这里也配置一个数据源,使用Spring自己的数据源实现:
1
2
3
4
5
6
7
8
|
<
bean
id
=
"dataSource"
class
=
"org.springframework.jdbc.datasource.DriverManagerDataSource"
>
<
property
name
=
"driverClassName"
value
=
"com.mysql.jdbc.Driver"
/>
<
property
name
=
"url"
value
=
"jdbc:mysql://localhost:3306/board"
/>
<
property
name
=
"username"
value
=
"root"
/>
<
property
name
=
"password"
value
=
"123"
/>
</
bean
>
<
bean
id
=
"messageBoardService"
class
=
"org.ourpioneer.board.service.MessageBoardServiceImpl"
/>
|
配置好后,我们来看看程序代码,首先看看定义的领域对象Message,很简单的bean:
1
2
3
4
5
6
7
8
|
package
org.ourpioneer.board.domain;
public
class
Message {
private
Long id;
private
String author;
private
String title;
private
String body;
//省略了getter和setter方法
}
|
下面是Service,我们使用了实现和接口相分离的原则,方面后续在WebService中公开等,可能用不到,但这是一个良好的设计原则。接口内定义四个方法声明:
1
2
3
4
5
6
7
8
9
|
package
org.ourpioneer.board.service;
import
java.util.List;
import
org.ourpioneer.board.domain.Message;
public
interface
MessageBoardService {
public
List<Message> listMessages();
public
void
postMessage(Message message);
public
void
deleteMeesage(Message message);
public
Message findMessageById(Long messageId);
}
|
下面是Service的实现类,就用List放置Message即可,这里我们对Service的方法也进行了安全拦截,这是更细粒度的拦截,后面会详细介绍,现在可以不管:
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
|
package
org.ourpioneer.board.service;
import
java.util.ArrayList;
import
java.util.LinkedHashMap;
import
java.util.List;
import
java.util.Map;
import
org.ourpioneer.board.domain.Message;
import
org.springframework.security.access.annotation.Secured;
public
class
MessageBoardServiceImpl
implements
MessageBoardService {
private
Map<Long, Message> messages =
new
LinkedHashMap<Long, Message>();
//@Secured( { "ROLE_ADMIN", "IP_LOCAL_HOST" })
public
synchronized
void
deleteMeesage(Message message) {
messages.remove(message.getId());
}
//@Secured( { "ROLE_USER", "ROLE_GUEST" })
public
Message findMessageById(Long messageId) {
return
messages.get(messageId);
}
//@Secured( { "ROLE_USER", "ROLE_GUEST" })
public
List<Message> listMessages() {
return
new
ArrayList<Message>(messages.values());
}
//@Secured( { "ROLE_USER" })
public
synchronized
void
postMessage(Message message) {
message.setId(System.currentTimeMillis());
messages.put(message.getId(), message);
}
}
|
下面就该进入控制器部分了,我们一个一个来看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package
org.ourpioneer.board.web;
import
java.util.List;
import
org.ourpioneer.board.domain.Message;
import
org.ourpioneer.board.service.MessageBoardService;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.security.access.annotation.Secured;
import
org.springframework.stereotype.Controller;
import
org.springframework.ui.Model;
import
org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping
(
"/messageList.htm"
)
public
class
MessageListController {
@Autowired
private
MessageBoardService messageBoardService;
@RequestMapping
(method = RequestMethod.GET)
//@Secured( { "ROLE_USER" })
public
String generateList(Model model) {
List<Message> messages = java.util.Collections.emptyList();
messages = messageBoardService.listMessages();
model.addAttribute(
"messages"
, messages);
return
"messageList"
;
}
}
|
对该类进行控制器注解声明,说明是Spring MVC中的控制器,下面是请求映射声明,处理/messageList.htm的请求,Service的注入采用自动装配,连set方法都不用了,下面是对处理方法,可以看出,这是一个简单的POJO,连方法名都是我们自定义的,只需声明HTTP请求方法,就能找到方法了,而Model是传递数据给页面的对象,把获取到的message列表放进去就行了,来看返回值,一个字符串,什么意思?就是JSP页面的名字,是不是很简单,MVC表现的淋漓尽致,这就会找到页面了。
下面是发布消息的类:
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
|
package
org.ourpioneer.board.web;
import
javax.servlet.http.HttpServletRequest;
import
org.ourpioneer.board.domain.Message;
import
org.ourpioneer.board.service.MessageBoardService;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.security.access.annotation.Secured;
import
org.springframework.stereotype.Controller;
import
org.springframework.ui.Model;
import
org.springframework.validation.BindingResult;
import
org.springframework.web.bind.annotation.ModelAttribute;
import
org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping
(
"/messagePost.htm"
)
public
class
MessagePostController {
@Autowired
private
MessageBoardService messageBoardService;
@RequestMapping
(method = RequestMethod.GET)
//@Secured( { "ROLE_USER" })
public
String setupForm(Model model) {
Message message =
new
Message();
model.addAttribute(
"message"
, message);
return
"messagePost"
;
}
@RequestMapping
(method = RequestMethod.POST)
//@Secured( { "ROLE_USER" })
public
String onSubmit(
@ModelAttribute
(
"message"
) Message message,
BindingResult result, HttpServletRequest request) {
message.setAuthor(request.getRemoteUser());
if
(result.hasErrors()) {
return
"messagePost"
;
}
else
{
messageBoardService.postMessage(message);
return
"redirect:messageList.htm"
;
}
}
}
|
GET方式是请求到这个页面,而POST方式是发布消息,最后是重定向,再到messageList.htm,就是这么简单的配置。要注意的是方法实现,先看页面请求方法setupForm(Model model),参数上面已经解释了,是传递给页面的数据对象,里面放置了一个Message对象,做什么用的?肯定页面使用了,不过这是一个空对象,那么自然想到要和表单属性进行绑定,等会看看页面就一清二楚了。下面是onSubmit方法,里面的参数都是我自己定义的,只要记住BindingResult要和数据对象参数Message写在一起,后面的参数写想用的就行,那么我想用HttpServletRequest对象,就写上去。真的很灵活。方法实现很简单,就不多说了。
最后是删除功能了,更简单了:
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
|
package
org.ourpioneer.board.web;
import
org.ourpioneer.board.domain.Message;
import
org.ourpioneer.board.service.MessageBoardService;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.security.access.annotation.Secured;
import
org.springframework.stereotype.Controller;
import
org.springframework.ui.Model;
import
org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.RequestMethod;
import
org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping
(
"/messageDelete.htm"
)
public
class
MessageDeleteController {
@Autowired
private
MessageBoardService messageBoardService;
@RequestMapping
(method = RequestMethod.GET)
//@Secured( { "ROLE_ADMIN" })
public
String messageDelete(
@RequestParam
(required =
true
, value =
"messageId"
) Long messageId,
Model model) {
Message message = messageBoardService.findMessageById(messageId);
messageBoardService.deleteMeesage(message);
model.addAttribute(
"messages"
, messageBoardService.listMessages());
return
"redirect:messageList.htm"
;
}
}
|
只是权限设置为有管理员权限的才能删除,这里先不用。来看方法参数,我们必须要一个请求参数,是messageId,删除消息的标识符。下面就是操作了,很简单。
最后来看一下页面:
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
|
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="security"
uri="http://www.springframework.org/security/tags"%>
<
html
>
<
head
>
<
title
>Message List</
title
>
</
head
>
<
body
>
<
h2
>Welcome! <
security:authentication
property
=
"name"
/></
h2
>
<
security:authentication
property
=
"authorities"
var
=
"authorities"
/>
<
ul
>
<
c:forEach
items
=
"${authorities}"
var
=
"authority"
>
<
li
>${authority.authority}</
li
>
</
c:forEach
>
</
ul
>
<
hr
/>
<
c:forEach
items
=
"${messages}"
var
=
"message"
>
<
table
>
<
security:authorize
ifAllGranted
=
"ROLE_ADMIN,ROLE_USER"
>
<
tr
>
<
td
>Author</
td
>
<
td
>${message.author}</
td
>
</
tr
>
</
security:authorize
>
<
tr
>
<
td
>Title</
td
>
<
td
>${message.title}</
td
>
</
tr
>
<
tr
>
<
td
>Body</
td
>
<
td
>${message.body}</
td
>
</
tr
>
<
tr
>
<
td
colspan
=
"2"
><
a
href
=
"messageDelete.htm?messageId=${message.id}"
>Delete</
a
></
td
>
</
tr
>
</
table
>
<
hr
/>
</
c:forEach
>
<
a
href
=
"messagePost.htm"
>Post</
a
>
<
a
href
=
"<c:url value="
/j_spring_security_logout" />">Logout</
a
>
</
body
>
</
html
>
|
列表页面有Security框架标签的使用,仅做MVC时可以先注释起来。这里使用了JSTL标签来遍历message列表,都很简单。
下面是发布消息的页面:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<
html
>
<
head
>
<
title
>Message Post</
title
>
</
head
>
<
body
>
<
form:form
method
=
"POST"
modelAttribute
=
"message"
>
<
table
>
<
tr
>
<
td
>Title</
td
>
<
td
><
form:input
path
=
"title"
/></
td
>
</
tr
>
<
tr
>
<
td
>Body</
td
>
<
td
><
form:textarea
path
=
"body"
/></
td
>
</
tr
>
<
tr
>
<
td
colspan
=
"2"
><
input
type
=
"submit"
value
=
"Post"
/></
td
>
</
tr
>
</
table
>
</
form:form
>
</
body
>
</
html
>
|
前面说的数据绑定,这里就很容易看明白了吧。没有什么可以多解释的。
准备都做好后就是运行了,我们启动Jetty,来看看效果。
因为我结合了Security框架,所以看到了我登录的身份列表,下面就是发布消息了,这就很简单了: