SpringMVC——请求数据传入、响应数据传出和@ModelAttribute注解

一、请求数据传入

1.1 请求处理方法签名

  • Spring MVC 通过分析处理方法的签名,HTTP请求信息绑定到处理方法的相应人参中。
  • Spring MVC 对控制器处理方法签名的限制是很宽松的,几乎可以按喜欢的任何方式对方法进行签名。
  • 必要时可以对方法及方法入参标注相应的注解( @PathVariable 、@RequestParam、@RequestHeader 等)、
  • Spring MVC 框架会将 HTTP 请求的信息绑定到相应的方法入参中,并根据方法的返回值类型做出相应的后续处理。
//访问路径:/handle02?username=zhangsan
//默认方式获取请求参数: 直接给方法入参上写一个和请求参数名相同的变量。这个变量就来接收请求参数的值;没有值则为null
@RequestMapping("/handle02")
public String handle02(String username){
    
    
	System.out.println("这个变量的值:"+username);//这个变量的值:zhangsan
	return "success";
}

1.2 @RequestParam注解

  • 在处理方法入参处使用 @RequestParam可以把请求参数传递给请求方法

  • 三个属性:

      value:参数名
      required:是否必须。默认为 true, 表示请求参数中必须包含对应的参数,若不存在,将抛出异常
      defaultValue: 默认值,当没有传递参数时使用该值
    
/**
 * @RequestParam 注解用于映射请求参数
 * 		value 用于映射请求参数名称
 * 		required 用于设置请求参数是否必须的,默认为 true表示请求参数中必须包含对应的参数,
 * 			若不存在,将抛出异常Required String parameter 'user' is not present
 * 		defaultValue 设置默认值,当没有传递参数时使用该值
 */
@RequestMapping("/handle03") //访问路径:handle03?username=zhangsan
public String handle03(@RequestParam(value="user",required=false,defaultValue="你没带")String username){
    
    
	System.out.println("这个变量的值:"+username);//输出:这个变量的值:你没带
	return "success";
}

1.3 @RequestHeader 注解

  • 请求头包含了若干个属性,服务器可据此获知客户端的信息,通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法的入参中
/**
 * @RequestHeader:获取请求头中某个key的值; 
 * 		以前获取请求头中某个key的值:request.getHeader("User-Agent");
 * @RequestHeader("User-Agent")String userAgent ,如果请求头中没有这个值就会报错; 
 * 等同于:userAgent = request.getHeader("User-Agent")
 *  
 *  同样可以设置value、required、defaultValue参数
 */
@RequestMapping("/handle04")
public String handle04(@RequestHeader(value="user-Agent",required=false)String userAgent){
    
    
	System.out.println("请求头中浏览器的信息:"+userAgent);
	return "success";
}

1.4 @CookieValue注解

  • @CookieValue可让处理方法入参绑定某个 Cookie 值
/**
 * @CookieValue:获取某个cookie的值; 以前的操作获取某个cookie; 
 * Cookie[] cookies = request.getCookies(); 
 * for(Cookie c:cookies){
 * 		if(c.getName().equals("JSESSIONID")){
 *  		String cv = c.getValue(); 
 *  	} 
 * }
 * 同样可以设置value、required、defaultValue参数
 */
@RequestMapping("/handle05")
public String handle05(@CookieValue(value="JSESSIONID",required=false)String jid){
    
    
	System.out.println("cookie中的jid的值:"+jid);
	return "success";
}

1.5 使用POJO作为参数

  • 使用 POJO 对象绑定请求参数值
  • Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值。 支持级联属性。如:dept.deptId、dept.address.tel 等

控制器方法:

/**
 * 如果我们的请求参数是一个POJO;
 * SpringMVC会自动的为这个POJO进行赋值?
 * 1)、将POJO中的每一个属性,从request参数中尝试获取出来,并封装即可;(通过set方法安赋值)
 * 2)、还可以级联封装;属性的属性
 * 3)、请求参数的参数名和对象中的属性名一一对应就行
 */
@RequestMapping("/book")
public String addBook(Book book,String bookName){
    
    
	System.out.println("图书的信息是:"+book);//将对象中属性名跟请求参数的参数名对应的直接赋值
	return "success";
}

