1.整体框架和流程

    首先是跟spring-mvc没有直接关系但是是系统整体的流程的说明。
    以user管理为例,最外层是UserController结构,这个结构是跟spring-mvc直接关联的,之后会细讲。这个结构的主要是分配url的处理函数。当用户在浏览器中输入url时,spring-mvc把url和表单信息引导到UserController的某个函数中处理,然后返回对应的view表示物。该view有多种返回形式。权限验证都在这一层,能够判断哪些view有权限访问。
    controller中有一个SystemService,基本就是传统ssh框架中的service了,负责某一功能的业务逻辑实现。service中还有各种DAO的实体,能够通过DAO访问数据库。除此之外,还有SystemAuthorizingRealm以及IdentityService,前者能在执行处理的过程中管理认证信息,特别是一些缓存信息;后者则跟Activiti,也就是工作流有关,具体细节还待摸索。总而言之,service直接操作数据实体,提供所需的各种业务逻辑实现。这些相关类都通过spring的自动装配导入。Transaction都在这一层。
    此外就是User类和UserDAO,hibernate的内容,不用赘述。

2.spring-mvc的annotation

2.1 annotaton

@Autowired:
    @Autowired注解标签可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作。可以写在成员变量之上,也可以在变量对应的set方法上。

public class RoleServicesLogic implements RoleServices {
    @Autowired
    private RoleDao roleDao; 
}

    也可以对方法进行注解

public class RoleServicesLogic implements RoleServices {
    private RoleDao roleDao; 
    @Autowired
    public void setRoleDao(RoleDao roleDao) {
        this.roleDao = roleDao;
    } 
}

    以上两种不同实现方式中,@Autowired的标注位置不同,它们都会在Spring在初始化RoleManagerImpl这个bean时, 自动装配roleDao这个属性,区别是:第一种实现中,Spring会直接将RoleDao类型的唯一一个bean赋值给roleDao这个成员变量; 第二种实现中,Spring会调用setRoleDao方法来将RoleDao类型的唯一一个bean装配到RoleDao这个属性。

2.2 常用spring的anootation

  • @Service用于标注业务层组件
  • @Repository用于标注数据访问组件,即DAO组件
  • @Controller用于标注控制层组件,如Struts中的Action或者springmvc的controller
  • @Component泛指组件,当组件不好归类时,可以使用这个注解进行标注
  • @Entity 实体类注解标签
  • @Table 实体类对应的数据库实际的表名,不设置表名默认数据库表名与类名一致。

2.3 RequestMapping

1)最基本的,方法级别上应用,例如:
@RequestMapping(value="/departments")  
public String simplePattern(){   
    System.out.println("simplePattern method was called");  
    return "someResult";  
}  

    则访问http://localhost/xxxx/departments的时候,会调用 simplePattern方法了

2)参数绑定
@RequestMapping(value="/departments")  
public String findDepatment(  
    @RequestParam("departmentId") String departmentId){       
    System.out.println("Find department with ID: " + departmentId);  
    return "someResult";  
}  

    形如这样的访问形式:
    /departments?departmentId=23就可以触发访问findDepatment方法了

3) REST风格的参数
@RequestMapping(value="/departments/{departmentId}")  
public String findDepatment(@PathVariable String departmentId){  
    System.out.println("Find department with ID: " + departmentId);  
    return "someResult";  
}  

    形如REST风格的地址访问,比如:
    /departments/23,其中用(@PathVariable接收rest风格的参数

4) REST风格的参数绑定形式之2

    先看例子,这个有点象之前的:

@RequestMapping(value="/departments/{departmentId}")  
public String findDepatmentAlternative(  
    @PathVariable("departmentId") String someDepartmentId){  
    System.out.println("Find department with ID: " + someDepartmentId);  
    return "someResult";  
}  

    这个有点不同,就是接收形如/departments/23的URL访问,把23作为传入的departmetnId,,但是在实际的方法findDepatmentAlternative中,使用
@PathVariable("departmentId") String someDepartmentId,将其绑定为
someDepartmentId,所以这里someDepartmentId为23

5) url中同时绑定多个id
   @RequestMapping(value="/departments/{departmentId}/employees/{employeeId}")  
public String findEmployee(  
    @PathVariable String departmentId,  
    @PathVariable String employeeId){  
    System.out.println("Find employee with ID: " + employeeId +   " from department: " + departmentId);  
    return "someResult";  
}  

    这个其实也比较好理解了。

