Spring 3之MVC & Security简单整合开发(一)

原文链接: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框架,所以看到了我登录的身份列表,下面就是发布消息了,这就很简单了: 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值