实体类:
在这里插入图片描述

form表单:

<form action="book" method="post"><br/>
	书名:<input type="text" name="bookName"/><br/>
	作者:<input type="text" name="author"/><br/>
	价格:<input type="text" name="price"/><br/>
	库存:<input type="text" name="stock"/><br/>
	销量:<input type="text" name="sales"/><br/>
	省:<input type="text" name="address.province"/><br/><!-- 级联属性 -->
	市:<input type="text" name="address.city"/><br/>
	街道:<input type="text" name="address.street"/><br/>
	<input type="submit" value="提交"/>
</form>

1.6 使用Servlet原生API作为参数

MVC 的 Handler 方法可以接受哪些 ServletAPI 类型的参数

HttpServletRequest
HttpServletResponse
HttpSession
java.security.Principal
Locale
InputStream
OutputStream
Reader
Writer
/**
 * SpringMVC可以直接在参数上写原生API;
 * 
 * HttpServletRequest
 * HttpServletResponse
 * HttpSession
 * 
 * java.security.Principal
 * Locale:国际化有关的区域信息对象
 * InputStream:
 * 		ServletInputStream inputStream = request.getInputStream();
 * OutputStream:
 * 		ServletOutputStream outputStream = response.getOutputStream();
 * Reader:
 * 		BufferedReader reader = request.getReader();
 * Writer:
 * 		PrintWriter writer = response.getWriter();
 * 		
 * @throws IOException 
 */
@RequestMapping("/handle06")
public String handle06(HttpSession session,HttpServletRequest req){
    
    
	//可以再请求转发的页面获取request域和session域中的数据
	req.setAttribute("reqParam","我是requset域中的值");
	session.setAttribute("sessionParam", "我是session域中的值");
	return "success";
}

1.7 SpringMVC乱码处理

之前对于中文乱码的处理:

 * 提交的数据可能有乱码:
 * 请求乱码:
 * 		GET请求:改server.xml;在8080端口处URIEncoding="UTF-8"
 * 		POST请求:
 * 			在第一次获取请求参数之前设置
 * 			request.setCharacterEncoding("UTF-8");
 * 			
 * 响应乱码:
 * 		response.setContentType("text/html;charset=utf-8")

而SpringMVC中如果出现中文乱码,需要配置字符编码过滤器,且配置其他过滤器之前。如(HiddenHttpMethodFilter),否则不起作用。

在web.xml中配置:

<!-- 配置一个字符编码的Filter;一定注意:字符编码filter一般都在其他Filter之前; -->
<filter>
	<filter-name>characterEncodingFilter</filter-name>
	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
	<init-param>
		<!-- encoding:指定解决post请求乱码 -->
		<param-name>encoding</param-name>
		<param-value>UTF-8</param-value>
	</init-param>
	<init-param>
		<!-- forceEncoding:解决响应乱码问题 -->
		<param-name>forceEncoding</param-name>
		<param-value>true</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>characterEncodingFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

二、响应数据传出

Spring MVC提供了以下几种途径输出模型数据:

  • ModelAndView —— 控制器的处理方法返回值类型为ModelAndView时,在处理方法中就可以通过该对象添加模型数据,然后输出。
  • Map、Model及ModelMap—— 入参为Model、ModelMap或Map时,处理方法返回时,Map中的数据会自动添加到模型中,然后输出。
  • @SessionAttributes —— 将模型中的某个属性暂存到HttpSession中,以便多个请求之间可以共享这个属性。

2.1 处理模型数据之 Map、Model和ModelMap)

可以在方法处传入Map、或者Model或者ModelMap。给这些参数里面保存的所有数据都会放在请求域中。可以在页面获取

具体使用步骤:

  1. Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器。
  2. 如果方法的入参为 Map 或 Model 类型,Spring MVC 会将隐含模型的引用传递给这些入参。
  3. 在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据

控制器方法:

/**
 * 可以在方法处传入Map、或者Model或者ModelMap。
 * 		给这些参数里面保存的所有数据都会放在请求域中。可以在页面获取
 */
