[Spring MVC 5]常用注解与数据校验

发布于:2022-11-09 ⋅ 阅读:(5) ⋅ 点赞:(0) ⋅ 评论:(0)

本次对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";
    }

运行后,如果写的密码小于三位,那么会在控制台打印相应的信息:
在这里插入图片描述
以上。