SpringMVC笔记(二)

使用 @RequestParam 绑定请求参数的值

在处理方法入参处使用@RequestParam 可以把请求参数传递给请求方法
-value : 参数名
-required :
是否必须 默认为 true 表示请求参数中必须包含相应参数, 若不在抛出异常

    /**
     * @RequestParam 来映射请求参数
     * value 值即请求参数的参数名
     * required 该参数是否必须, 默认为 true
     * defaultValue 请求参数的默认值
     */
    @RequestMapping(value="/testRequestParam")
    public String testRequestParam(@RequestParam(value="username") String user,
            @RequestParam(value="age",required=false, defaultValue="0") int age) {
        System.out.println("testRequestParam"+user+"-"+age);
        return SUCCESS;
    }
<a href="springmvc/testRequestParam?username=anqi&age=2">testRequestParam</a>
<br/><br/>

使用 @RequestHeader 绑定请求头的属性值

请求头包含了若干属性, 服务器可据此获知客户端的信息,通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法的入参中

<a href="springmvc/testRequestHeader">testRequestHeader</a>
</br></br>
/**
 * 了解即可
 * 映射请求头
 */
@RequestMapping("/testRequestHeader")
public String testRequestHeader(@RequestHeader(value="Host") String host) {
    System.out.println("testRequestHeader" +"Host:"+host);
    return SUCCESS;
}

@Cookie 可让处理方法入参绑定某个 Cookie 值

/**
 * 了解即可
 * @CookieValue : 映射一个 Cookie 值, 属性同 @RequestParam
 */
@RequestMapping("/testCookieValue")
public String testCookieValue(@CookieValue(value="JSESSIONID") String sessionID) {
    System.out.println("testCookieValue "+sessionID);
    return SUCCESS;
}
<a href="springmvc/testCookieValue">testCookieValue</a>

使用 POJO 对象绑定请求参数的值

SpringMVC 会按请求参数名和 POJO 属性名进行自动匹配,自动位该对象填充属性值,支持级联属性。如 dept.deptid dept.address.tel

/**
 * SpringMVC 会按请求参数名和 POJO 属性名进行自动匹配
 * 自动为该对象填充属性值, 支持级联属性
 * 如 dept.deptid dept.address.tel
 * 
 * 注意 前面的 input name 值要和 User字段名一致,并且顺序要一致
 * 
 */
@RequestMapping("/testPojo")
public String testPojo(User user) {
    System.out.println("testPojo "+user);
    return SUCCESS;
}
<form action="springmvc/testPojo" method="post">
    username : <input type="text" name="username"/><br>
    password : <input type="password" name="password"/><br>
    email : <input type="text" name="email"><br>
    age : <input type="text" name="age"><br>
    city : <input type="text" name="address.city"><br>
    province : <input type="text" name="address.province">
    <input type="submit" value="testPojo"/>
</form>

使用 Servlet 原生API作为参数

/**
 * 可以使用 Servlet 原生的 API 作为目标方法的参数 , 具体支持以下类型
 * 
 * HttpServletRequest
 * HttpServletResponse
 * HttpSession
 * java.security.Principal
 * Locale 
 * InputStream
 * OutputStream
 * Reader
 * Writer
 */
@RequestMapping("/testServletAPI")
public void testServletAPI(HttpServletRequest request,
        HttpServletResponse response, Writer out) throws IOException {
    System.out.println("testServlet API"+ request+response);
    out.write("hello Servlet API");
    /*return SUCCESS;*/
}
<a href="springmvc/testServletAPI">Test ServletAPI</a>

处理模型数据

SpringMVC 提供了以下几种途径输出模型数据

  • ModelAndView:处理方法返回值类型为 ModelAndView 时,方法即可通过该对象添加模型数据。
  • Map 及 Model:入参为 org.springframework.ui.Model、org.springframework.ui.ModelMap 或者 java.util.Map 时,处理方法返回时,Map 中的数据会自动添加到模型中
  • @SessionAttributes:将模型中的某个属性暂存到 HttpSession 中,以便多个请求之间可以共享这个属性
  • @ModelAttribute:方法入参标注该注解后,入参的对象就会方法哦数据模型中。