@Controller
public class OutputController {
    
    
	//参数为Map类型
	@RequestMapping("/handle01")
	public String handle01(Map<String,Object> map){
    
    
		//org.springframework.validation.support.BindingAwareModelMap
		System.out.println("map的类型"+map.getClass());
		map.put("msg","message信息");//可以再request域中获取数据
		return "success";
	}
	//参数为Model类型
	@RequestMapping("/handle02")
	public String handle02(Model model){
    
    
		System.out.println("model的类型"+model.getClass());
		model.addAttribute("msg", "message信息");
		return "success";
	}
	//参数为ModelMap类型
	@RequestMapping("/handle03")
	public String handle03(ModelMap modelMap){
    
    
		System.out.println("modelMap的类型"+modelMap.getClass());
		modelMap.addAttribute("msg", "message信息");
		return "success";
	}
	
}

跳转页面可在request域中获取Map、或者Model或者ModelMap中的数据

获取map中的数据:${requestScope.msg }<br/>
  • Map、Model、ModelMap、BindingAwareModelMap之间的关系
    在这里插入图片描述

    推荐:Map, 便于框架移植

2.2 处理模型数据之 ModelAndView

  • 控制器处理方法的返回值如果为 ModelAndView, 则其既包含视图信息,也包含模型数据信息。
  • 添加模型数据:
    MoelAndView addObject(String attributeName, Object attributeValue)
    ModelAndView addAllObject(Map<String, ?> modelMap)
  • 设置视图:
    void setView(View view)
    void setViewName(String viewName)

控制器方法:

/**
 * 方法的返回值可以变为ModelAndView类型;
 * 		既包含视图信息(页面地址)也包含模型数据(给页面带的数据);
 * 		而且数据是放在请求域中;
 */
@RequestMapping("/handle04")
public ModelAndView handle04(){
    
    
	//之前的返回值我们就叫视图名;视图名视图解析器是会帮我们最终拼串得到页面的真实地址;
//		ModelAndView mv = new ModelAndView("success");//可以使用有参构造器传入视图名
	ModelAndView mv = new ModelAndView();
	mv.setViewName("success");
	mv.addObject("msg", "message信息");//实质上存放到request域中
	return mv;
}

2.3 处理模型数据之 SessionAttributes 注解

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

例如:

@SessionAttributes(types=User.class) 会将隐含模型中所有类型为 User.class 的属性添加到会话中。
value={"msg"}:只要保存的是这种key的数据,给Session中放一份
types={String.class}:只要保存的是这种类型的数据,给Session中也放一份
/**
 * SpringMVC提供了一种可以临时给Session域中保存数据的方式;
 * 	使用一个注解	@SessionAttributes(只能标在类上)
 * 例如:
 * 	@SessionAttributes(value="msg"):
 * 		给BindingAwareModelMap中保存的数据,或者ModelAndView中的数据,同时给session中放一份;
 * 		value指定保存数据时要给session中放的数据的key;
 * 
 * 两个属性:
 * 	value={"msg"}:只要保存的是这种key的数据,给Session中放一份
 * 	types={String.class}:只要保存的是这种类型的数据,给Session中也放一份
 * 
 *  后来推荐@SessionAttributes就别用了,可能会引发异常;
 * 			给session中放数据请使用原生API;
 * @return
 */
//@SessionAttributes(value={"msg","key"})//只要保存的key的值是msg和key,就给session中也放一份
@SessionAttributes(types={
    
    String.class})//只要保存的是这种类型的数据,给Session中也放一份
@Controller
public class OutputController {
    
    
	
	@RequestMapping("/handle05")
	public String handle05(Map<String,Object> map){
    
    
		//默认是被存放到request 域,如果设置了@SessionAttribute注解,就同时存放到session域中
		map.put("msg","message信息");
		map.put("key", "字符串类型");
		return "success";
	}
}

三、@ModelAttribute注解(了解)

以下转载自https://blog.csdn.net/sinat_28978689/article/details/65627501

好的博客:
@ModelAttribute注解的使用总结

spring学习之@ModelAttribute运用详解

3.1 问题的引入

假设我们需要修改一个User对象,但是我们规定某一个或者几个字段不能修改,我们会利用表单填写相关信息,然后提交给后台,然后 在后台会new出一个对象,并将表单提交的数据赋值到这个表单的属性中,然后执行update操作,但是由于有些字段不能被修改,所以我们在前台传递的对象有些属性为空,所以在更新的时候,那些不能被修改的字段就会被默认为空值。
在这里插入图片描述

