本次对Spring MVC中常见的注解做了注释,同时也对数据检验进行进阶。
总体上Spring MVC大头也基本结束了,后面也大多针对MaBatis进行的事务管理与缓存机制,当然抽时间也会对其原理进行剖析。
下一个阶段将会继续深化Spring Boot与Spring Cloud,当然了有时候也会抽空看看Android的。
目录
请求映射注解
@Controller
在Spring MVC中,控制器Controller负责处理有DispatcherServlet分发的请求,把用户请求的数据经过业务处理层处理后封装成Model,然后再把Model返回对应的view视图层进行展示。Spring MVC提供了Controller方法,无需继承特定的类或者接口,只需要@Controller,然后使用@RequestMapping和@RequestParam等一些注解用以定义URL请求和Controller方法之间的映射。
下面来看一个实例:
@Controller
@RequestMapping(value = "/user")
public class AyUserController {
@GetMapping("/findAll")
public String findAll(Model model) {
List<AyUser> ayUserList = ayUserService.findAll();
for(AyUser ayUser: ayUserList) {
System.out.println("id: A"+ayUser.getId());
System.out.println("name: "+ayUser.getName());
}
return "hello";
}
}
上述代码定义一个AyUserController控制层,使用@Controller注解进行表示,使用@GetMapping 注解映射一个请求,为了保证Spring 能找到控制层,需要进行额外配置(之前已经配过了)在applicationContext.xml:
<context:component-scan base-package="com.ay"/>
当然了,如果更加精确点:
<context:component-scan base-package="com.ay。controller"/>
component-scan 功能是启动包扫描功能,使加有@Controller,@Service,@Respository,@Component 等注解能成为bean,base-package属性指定了需要扫描类包。
当然了,需要了在web.xml配置Spring MVC前端控制器DispatcherServlet,以及在spring-mvc配置InternalResourceViewResolver 视图解析器。
web.xml:
<!--配置DispatcherServlet -->
<servlet>
<servlet-name>spring-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置SpringMVC需要加载的配置文件 spring-mvc.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-dispatcher</servlet-name>
<!--默认匹配所有的请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
sping-mvc.xml:
<!--配置JSP 显示ViewResolver(视图解析器)-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
@RequestMapping
这个是最常用的注解之一,这个注解会将HTTP请求映射到MVC和RES控制器的处理方法上。其可以在控制类的级别或方法的级别上使用,在类级别上会将一个特定的请求或者请求模式映射到一个控制器上,之后可以另外添加别的注解来进一步指定映射关系。例如上述代码。
除了value的属性外:
例如:
@RequestMapping(
value = {
"",
"/page",
"page*",
"view/*,**/msg"
}
)
public String hello(Model model){}
可见,可以将多个请求映射到一个方法上,只需要添加一个带有请求路径值列表的@RequestMapping即可。
同时可以处理HTTP请求的方法,比如GET,PUT,POST,DELETE以及PATCH,所有的请求默认是HTTP GET,为了将一个请求映射到一个特定的HTTP方法,需要使用method属性声明:
@RequestMapping(method = RequestMethod.GET)
@GetMapping是一个组合注解,是 ·@RequestMapping(method = RequestMethod.GET)的缩写。将HTTP GET请求映射到特定的处理方法。
Model和ModelMap
Spring MVC内部使用一个org.springframework.ui.Model接口存储数据模型,功能类似于Map.
在调用方法前会创建一个隐含的数据模型,作为模型数据的存储容器,处理方法入参为Map或者Model类型,Spring MVC会将隐含模型的引用传递给Map或者Model,可以向模型中添加新的属性数据。
@ModelAttribute
public void redirect(Model model) {
model.addAttribute("name","ay");
}
@RequestMapping("hello")
public String hello(Model model,ModelMap modelMap,Map map) {
return "hello";
}
浏览器输入请求URL:80/hello,由于redirect方法添加@ModelAttribute注解,故redirect方法优先执行。在hello方法中,三个参数对象都可以获取到name属性值,属于同一对象。
ModelAndView
当控制器处理完请求时,通常会将包含视图信息和模型数据信息的ModelAndView对象返回,这样Spring MVC将使用包含的视图对模型数据进行渲染,具体如下:
@RequestMapping("hello")
public ModelAndView hello() {
ModelAndView mv = new ModelAndView();
mv.addObject("name","ay");
mv.setViewName("hello");
return mv;
//或者不用返回,而是直接model.addAttribute("name","ay");
}
请求方法
如果要访问HttpServletRequest对象,可以将其添加到方法中作为参数,Spring 会将对象正确的传递:
@RequestMapping("hello")
pubilc ModelAndView hello(HttpMethod method) {
ModelAndView mv = new ModelAndView();
return mv;
}
一般来说,请求方法可返回的参数可以有以下几种:
参数绑定注解
@RequestParam
用于将制定的请求参数赋值给方法中的形参。
实例如下:
@RequestMapping("findByID")
public String findByID(@RequestParam(value="id") String id) {
AyUSer ay = ayUserService.findByID(id);
return "hello";
}
当请求80/findByID?id=1,请求的id会将值赋给id变量。该注解可以使用required属性指定参数是否必须传值,如果参数没有接收到任何值,可以使用defaultValue 指定参数的默认值。
@RequestMapping("findPassword")
public String findPassword(@RequestParam(value="name") String name,@RequestParam(value="password",required=false,dafaultValue="123")String password){
。。。
}
传参的时候会赋值,例如输入80/findPassword?name=ay,name参数被赋值为ay,password没有传参故默认值为123.
@PathVariable
此注解可以将URL中动态参数绑定到控制器处理方法的入参中。@PathVarible注解只有一个value属性,String类型。
@RequestMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable Long ownerId, @PathVarible Long petId){
// 。。。
return "";
}
如输入URL:80/owners/123/pets/456,则自动将参数{ownerId}和{petId}值 123和456绑定到@PathVariable同名参数上。当然了动态参数除了可以绑定在方法上也可以绑定在类上面。
@ModelAttribute
此注解是将请求参数绑定到Model对象上。只有一个value属性,类型String,表示绑定的属性名称。当Controller类中有任意一个方法被@ModelAttribute注解标记,页面只要进入这个控制器,不管请求哪个方法,均会执行@ModelAttribute标记的方法,所以用它做一些初始化操作。
@ModelAttribute
public void init() {
System.out.println("innit...");
}
当注解的方法无返回值:
@ModelAttribute
public void init(Model model){
AyUser ayUser = new AyUser();
ayUser.setId(1);
ayUser.setName("ay");
model.addAttribute("user",ayUser);
}
在init方法无返回值,但调用了Model对象,可以在前端页面hello.jsp中:
<%@page isELIgnored="false" %>
<body>
hello, ${user.name}
</body>
当url输入80/hello的时候:会显示:hello,ay。因为model模型数据的作用域与request相同
当方法有返回值:
@RequestMapping("/test")
类
@ModelAttribute("name")
@RequestMapping(value = "/hello")
public String hello() {
//return "hello";
return "ay";
}
jsp文件:
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Hello World</title>
</head>
<body>
hello,${name}
</body>
</html>
不过我运行的时候,需要将hello.jsp文件放在views/test/hello.jsp中,否则会找不到,此时就能得到name的值。
上述代码类似于model.addAttribute(“name”,name);
public String hello(Model model) {
//return "hello";
model.addAttribute("name","akk");
return "hello";
}
使用其注解方法的参数,传入对象.
@ModelAttribute("ayUser")
public AyUser init(@RequestParam("id")Integer id,@RequestParam("name") String name) {
AyUser ayUser = new AyUser();
ayUser.setId(id);
ayUser.setName(name);
return ayUser;
}
@RequestMapping(value = "/helloc")
public String hello(@ModelAttribute("ayUser") AyUser ayUser) {
return "hello";
}
jsp文件:
<body>
hello,${ayUser.name}
</body>
输入80/helloc?id=1&name=ay,会优先执行Init方法,把id和name的值赋给init绑定的参数,同时构造AyUser对象返回。
@SessionAttribute
@ModelAttribute 注解作用在方法或者方法的参数上,表示将注解的方法返回值等加入到Model中,Spring框架会将 Model 传递给前端。Model 的生命周期只存在于HTTP请求的处理过程中,请求完成后,Model就销毁了。如果想让参数在多个请求共享,那么就需要 @SessionAttribute,注意这个只能声明在类上面,不能在方法上。
@Controller
@SessionAttributes("ayUser")
@RequestMapping("/test")
public class AyTestController {
@RequestMapping("/redirect")
public String redirectTest(Model model) {
AyUser ayUser = new AyUser();
ayUser.setName("ay");
model.addAttribute("ayUser",ayUser);
return "redirect:hello";
}
@RequestMapping("/hello")
public String testhel(ModelMap modelMap) {
AyUser ayUser = (AyUser) modelMap.get("ayUser");
System.out.println(ayUser.getName());
return "hello";
}
}
浏览器输入80/test/redirect时候,由于在该类添加了@SessionAttribute,方法redirectTest执行过程中,将AyUser对象存放到Model对象的同时,也会把对象放到HttpSession作用域中。执行完后悔重定向hello方法,在testhel方法中,HttpSession对象会将@SessionAttribute注解的属性写入到新的Model中,可以通过ModelMap获取的AyUser打印信息。
@ResponseBody和@RequestBody
@ResponseBody注解用于将Controller方法返回的对象,通过HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。可以直接写入HTTP响应正文,一般在异步获取数据时使用。在使用RequestMapping,返回值通常被解析为跳转路径,在加上@Responsebody 后返回结果就不会解析为跳转路径,而是直接写入到HTTP正文中。
在使用这个之前,需要引入Jackson相关依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
Spring MVC主要利用类型转换器messageConverters将前台信息转换为开发者需要的格式,在controller方法接收参数前添加此注解进行数据转换:
返回普通字符串:
@RequestMapping("/view")
@ResponseBody
public String testview() {
return "View Test";
}
此时80/view将会出现View Test.注意返回的不是视图,而是将字符串写入HTTP正文中。
返回集合对象:
@RequestMapping("/telist")
@ResponseBody
public List<String> testlist() {
List<String> list = new ArrayList<>();
list.add("Ay");
list.add("al");
return list;
}
此时输入相应的地址,会出现集合。
@RequestBody注解用于读取Request请求的body部分数据,使用默认配置HttpMessageConverter进行解析,然后把相应的数据绑定到Controller参数上。
@RequestMapping("/tereq")
@ResponseBody
public void tereq(@RequestBody AyUser ayUser) {
System.out.println(ayUser.getName());
}
信息转换详解
HttpMessageConverter
在Spring MVC中,HttpMessageConverter接口扮演着重要的角色,可以接收不同的消息格式,也可以将不同的消息格式响应回去(JSON)
RequestMappingHandlerAdapter
配置mvc:annotation-driven/ 注册三个bean
RequestMappingHandlerMapping,RequestMappingHandlerAdapter,DefaultHandlerExceptionResolver。
DispatcherServlet默认装配RequestMappingHandlerAdapter作为HandlerAdapter组件的实现类。
Spring 数据检验
在Web程序中,为了防止客户端传来的数据发生异常,常常需要对数据进行验证。数据验证分为客户端验证与服务器端验证。客户端验证主要通过JavaScript脚本进行,而服务器端验证主要通过Java代码进行验证。为了保证数据的安全性,客户端和服务器端验证是必须的。
Validation 检验框架
Validation接口源码:
public interface Validator {
// 对clazz类型进行检验
boolean supports(Class<?> clazz);
// 对目标进行检验,必将检验错误记录在errors中。
void validate(@Nullable object target,Errors errors);
}
在实际开发中,spring-mvc.xml 配置中mvc:annotation-driven/ 会默认装配好LocalValidatorFactoryBean,所以在实际开发中不需要手动配置。下面来看一个实例:
实体类AyUser请参考我Spring MVC1的内容。
在com/ay/validator目录下创建用户数据检验类AyUserValidator,代码:
注意前面的扫描注解不要少了。
实现了Validator接口,实现supports和validate方法。在supports方法使用了ValidationUtils的静态方法rejectIfEmpty对name和age进行检验。
@Component
public class AyUserValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return AyUser.class.equals(aClass);
}
@Override
public void validate(Object o, Errors errors) {
// 指定errors对象,验证失败的字段、错误码
ValidationUtils.rejectIfEmpty(errors,"name","name.empty");
AyUser p = (AyUser) o;
if( p.getAge()<0) {
errors.rejectValue("age","年龄不能小于0岁");
}else if(p.getAge()>150) {
errors.rejectValue("age","年龄不超过150岁");
}
}
}
在views创建用户页面创建保存用户页面saveUser.jsp:
<form action="/testspring_war_exploded/user/insert" method="post">
<table>
<tr>
<td>姓名:</td>
<td><input name="name" type="text"></td>
</tr>
<tr>
<td>密码:</td>
<td><input name="password" type="text"></td>
</tr>
<tr>
<td>年龄:</td>
<td><input name="age" type="text"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form>
在控制层AyUserController添加相关的代码:
这里浅析一下下面的/insert代码:
首先会加载对象
@RequestMapping("/save")
public String save() {
return "saveUser";
}
@Resource
private AyUserValidator ayUserValidator;
@PostMapping("/insert")
public String insert(@ModelAttribute AyUser ayUser, Model model, Errors errors) {
ayUserValidator.validate(ayUser,errors);
if(errors.hasErrors()) {
//错误存放到model中
model.addAttribute("errors",errors);
return "error";
}
int count = ayUserService.insertUserTest(ayUser);
return "success";
}
然后创建success和error jsp文件即可。
输入80/save进入输入界面,输入年龄小于0则返回error页面。
不过我这里一开始遇到了一些问题,问题出在jsp的action的提交方法:
因为提交过后,在浏览器就没有项目名字了,解决办法就是:
或者就像我的jsp中把项目名字加上。当然也可以添加路径名字:
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!--该语句需要放到head标签里-->
<base href="<%=basePath%>">
在这里我纳闷的是,为什么可以直接接收到jsp表单的内容,这就是Spring的特性。
要求form中的某个属性name和entity的属性名一致。
在我的参数中引用了ModelAttribute: 1、注解Controller中的方法时,返回的参数是一个属性值,@ModelAttribute注解的方法在Controller中每个URL处理方法调用之前,都会按照先后顺序执行。2、注解Controller方法的参数,用于从model、Form表单或者URL请求参数中获取属性值。
In Spring MVC, the @ModelAttribute annotation binds a method parameter or method return value to a named model attribute and then exposes it to a web view. It refers to the property of the Model object.
JSR 303检验
JSR 303是Java为Bean数据合法性检验的标准框架。核心接口是Validator,根据目标对象类中所标注的校验注解进行数据检验,并得到检验结果,在Bean属性中标注类似@NotNull,@Max等标注的注解指定校验规则,并通过标准的验证接口对bean进行验证。
注入依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
在AyUser.java修改为:
@NotBlank(message = "name不能为空")
private String name;
@Length(min = 3, max = 16, message = "密码长度必须在3~16位之间")
private String password;
@Range(min = 0, max = 150, message = "年龄必须在0~150岁之间")
private Integer age;
saveUser.jsp表单同上。
在控制层添加相应的方法
@PostMapping("/insert")
public String getinsert(@Valid AyUser ayUser, Model model, BindingResult result,Errors errors) {
if(errors.hasErrors()) {
model.addAttribute("errors",errors);
return "error";
}
int count = ayUserService.insertUserTest(ayUser);
return "success";
}
运行后,如果写的密码小于三位,那么会在控制台打印相应的信息:
以上。