ModelAndView

  • 控制器方法的返回值如果是 ModelAndView,则其既包含视图信息,也包含模型数据信息。
  • 添加模型数据:
  • 设置视图
    • void setView(View view)
    • void setViewName(String viewName)
/**
 * 目标方法的返回值可以是 ModelAndView 类型
 * 其中可以包含视图和模型信息
 * SpringMVC 会把 ModelAndView 的 model 中数据放入到 request 域对象中 
 */
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
    String viewName = SUCCESS;
    ModelAndView modelAndView = new ModelAndView(viewName);

    //添加模型数据到 ModelAndView 中
    modelAndView.addObject("time", Calendar.getInstance().getTime());

    return modelAndView;
}
<a href="springmvc/testModelAndView">TestModelAndVies</a>

success.jsp

<font color="red">Success</font>
${requestScope.time}

Map 以及 Model

  • Spring MVC 在内部使了一个 org.springframework.ui.Model 接口存储模型数据
  • 具体步骤
    • Spring MVC 在调用方法前会创建一个隐含的模型对作为模型数据存储容器。
    • 如果方法的参为 Map 或 Model 类型。Spring MVC会将隐含模型的引用传递给这些入参。在方法体内,开发者可以通过这个入参对象访问到模型中所有数据,也可以向模型中添加新属性数据
/**
 * 目标方法可以添加 Map 类型(实际上也可以是 Model 类型或者 ModelMap 类型)的参数 
 * 也是将内容放入到了 request 的 域对象中
 */
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
    System.out.println("testMap "+ map.getClass().getName());
    map.put("names", Arrays.asList("Tom", "Angel", "Lichen"));
    return SUCCESS;
}
<a href="springmvc/testMap">Test Map</a>
<font color="red">Success</font>
${requestScope.names }

SessionAttributes

  • 希望在多个请求之间共用某个模型属性数据 则可以在控制器类上标注一个@SessionAttributes, Spring MVC将在模型中对应属性暂存到 HttpSession 中
  • @SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外,还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中

    @SessionAttributes(types=User.class) 会将隐含模型中所有类型为 User.class 属性添加到 话中。
    @SessionAttributes(value={“user1”, “user2”})
    @SessionAttributes(types={User.class, Dept.class})
    @SessionAttributes(value={“user1”, “user2”},types={Dept.class})

@SessionAttributes(value= {"user"},types={String.class})
@RequestMapping("/springmvc")
@Controller()
public class TestRequestMapping {
    private static final String SUCCESS = "success";
    /**
     * @SessionAttributes 除了可以通过属性名指定需要放到会话中的属性外(实际上使用的是 value 值)
     * 也可以通过模型属性的对象类型来指定哪些模型属性需要被放到会话中(实际上使用的是 types 属性值) 
     * 
     * 注意:该注解只能放在类的上面
     */
    @RequestMapping("/testSessionAttributes")
    public String testSessionAttributes(Map<String,Object> map) {
        User user = new User("anqi","5161","[email protected]",19);
        map.put("user", user);
        map.put("school", "千峰");
        return SUCCESS;
    }
}
    request->user:${requestScope.user }
    <br>
    session->user:${sessionScope.user}
    <br>
    request->school:${requestScope.user }
    <br>
    session->school:${sessionScope.user}
    <br>

运行结果

Success 
request->user:User [username=anqi, password=5161, [email protected], age=19, address=null] 
session->user:User [username=anqi, password=5161, [email protected], age=19, address=null] 
request->school:User [username=anqi, password=5161, [email protected], age=19, address=null] 
session->school:User [username=anqi, password=5161, [email protected], age=19, address=null] 

ModelAttribute

使用场景:在控制器方法获取用户表单传来的POJO,并且根据POJO修改数据库时,会出现有些字段数据没有出现在实参的POJO中,但是如果直接修改会导致那些没有赋值的字段变为null,所以需要引入@ModelAttribute。