解决:1、我们可以利用隐藏域,在用户进行修改的时候,将对应的不能修改的字段进行隐藏,这样的话,可以解决,但是如果对应的字段的敏感度比较高,例如密码不适合利用隐藏域进行记录,这种方法是不合适的。
2、我们可以在数据更新之前,先从数据库中将用户信息给读取出来,然后将不能修改的字段重新进行赋值然后更新。 但是如果所不能修改的字段比较多,我们就会对这个比较麻烦。重复代码较多。

ModelAttribute注解引出:
对于上述两种方式都有对应的缺点,但是对于更新数据比较小的可以使用,所以我们可以利用我们的这个ModelAttribute注解进行处理。
在这里插入图片描述

注意 :这里的对象不是new出来的,而是从数据库获取出来的,这就是ModelAttribute的作用之一。

在没有利用@ModelAttribute修饰前演示

<!-- POJO类ModelAttributes属性  -->
     private int id;  
     private String name;
     private int age;
     private String email;
     private String password; // 不能被修改


<!--
            模拟修改操作  前台页面
            1.原始数据为 1 tom  12 ff@163.com 12345
            2.密码不能修改
            3.表单回显 ,模拟操作直接在表单填写对应的属性值           
            在用户修改数据时,有些数据不能修改,所以在修改时我们把这些子段不给显示,
            我们应该是从数据库中先获取,然后 利用ModelAttribute注解进行 覆盖,
            会比我们自己手动利用覆盖的效果高
 -->

<form action="TestModelArributes" method="post" >
    <input type="hidden" name="id" value="1">
    name:<input type="text" name="name" value="tom">
    age:<input type="text" name="age" value="12" >
    email:<input type="text" name="eamil" value="[email protected]" >
    <input type="submit" value="submit"> 
</form>

<!-- 后台处理 -->
@RequestMapping("/TestModelArributes")
    public String TestModelArributes(User user){
    
    
        System.out.println("修改:"+user);
        // 执行更新操作 省略 
        return SUCCESS;
    }
<!-- 控制台打印结果 -->
修改:User[id=1, name=tom, age=13, email=ff@163.com, password=null]

注意:在这里我们的原始数据 带有密码,但是从前台到后台处理的时候,这个密码由于没有在表单之中,所以传过来的POJO 的密码为空。 即利用方法一所造成的。

然后我们在后台处理中新增加一个方法,注意是被@ModelAttribute修饰的

    @ModelAttribute
    public void getUser(@RequestParam(value="id",required=false) Integer id,
            Map<String, Object> maps){
    
    
        if(id != null){
    
       // id不为空,说明是用户执行了修改操作
            // 模拟从数据库中获取对象 我们是生成的
        User user =new User(1,"tom", 12,"[email protected]", "1234");
        System.out.println("从数据库中获取一个对象:"+user );
        maps.put("user", user);
        }   
    }

<!-- 控制台打印结果 -->

从数据库中获取一个对象:User[id=1, name=tom, age=12, email=ff@163.com, password=1234]
修改:User[id=1, name=tom, age=13, email=ff@163.com, password=1234]

当被@ModelAttribute修饰一个方法之后,结果就变成了password有值了,而且也应该注意到,被@ModelAttribute修饰的方法给提前执行了,这就是@ModelAttribute的作用。这样的话,再做修改操作的话,就能将原始密码给带进入修改操作了。为什么被@ModelAttribute修饰一个方法之后就会有这样的结果呢?

下面我们抽出主要代码进行分析:

	<!-- 第一种方式 -->
	@ModelAttribute
    public void getUser(@RequestParam(value="id",required=false) Integer id,
            Map<String, Object> maps){
    
    
        if(id != null){
    
    
	        User user =new User(1,"tom", 12,"[email protected]", "1234");
	        maps.put("user", user);
        }   
    }

    @RequestMapping("/TestModelArributes")
    public String TestModelArributes(User user){
    
    
        System.out.println("修改:"+user);
        return SUCCESS;
    }

