SpringMVC之Controller和参数绑定
在上一篇Spring+SpringMVC+Mybatis整合中说到了SSM的整合,并且在其中添加了一个简单的查询功能,目的只是将整个整合的流程进行一个梳理,下面在上一篇中工程的基础上再说一些关于SpringMVC的Controller的一些细节。
首先附上整个项目结构图,附上整个代码工程的下载地址,下面所讲到的测试用例都是在下面这个测试项目的基础上进行的。
一、关于Controller的注解形式
1、使用@Controller注解可以实现Controller的注解开发,然后在springmvc.xml的配置文件中配置注解扫描器,就可以使用注解形式进行Controller的开发,下面我们简单使用一个helloworld的例子进行说明
①在springmvc.xml中配置注解扫描器
其中也当然包含springmvc所需要的处理器映射器、处理器适配器、视图解析器(这几个组件个概念可以查看SpringMvc入门,其中开篇说到了SpringMVC的处理流程和各个组件以及之间的关系),我们这里直接使用下面的配置方式进行配置
②写一个简单的helloworld,在浏览器中请求对应的Controller,然后输出在页面上
1 package cn.test.ssm.controller; 2 3 import org.springframework.stereotype.Controller; 4 import org.springframework.web.bind.annotation.RequestMapping; 5 import org.springframework.web.servlet.ModelAndView; 6 7 @Controller 8 public class HelloWorldController { 9 10 @RequestMapping("/helloWorld.do") 11 public ModelAndView helloWorld() throws Exception{ 12 ModelAndView modelAndView = new ModelAndView(); 13 modelAndView.addObject("test","HelloSSM"); 14 modelAndView.setViewName("/WEB-INF/items/hello.jsp"); 15 return modelAndView; 16 } 17 }
1 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 2 <html> 3 <head> 4 <title>$Title$</title> 5 </head> 6 <body> 7 测试Controller 8 ${test} 9 </body> 10 </html>
③然后在地址栏中请求http://localhost:8080/TestSSM2/helloWorld.do,输出
二、关于RequestMapping
1、使用不同的处理器映射规则
a、我们通过RequestMapping 可以使用不同的处理器映射规则,RequestMapping注解能够控制http请求的路径和方式(get、post......),在同一个Controller中可以写不同的映射方法,映射浏览器不同的请求业务。
具体的使用方式就是:@RequestMapping(value="/test.do")或@RequestMapping("/test),其中value的值是数组,可以将多个url映射到同一个方法
b、下面我们就在上一篇中查询列表的基础上增加查询详细信息的一个功能,通过RequestMapping注解来实现
①首先在mapper中将ProductDemo.xml中添加查询详细信息的Sql配置
<select id="queryProductInfo" parameterType="java.lang.Integer" resultType="cn.test.ssm.po.ProductExtend"> SELECT pname,shop_price FROM product WHERE pid = #{id} </select>
②在mapper接口中添加上面的方法
③在service接口中添加相应的方法和方法实现
service接口
接口实现类
④在controller层中加上queryInfo方法,其中使用RequestMapping映射了两个不同请求对应的方法实现
1 package cn.test.ssm.controller; 2 3 import cn.test.ssm.po.ProductExtend; 4 import cn.test.ssm.service.ProductService; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.stereotype.Controller; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.servlet.ModelAndView; 9 10 import java.util.List; 11 12 @Controller 13 public class ProductController { 14 15 @Autowired 16 private ProductService productService; 17 18 @RequestMapping("/queryList.do") 19 public ModelAndView queryList() throws Exception{ 20 //从service层调用方法 21 List<ProductExtend> productExtendList = productService.findProductListByName(null); 22 //返回ModelandView 23 ModelAndView modelAndView = new ModelAndView(); 24 modelAndView.addObject(productExtendList); 25 modelAndView.setViewName("/WEB-INF/items/itemsList.jsp"); 26 return modelAndView; 27 } 28 29 @RequestMapping("/queryInfo.do") 30 public ModelAndView queryInfo() throws Exception { 31 32 ProductExtend productExtend = productService.queryProductInfo(1); 33 productExtend.setDesc("这是相机"); 34 ModelAndView modelAndView = new ModelAndView(); 35 modelAndView.addObject(productExtend); 36 modelAndView.setViewName("/WEB-INF/items/editItem.jsp"); 37 return modelAndView; 38 } 39 }
⑤最后在查询列表中点击查询即可查看详细信息
2、窄化请求映射
a)为了实现不同模块之间的开发,我们可以进行这样的使用:在class上添加@RequestMapping(url)指定通用请求前缀, 限制此类下的所有方法请求url必须以请求前缀开头,通过此方法对url进行分类管理。
b)如下:@RequestMapping放在类名上边,设置请求前缀
@Controller
@RequestMapping("/test")
然后在方法名上边设置请求映射url:
@RequestMapping("/queryItem ")
访问地址为:http://localhost:8080/TestSSM2/test/queryList.do
3、关于http请求方式限定
a)限定POST方法:@RequestMapping(method = RequestMethod.POST)
如果通过Get访问则报错:HTTP Status 405 - Request method 'GET' not supported,例如
然后访问http://localhost:8080/TestSSM2/test/queryList.do,就会是下面的错误
b)限定GET方法:@RequestMapping(method = RequestMethod.GET)
如果通过Post访问则报错:HTTP Status 405 - Request method 'POST' not supported
c)GET和POST都可以:@RequestMapping(method={RequestMethod.GET,RequestMethod.POST})
三、关于Controller的返回值问题
1、返回ModelAndView
a)我们上面编写Controller都是以这种方式进行的,大概就是定义一个ModelAndView对象,然后填充模型(从数据库中得到的数据)和逻辑视图(指定的jsp等路径),并返回即可
b)例如
2、使用void类型
a)在controller方法形参上可以定义request和response,使用request或response指定响应结果:
①使用request转向页面:request.getRequestDispatcher("页面路径").forward(request, response);
例如:
@RequestMapping("/test_void.do") public void testVoid(HttpServletRequest request, HttpServletResponse response) throws Exception{ request.setAttribute("test","返回值为void类型的测试"); request.getRequestDispatcher("/WEB-INF/items/hello.jsp").forward(request,response); }
然后输出结果
②通过response页面重定向:response.sendRedirect("url"),实现方式同上
3、使用String作为返回值
a)Controller中的方法形参为model,然后通过形参将数据返回到请求页面上,最后返回字符串可以指定逻辑视图名(路径信息),通过视图解析器解析为物理视图地址;
1 @RequestMapping("/testString.do") 2 public String testString(Model model) throws Exception { 3 //其他进行的操作 4 //通过形参model将数据返回到请求页面上 类似于返回ModelAndView中的addObject方法 5 model.addAttribute("testString","testString"); 6 //然后返回逻辑视图名,经过视图解析器解析为相应的jsp等路径 7 return "test/helloWorld"; 8 }
b)重定向:Contrller方法返回结果重定向到一个url地址,但是由于重定向之后原来的request中的数据不在,所以如果要传参数可以/item/queryItem.action后边加参数:/test/queryTest?test1Key=test1Value&test2Key=test2Value
c)转发:Controller中的方法执行后继续执行另一个controller方法,如下信息modify提交后转向到信息显示页面,修改信息的id参数可以带到修改方法中。//结果转发到update.action,request可以带过去:return "forward:update.action";forward方式相当于“request.getRequestDispatcher().forward(request,response)”,转发后浏览器地址栏还是原来的地址。转发并没有执行新的request和response,而是和转发前的请求共用一个request和response。所以转发前请求的参数在转发后仍然可以读取到。
四、关于SpringMVC的参数绑定问题
1、参数绑定过程
a)参数绑定:注解适配器对RequestMapping标记的方法进行适配,将从浏览器中请求的数据(key/value或者表单信息)在方法中的形参会进行参数绑定,所以在springmvc中的参数绑定是通过Controller的方法形参进行绑定的。
b)参数绑定所支持的默认参数类型,可以直接在Controller方法上面定义下面类型的形参,然后在方法体内直接使用
①HttpServletRequest(通过request对象获取请求信息)
②HttpServletResponse(通过response处理响应信息)
③HTTPSession(通过session对象得到session中存放的对象)
④Model(通过model向页面传递数据,然后页面通过${test.XXXX}获取item对象的属性值。如同上面Controller中方法返回值为String的情况)
2、RequestParam注解使用
a)在没有使用注解的时候,我们在前端提交参数的key名字应该Controller中方法的形参相同,否则在request域中无法进行匹配。当我们需要将Controller方法中的形参设置为不一样的参数名时候,就需要使用这个注解
b)注解简介:@RequestParam用于绑定单个请求参数。
①value:参数名字,即入参的请求参数名字,如value=“test_id”表示请求的参数区中的名字为test_id的参数的值将传入;
②required:是否必须传入参数,默认是true,表示请求中一定要有相应的参数,否则将报HTTP Status 400 - Required Integer parameter 'XXXX' is not present
③defaultValue:默认值,表示如果请求中没有同名参数时的默认值
比如:形参名称为id,但是这里使用value=" test_id"限定请求的参数名为test_id,所以页面传递参数的名必须为test_id。
注意:如果请求参数中没有test_id将抛出异常:HTTP Status 500 - Required Integer parameter 'test_id' is not present
这里通过required=true限定itest_id参数为必需传递,如果不传递则报400错误,可以使用defaultvalue设置默认值,即使required=true也可以不传item_id参数值
c)下面使用例子来进行说明上面的几点内容
①测试注解
测试工程如同上一篇SSM整合中搭建的工程,其中只有一个功能就是查询列表。然后我们在本篇最开始的时候介绍RequestMapping时候添加了查询详细信息的功能,但是其中我们没有接收前端传入的数据,全部都是用的默认值1,下面来将这个方法使用RequestParam进行改写,接收前端传入的参数进行查询。
1 @RequestMapping("/queryInfo.do") 2 public ModelAndView queryInfo(@RequestParam(value = "id") Integer testId) throws Exception { 3 //在没有使用注解的时候,方法形参中的参数名需要和前端请求的key名称一样,使用之后就可以自定义 4 ProductExtend productExtend = productService.queryProductInfo(testId); 5 6 productExtend.setDesc("这是相机"); 7 ModelAndView modelAndView = new ModelAndView(); 8 modelAndView.addObject(productExtend); 9 modelAndView.setViewName("/WEB-INF/items/editItem.jsp"); 10 return modelAndView; 11 }
然后我们再次进行测试,首先访问http://localhost:8080/TestSSM2/test/queryList.do,然后查看id=2的信息
得到下面的结果
在对比id=1的时候
②测试required
在上面的方法中加上
然后进行测试,输入http://localhost:8080/TestSSM2/test/queryInfo.do,不加参数,则报出下面的错误
③测试defaultValue
当required设置为true的时候,如果没有传入key/value,当在Controller中设置defaultValue的时候也不会报出上面的异常,在Controller方法参数中改成下面这样
然后直接输入http://localhost:8080/TestSSM2/test/queryInfo.do不加id参数,还是能够查询到默认的id=1的数据
3、普通POJO类型绑定
a)将pojo对象中的属性名和传递进来的属性名对应,如果传进来的参数名称和对象中的属性名称一致则将参数值设置在pojo对象中 ,然后在Contrller方法定义: 请求的参数名称和pojo的属性名称一致,会自动将请求参数赋值给pojo的属性。
b)我们现在在Controller中新添加一个下面的方法,输出从页面上面提交的数据
c)在页面表单中点击提交数据
然后后台中输出
4、自定义参数绑定
a)定义一个Date类型的参数绑定,首先建议里个转换器的java工具类,用来将String转换为java.util.Date类型
1 package cn.test.ssm.controller.converter; 2 3 4 import org.springframework.core.convert.converter.Converter; 5 6 import java.text.ParseException; 7 import java.text.SimpleDateFormat; 8 import java.util.Date; 9 10 public class StringToDateConverter implements Converter<String, Date> { 11 @Override 12 public Date convert(String s) { 13 14 try { 15 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 16 return simpleDateFormat.parse(s); 17 } catch (ParseException e) { 18 e.printStackTrace(); 19 } 20 return null; 21 } 22 }
然后在springmvc.xml中配置上上面的转换器
1 <!--配置mvc:annotation代替基于注解方式的处理器映射器和适配器的配置--> 2 <mvc:annotation-driven conversion-service="converterService"></mvc:annotation-driven> 3 4 <bean id="converterService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> 5 <!--配置转换器--> 6 <property name="converters"> 7 <list> 8 <bean class="cn.test.ssm.controller.converter.StringToDateConverter"></bean> 9 </list> 10 </property> 11 </bean>
最后可以修改一下上面的查看信息方法,在后台中打印出所有提交的数据,注意下面的方法中形参createtime的类型已经设置为Date类型,在前端页面中也添加上了createtime的输入
1 @RequestMapping("/printInfo.action") 2 public String printInfo(Product product, Date createtime) throws Exception { 3 System.out.println("输出信息"+product); 4 System.out.println(createtime); 5 return "forward:queryList.do"; 6 }
最后在浏览器中进行如下输入测试,输入一个日期类型
最后在后台查看打印的信息
5、自定义包装类型
a)当前天传入的参数比较复杂的时候(比如说涵盖不同数据实体类之间关联的查询或者某个实体类的扩展属性信息),这个时候我们可以在扩展类中加上额外的属性然后将其作为我们自定义的包装类的属性,然后将前台页面传入参数的name设置为包装类的属性名.实体类属性(testExtend.name)
b)看下面的例子,这是一个模糊查询的简单功能实现
①我们首先在实体类Product的扩展类ProductExtend中添加接受模糊查询参数的一个属性
②然后定义一个包装类型,其中将上面的扩展类型设置为一个属性
③然后就在Controller方法中将自定义的包装类型设置为方法形参,用以进行参数绑定,并且调用service的方法进行查询
④在前台中查看查询
⑤在debug模式中查看是否接收到前端传入的参数,发现可以接收到页面传入的参数
并且在调用service方法之后的查询结果也为
最终页面显示结果