@ModelAttribute思想是把用户表单数据封装成POJO对象前,从数据库取出对应记录并封装成POJO对象,然后根据表单数据修改这个POJO对象,那么那些没有数据的字段就仍然会是原先数据库中的数据。

所以,需要以下步骤

  1. 在得到用户表单数据后,但是在封装成POJO对象前,从数据库中取出数据并封装。
  2. 把封装好的对象交给SpringMVC
  3. SpringMVC根据表单数据修改上一步传入的POJO对象
  4. SpringMVC把修改后的对象作为控制器中具体用来修改数据库的方法的实参传入。
  5. 执行数据库修改操作

顺便整理没有使用@ModelAttribute之前的步骤

  1. SpringMVC创建一个新的 POJO 对象,然后根据表单传入的数据修改对象数据
  2. SpringMVC把修改后的对象作为控制器中具体用来修改数据库的方法的实参传入。
  3. 执行数据库修改操作

所以,使用@ModelAttribute后,增加了1、2两步,并且把第三步中原来应新创建的对象改为从数据库中取出并封装好的对象。

具体实现其实添加1、2两步工作就好,第三步的差别会由SpringMVC自动识别并进行改变。

1、2两步也可以合并为一步,具体为编写一个方法,在其中取出数据库数据并封装,然后把它添加到ModelMap中。而这个方法只需要添加@ModelAttribute注解,SpringMVC将会自动在得到用户表单数据后,但是在封装成POJO对象前调用。以下为一个例子:

需要把类前的该注解注释掉

//@SessionAttributes(value= {"user"},types={String.class})


Spring MVC 确定目标方法 POJO 类型入参的过程
1. 确定一个 Key :
1). 若目标方法的 POJO 类型的参数木有使用 @ModelAttribute 作为修饰, 则 key 为 POJO 类名第一个字母小写
2). 若使用了 @ModelAttribute 来修饰,则 key 为 @ModelAttribute 注解的 value 属性值
2. 在 implicitModel 里查找 key 对应的对象, 若存在, 则作为入参传入
1). 若在 @ModelAttribute 标记的方法中在 Mao 中保存过, 且 key 和 1 确定的 key 一致, 则会获取到
3. 若 implicitModel 中不存在 key 对应的对象, 则检查当前的 Handler 是否使用 @SessionAttributes 注解修饰, 若使用了该注解, 且 @SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所对应的 value 值, 若存在则直接传入到目标方法的入参中, 若不存在则抛出异常。
4. 若 Handler 没有标识 @SessionAttributes 注解或 SessionAttributes 注解的 value 值中不包含 key, 则 会通过反射来创建 POJO 类型的参数, 传入为目标方法的参数
5. Spring MVC 会把 key 和 POJO 类型的对象保存到 implicitModel 中, 进而会保存到 request 中

/**
 * 有 @ModelAttribute 标记的方法, 会在每个目标方法执行之前被 Spring MVC 调用
 */
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id,
        Map<String, Object> map) {
    if(id != null) {
        //模拟从数据库获取某个对象
        User user = new User(id, "Tom", "123456", "[email protected]", 5);
        System.out.println("从数据库中获取一个对象 " + user);

        map.put("user", user);
    }
}

/**
 * 运行流程 :
 * 1. 执行 @ModelAttribute 注解修饰的方法 : 从数据库中取出对象. 把对象放入到了 Map 中,键为 user
 * 2. SpringMVC 从 Map 中取出 User 对象, 并把表单的请求参数赋给该 User 对象的对应属性
 * 3. SpringMVC 把上述对象传入目标方法的参数
 * 
 * 注意 : 在 @ModelAttribute 修饰的方法中, 放入到 Map 时的键需要和目标方法入参类型的第一个字母小写一致!
 */
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user) {
    System.out.println("修改:"+user);
    return SUCCESS;
}
//修改:User [username=1, password=null, [email protected], age=23]