=============================


    <!--第二种方式 -->
    @ModelAttribute
    public void getUser(@RequestParam(value="id",required=false) Integer id,
            Map<String, Object> maps){
    
    
        if(id != null){
    
    
	        User user=new User(1,"tom", 12, "1234");
	        maps.put("change", user);
	    }
    }


     @RequestMapping("/TestModelArributes")
     public String TestModelArributes(@ModelAttribute( value="change") User user){
    
    
         System.out.println("修改:"+user);
         return SUCCESS;
     }

注意
1、@ModelAttribute修饰的方法会被Springmvc提前执行,运行在每个目标方法之前。
2、在@ModelAttribute修饰的方法中,【map存入的键需要和目标方法入参类型的第一个字母小写的字符串一致】方式一所示,否则的话,在目标的请求参数方法中不能匹配到从数据库中提取出来的数据,或者就是你也将目标方法的参数用利用注解ModelAttribute修饰且设置value属性值指向你的map中的键名,上述代码的第二种方式

3.2 分析流程

运行流程:
我们上述代码可以分为三个步骤:
1、 执行ModelArribute修饰的方法,从数据库中取出对象,把对象放到Map 中,键为user。
2、springmvc 从Map 中取出User 对象 ,并把表单的请求参数赋给该User对象的对应属性。
3、springmvc 把上述对象传入到目标方法的参数。

3.2.1 @ModelAttribute源码分析流程

源码分析流程:对应上面的运行流程。
1、调用@ModelAttribute 注解修饰的方法,实际上把@ModelAttribute方法中 Map中的数据放在了implicitModel中。
2、解析请求处理器的目标参数,实际上该目标参数来自于WebDataBinder对象的target属性。
2.1、创建WebDataBinder对象。
a) 确定objectName属性:若传入的attrName属性值为空”“,则objectName为类名第一个字母小写。需要注意的是:attrName。如果目标方法的POJO参数属性使用了@ModelAttribute来修饰,则attrName值即为@ModelAttribute的value属性值(对应我们的第二种写法)
b)确定target属性:在implicitModel 中查找attrName对应的属性值,如果存在,进行利用。如果不存在,则验证当前Handler是否使用了@SessionAttribute进行修饰类。如果使用了@SessionAttribute,则尝试从HttpSession中获取attrName所对应的值,若Session中没有所对应的值,则会抛出异常。【这也是SessionAttribute与ModelAttribute共同使用时造成的一个常见问题】,如果当前Handler没有使用@SessionAttribute修饰类,且@SessionAttribute中没有使用value值指定的key和attrName相匹配,则通过反射机制进行创建POJO 对象。
2.2、Springmvc把表单的请求参数赋值给WebDataBinder 的target对应的属性。
2.3、Springmvc 会把WebDataBinder的attrName和target给到implicitModel,进而传递到request域对象中。
3、把WebDataBinder的target作为参数传递给目标方法。

3.2.2 SpringMVC 确定目标方法 POJO 类型入参的过程

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

3.3 使用总结

1、注解在没有返回值的方法上面
就如上面的例子注解在没有返回值的方法上面,用在POJO入参上面,或者我们可以在这里设定一些Request域中的对象,在前台可以直接使用

@ModelAttribute
public void getUser(){
    
    
    maps.put("name", "zhangsan");
}

2、注解在有返回值的方法上面
这里要用value属性进行数据存储,会将返回值存储在value属性的值中,可以在前台直接获取。

@ModelAttribute(value="user00")
public User testModelAttribute(){
    
    
    User user =new User(1,"tom00", 12,"[email protected]", "123400");
    return user;
}

3、在方法参数上使用@ModelAttribute
在方法的参数中使用必须是修饰POJO类,这样的话,才能从相应的域中获取数据

@RequestMapping("/TestModelArributes")
public String TestModelArributes(@ModelAttribute( value="change") User user){
    
    
    System.out.println("打印"+user);
    return SUCCESS;
}

这样的话,会从implicitModel 隐藏域中查询此类型对象,就犹如上面分析的传递POJO流程,其实这里的作用只是可以使用change标记我们的user对象。

猜你喜欢

转载自blog.csdn.net/weixin_44630656/article/details/115130041