6) 支持正则表达式
@RequestMapping(value="/{textualPart:[a-z-]+}.{numericPart:[\\d]+}")  
public String regularExpression(  
    @PathVariable String textualPart,  
    @PathVariable String numericPart){  
    System.out.println("Textual part: " + textualPart +   ", numeric part: " + numericPart);  
    return "someResult";  
}  

    比如如下的URL:/sometext.123,则输出:
    Textual part: sometext, numeric part: 123

2.4 RequestParam

    springmvc提供了@RequestParam注释帮助我们获取参数。
    用法@RequestParam("接收的参数名")

@RequestMapping(params="servlet=login")
public String login(@RequestParam("username")String username,@RequestParam("password")String password,HttpServletRequest request,ModelMap map){
     //处理登录逻辑,省略
    return "success";
}

    当客户端的URL提交了username参数,password参数,那么我们的Controller就可以接收并处理了。
    要注意,提交的username参数和password参数不可以是null,即一定要传这两个参数,不然会抛异常。
    另外,@RequestParam可以省略参数名,那么就会以它注释的变量名作为参数名。

2.5 ModelAttribute

    @ModelAttribute一个具有如下三个作用:
    ①绑定请求参数到命令对象:放在功能处理方法的入参上时,用于将多个请求参数绑定到一个命令对象,从而简化绑定流程,而且自动暴露为模型数据用于视图页面展示时使用;

    ②暴露表单引用对象为模型数据:放在处理器的一般方法(非功能处理方法)上时,是为表单准备要展示的表单引用对象,如注册时需要选择的所在城市等,而且在执行功能处理方法(@RequestMapping注解的方法)之前,自动添加到模型对象中,用于视图页面展示时使用;

    ③暴露@RequestMapping方法返回值为模型数据:放在功能处理方法的返回值上时,是暴露功能处理方法的返回值为模型数据,用于视图页面展示时使用。

2.5.1 绑定请求参数到命令对象

    如用户登录,我们需要捕获用户登录的请求参数(用户名、密码)并封装为用户对象,此时我们可以使用@ModelAttribute绑定多个请求参数到我们的命令对象。

public String test1(@ModelAttribute("user") UserModel user)

    只是此处多了一个注解@ModelAttribute("user"),它的作用是将该绑定的命令对象以“user”为名称添加到模型对象中供视图页面展示使用。我们此时可以在视图页面使用${user.username}来获取绑定的命令对象的属性。

    绑定请求参数到命令对象支持对象图导航式的绑定,如请求参数包含“?username=zhang&password=123&workInfo.city=bj”自动绑定到user中的workInfo属性的city属性中。