//加上 @ModelAttribute 后的效果
//从数据库中获取一个对象 User [username=Tom, password=123456, [email protected], age=5]
//修改:User [username=Tom, password=123456, [email protected], age=23]
<!-- 
    模拟修改操作
    1. 原始数据为 :1, Tom, 123456, [email protected], 23
    2. 密码不能被修改
    3. 表单回显, 模拟(数据库查找)操作 直接在表单填写对应的属性值
 -->
 <form action="springmvc/testModelAttribute" method="post">
    <input type="hidden" name="id" value="1">
    username : <input type="text" name="username" value="Tom">
    <br>
    email : <input type="text" name="email" value="[email protected]">
    <br>
    age : <input type="text" name="age" value="23">
    <input type="submit" name="Submit">
 </form>

当然这里还有一些问题要处理:

  1. 添加了@ModelAttribute注解的方法将在所有控制器方法执行之前执行,所以必须要判断具体情况并执行我们想要的代码。上述例子中是通过判断是否存在id,这个id是通过表单传入的,而其他方法不会传入该属性。
  2. 在map中放入放入的键值对的键默认为POJO类名并把首字母改为小写。如果需要自定义该键名,则需要在控制器方法的POJO类型形参前加入@ModelAttribute并且把value属性改为需要自定义的键名。如:@ModelAttribute(value=“myuser”)User user
  3. 如果在ModelMap中不存在对应的键值对,那么SpringMVC会判断当前的控制器类是否添加了SessionAttribute注解,如果添加了且value属性中有对应的键名(在上述例子中就是user),但是实际的session中找不到该键值对,那么SpringMVC将会抛出异常,例如:org.springframework.web.HttpSessionRequiredException: Session attribute ‘user’ required - not found in session
  4. 如果在ModelMap中不存在对应的键值对,或当前控制器类没有添加SessionAttribute注解,或添加了SessionAttribute注解但是value属性没有对应的键名,SpringMVC将会通过反射机制创建一个新的POJO对象传入对应的方法中。
  5. 如果在同一控制器中定义了多个有@ModelAttribute注解的方法,则按在类中定义的顺序反序执行。

ModelAttribute 来修饰 POJO 类型对象的入参

/**
 * 1. 有 @ModelAttribute 标记的方法, 会在每个目标方法执行之前被 Spring MVC 调用
 * 2. @ModelAttribute 注解也可以来修饰目标方法 POJO 类型的入参, 其 value 属性值有以下作用:
 *   1). Spring MVC 会使用 value 属性值在 implicitModel 中查找对应的对象, 若存在则会直接
 *        传入到目标方法的入参中去
 *   2). Spring MVC会以 value 为 key, POJO 类型的对象为 value, 存入到 request 中
 */
@ModelAttribute
public void getUser(@RequestParam(value="id",required=false) Integer id,
        Map<String, Object> map) {
    if(id != null) {
        //模拟从数据库获取某个对象
        User user = new User(id, "Tom", "123456", "[email protected]", 5);
        System.out.println("从数据库中获取一个对象 " + user);

        map.put("userA", user);
    }
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute("userA") User user) {
    System.out.println("修改:"+user);
    return SUCCESS;
}
request->userA:${requestScope.userA}
request->userA:User [username=Tom, password=123456, [email protected], age=23] 

SessionAttributes 注解引发的异常

异常:Session attribute ‘user’ required - not found in session
原因:
@SessionAttributes 注解的 value 属性值中包含了 key, 则会从 HttpSession 中来获取 key 所对应的 value 值, 若存在则直接传入到目标方法的入参中, 若不存在则抛出异常。
解决方法:

  1. 将入参中的 POJO 对象标注 @ModelAttribute(“…”)里面内容不要填 SessionAttributes 包含的 value 值
  2. 使用 @ModelAttribute 标识一个方法, 并把一个POJO 类型对象放入 map

猜你喜欢

转载自blog.csdn.net/baidu_37181928/article/details/80275369