@RequestMapping(value="/model2/{username}")
public String test2(@ModelAttribute("model") DataBinderTestModel model) { 

    URI模板变量也能自动绑定到命令对象中,当你请求的URL中包含“bool=yes&schooInfo.specialty=computer&hobbyList[

2.5.2 暴露表单引用对象为模型数据
@ModelAttribute("cityList")
public List<String> cityList() {
    return Arrays.asList("北京", "山东");
} 

    如上代码会在执行功能处理方法之前执行,并将其自动添加到模型对象中,在功能处理方法中调用Model 入参的containsAttribute("cityList")将会返回true。

@ModelAttribute("user")  //①
public UserModel getUser(@RequestParam(value="username", defaultValue="") String username) {
    //TODO 去数据库根据用户名查找用户对象
    UserModel user = new UserModel();
    user.setRealname("zhang");
     return user;
} 

    如你要修改用户资料时一般需要根据用户的编号/用户名查找用户来进行编辑,此时可以通过如上代码查找要编辑的用户。也可以进行一些默认值的处理。

@RequestMapping(value="/model1") //②
public String test1(@ModelAttribute("user") UserModel user, Model model) 

    此处我们看到①和②有同名的命令对象,那Spring Web MVC内部如何处理的呢:

  • 首先执行@ModelAttribute注解的方法,准备视图展示时所需要的模型数据;@ModelAttribute注解方法形式参数规则和@RequestMapping规则一样,如可以有@RequestParam等;
  • 执行@RequestMapping注解方法,进行模型绑定时首先查找模型数据中是否含有同名对象,如果有直接使用,如果没有通过反射创建一个,因此②处的user将使用①处返回的命令对象。即②处的user等于①处的user。
2.5.3 暴露@RequestMapping方法返回值为模型数据
public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)

    大家可以看到返回值类型是命令对象类型,而且通过@ModelAttribute("user2")注解,此时会暴露返回值到模型数据(名字为user2)中供视图展示使用。那哪个视图应该展示呢?此时Spring Web MVC会根据RequestToViewNameTranslator进行逻辑视图名的翻译。
    此时又有问题了,@RequestMapping注解方法的入参user暴露到模型数据中的名字也是user2,其实我们能猜到:
    @ModelAttribute注解的返回值会覆盖@RequestMapping注解方法中的@ModelAttribute注解的同名命令对象。

2.5.4 匿名绑定命令参数
public String test4(@ModelAttribute UserModel user, Model model)
或
public String test5(UserModel user, Model model) 

    此时我们没有为命令对象提供暴露到模型数据中的名字,此时的名字是什么呢?Spring Web MVC自动将简单类名(首字母小写)作为名字暴露,如“cn.javass.chapter6.model.UserModel”暴露的名字为“userModel”。

public @ModelAttribute List<String> test6()
或
public @ModelAttribute List<UserModel> test7() 

    对于集合类型(Collection接口的实现者们,包括数组),生成的模型对象属性名为“简单类名(首字母小写)”+“List”,如List生成的模型对象属性名为“stringList”,List生成的模型对象属性名为“userModelList”。
    其他情况一律都是使用简单类名(首字母小写)作为模型对象属性名,如Map<String, UserModel>类型的模型对象属性名为“map”。

3.前后台交互

3.1 通过注释ModelAttribute来进行。这部分已经在上文介绍过。

3.2 方法返回参数

    在低版本的 Spring MVC 中,请求处理方法的返回值类型都必须是 ModelAndView。而在 Spring 2.5 中,你拥有多种灵活的选择。通过下表进行说明:

void:

此时逻辑视图名由请求处理方法对应的 URL 确定,如以下的方法:
@RequestMapping("/welcome.do")
public void welcomeHandler() {
}
对应的逻辑视图名为“welcome”

String:

此时逻辑视图名为返回的字符,如以下的方法:
@RequestMapping(method = RequestMethod.GET)
public String setupForm(@RequestParam("ownerId") int ownerId, ModelMap model) {
    Owner owner = this.clinic.loadOwner(ownerId);
    model.addAttribute(owner);
    return "ownerForm";
}
对应的逻辑视图名为“ownerForm”

org.springframework.ui.ModelMap:

和返回类型为 void 一样,逻辑视图名取决于对应请求的 URL,如下面的例子:
@RequestMapping("/vets.do")
public ModelMap vetsHandler() {
    return new ModelMap(this.clinic.getVets());
}
对应的逻辑视图名为“vets”,返回的 ModelMap 将被作为请求对应的模型对象,可以在 JSP 视图页面中访问到。

ModelAndView:

应该说使用 String 作为请求处理方法的返回值类型是比较通用的方法,这样返回的逻辑视图名不会和请求 URL 绑定,具有很大的灵活性,而模型数据又可以通过 ModelMap 控制。当然直接使用传统的 ModelAndView 也不失为一个好的选择。

3.3 按契约绑定URL 参数

Controller 的方法标注了 @RequestMapping 注解后,它就能处理特定的 URL 请求。我们不禁要问:请求处理方法入参是如何绑定 URL 参数的呢?在回答这个问题之前先来看下面的代码:

@RequestMapping(params = "method=listBoardTopic")
//<—— ① topicId入参是如何绑定URL请求参数的?
public String listBoardTopic(int topicId) { 
    bbtForumService.getBoardTopics(topicId);
    System.out.println("call listBoardTopic method.");
    return "listTopic";
}

    当我们发送 http://localhost//bbtForum.do?method=listBoardTopic&topicId=10 的 URL 请求时,Spring 不但让 listBoardTopic() 方法处理这个请求,而且还将 topicId 请求参数在类型转换后绑定到 listBoardTopic() 方法的 topicId 入参上。而 listBoardTopic() 方法的返回类型是 String,它将被解析为逻辑视图的名称。也就是说 Spring 在如何给处理方法入参自动赋值以及如何将处理方法返回值转化为 ModelAndView 中的过程中存在一套潜在的规则,不熟悉这个规则就不可能很好地开发基于注解的请求处理方法,因此了解这个潜在规则无疑成为理解 Spring MVC 框架基于注解功能的核心问题。
    我们不妨从最常见的开始说起:请求处理方法入参的类型可以是 Java 基本数据类型或 String 类型,这时方法入参按参数名匹配的原则绑定到 URL 请求参数,同时还自动完成 String 类型的 URL 请求参数到请求处理方法参数类型的转换。下面给出几个例子:

listBoardTopic(int topicId):和 topicId URL 请求参数绑定;
listBoardTopic(int topicId,String boardName):分别和 topicId、boardName URL 请求参数绑定;

    特别的,如果入参是基本数据类型(如 int、long、float 等),URL 请求参数中一定要有对应的参数,否则将抛出 TypeMismatchException 异常,提示无法将 null 转换为基本数据类型。
    另外,请求处理方法的入参也可以一个 JavaBean,如下面的 User 对象就可以作为一个入参:

User.java:一个 JavaBean
            
package com.baobaotao.web;

    public class User {
    private int userId;
    private String userName;
    //省略get/setter方法
    public String toString(){
        return this.userName +","+this.userId;
    }
}

下面是将 User 作为 listBoardTopic() 请求处理方法的入参:

使用 JavaBean 作为请求处理方法的入参

@RequestMapping(params = "method=listBoardTopic")
public String listBoardTopic(int topicId,User user) {
    bbtForumService.getBoardTopics(topicId);
    System.out.println("topicId:"+topicId);
    System.out.println("user:"+user);
    System.out.println("call listBoardTopic method.");
    return "listTopic";
}

    这时,如果我们使用以下的 URL 请求:http://localhost/bbtForum.do?method=listBoardTopic&topicId=1&userId=10&userName=tom
    topicId URL 参数将绑定到 topicId 入参上,而 userId 和 userName URL 参数将绑定到 user 对象的 userId 和 userName 属性中。和 URL 请求中不允许没有 topicId 参数不同,虽然 User 的 userId 属性的类型是基本数据类型,但如果 URL 中不存在 userId 参数,Spring 也不会报错,此时 user.userId 值为 0。如果 User 对象拥有一个 dept.deptId 的级联属性,那么它将和 dept.deptId URL 参数绑定。

3.3 通过注解指定绑定 URL 参数

    如果我们想改变这种默认的按名称匹配的策略,比如让 listBoardTopic(int topicId,User user) 中的 topicId 绑定到 id 这个 URL 参数,那么可以通过对入参使用 @RequestParam 注解来达到目的:

package com.baobaotao.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/bbtForum.do")
public class BbtForumController {

    @RequestMapping(params = "method=listBoardTopic")
    public String listBoardTopic(@RequestParam("id") int topicId,User user) {
        bbtForumService.getBoardTopics(topicId);
        System.out.println("topicId:"+topicId);
        System.out.println("user:"+user);
        System.out.println("call listBoardTopic method.");
        return "listTopic";
    }
}

    这里,对 listBoardTopic() 请求处理方法的 topicId 入参标注了 @RequestParam("id") 注解,所以它将和 id 的 URL 参数绑定。

3.4 绑定模型对象中某个属性

    Spring 2.0 定义了一个 org.springframework.ui.ModelMap 类,它作为通用的模型数据承载对象,传递数据供视图所用。我们可以在请求处理方法中声明一个 ModelMap 类型的入参,Spring 会将本次请求模型对象引用通过该入参传递进来,这样就可以在请求处理方法内部访问模型对象了。来看下面的例子:

使用 ModelMap 访问请示对应的隐含模型对象
            
@RequestMapping(params = "method=listBoardTopic")
 public String listBoardTopic(@RequestParam("id")int topicId,User user,ModelMap model) {
     bbtForumService.getBoardTopics(topicId);
     System.out.println("topicId:" + topicId);
     System.out.println("user:" + user);
     //① 将user对象以currUser为键放入到model中
     model.addAttribute("currUser",user); 
     return "listTopic";
 }

    对于当次请求所对应的模型对象来说,其所有属性都将存放到 request 的属性列表中。象上面的例子,ModelMap 中的 currUser 属性将放到 request 的属性列表中,所以可以在 JSP 视图页面中通过 request.getAttribute(“currUser”) 或者通过 ${currUser} EL 表达式访问模型对象中的 user 对象。从这个角度上看, ModelMap 相当于是一个向 request 属性列表中添加对象的一条管道,借由 ModelMap 对象的支持,我们可以在一个不依赖 Servlet API 的 Controller 中向 request 中添加属性。
    在默认情况下,ModelMap 中的属性作用域是 request 级别是,也就是说,当本次请求结束后,ModelMap 中的属性将销毁。如果希望在多个请求中共享 ModelMap 中的属性,必须将其属性转存到 session 中,这样 ModelMap 的属性才可以被跨请求访问。