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


    本次对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";
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上述代码定义一个AyUserController控制层,使用@Controller注解进行表示,使用@GetMapping 注解映射一个请求,为了保证Spring 能找到控制层,需要进行额外配置(之前已经配过了)在applicationContext.xml:

    <context:component-scan base-package="com.ay"/>
    当然了,如果更加精确点:
    <context:component-scan base-package="com.ay。controller"/>
    
    • 1
    • 2
    • 3

    component-scan 功能是启动包扫描功能,使加有@Controller,@Service,@Respository,@Component 等注解能成为bean,base-package属性指定了需要扫描类包。
    当然了,需要了在web.xml配置Spring MVC前端控制器DispatcherServlet,以及在spring-mvc配置InternalResourceViewResolver 视图解析器。
    web.xml:

    
      <servlet>
        <servlet-name>spring-dispatcherservlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
        
        <init-param>
          <param-name>contextConfigLocationparam-name>
          <param-value>classpath:spring-mvc.xmlparam-value>
        init-param>
        <load-on-startup>1load-on-startup>
      servlet>
      <servlet-mapping>
        <servlet-name>spring-dispatcherservlet-name>
        
        <url-pattern>/url-pattern>
      servlet-mapping>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    sping-mvc.xml:

    
        <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    @RequestMapping

    这个是最常用的注解之一,这个注解会将HTTP请求映射到MVC和RES控制器的处理方法上。其可以在控制类的级别或方法的级别上使用,在类级别上会将一个特定的请求或者请求模式映射到一个控制器上,之后可以另外添加别的注解来进一步指定映射关系。例如上述代码。
    除了value的属性外:
    在这里插入图片描述
    例如:

      @RequestMapping(
    		value = {
    		"",
    		"/page",
    		"page*",
    		"view/*,**/msg"
    	}
    )
    public String hello(Model model){}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可见,可以将多个请求映射到一个方法上,只需要添加一个带有请求路径值列表的@RequestMapping即可。
    同时可以处理HTTP请求的方法,比如GET,PUT,POST,DELETE以及PATCH,所有的请求默认是HTTP GET,为了将一个请求映射到一个特定的HTTP方法,需要使用method属性声明:

    @RequestMapping(method = RequestMethod.GET)
    
    • 1

    @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";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    浏览器输入请求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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    请求方法

    在这里插入图片描述
    如果要访问HttpServletRequest对象,可以将其添加到方法中作为参数,Spring 会将对象正确的传递:

    @RequestMapping("hello")
    pubilc ModelAndView hello(HttpMethod method) {
    	ModelAndView mv = new ModelAndView();
    	return mv;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    一般来说,请求方法可返回的参数可以有以下几种:
    在这里插入图片描述
    在这里插入图片描述

    参数绑定注解

    @RequestParam

    用于将制定的请求参数赋值给方法中的形参。
    在这里插入图片描述
    实例如下:

    @RequestMapping("findByID")
    public String findByID(@RequestParam(value="id") String id) {
    	AyUSer ay = ayUserService.findByID(id);
    	return "hello";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当请求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){
    	。。。
    }
    
    • 1
    • 2
    • 3
    • 4

    传参的时候会赋值,例如输入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 "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如输入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...");
    }
    
    • 1
    • 2
    • 3
    • 4

    当注解的方法无返回值

    @ModelAttribute
    public void init(Model model){
    	AyUser ayUser = new AyUser();
    	ayUser.setId(1);
    	ayUser.setName("ay");
    	model.addAttribute("user",ayUser);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在init方法无返回值,但调用了Model对象,可以在前端页面hello.jsp中:

    <%@page  isELIgnored="false" %>
    <body>
    hello, ${user.name}
    body>
    
    • 1
    • 2
    • 3
    • 4

    当url输入80/hello的时候:会显示:hello,ay。因为model模型数据的作用域与request相同

    当方法有返回值

    @RequestMapping("/test")@ModelAttribute("name")
        @RequestMapping(value = "/hello")
        public String hello() {
            //return "hello";
            return "ay";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    jsp文件:

    <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
    <html>
    <head>
        <title>Hello World</title>
    </head>
    <body>
    hello,${name}
    </body>
    </html>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    不过我运行的时候,需要将hello.jsp文件放在views/test/hello.jsp中,否则会找不到,此时就能得到name的值。
    上述代码类似于model.addAttribute(“name”,name);

    public String hello(Model model) {
            //return "hello";
            model.addAttribute("name","akk");
            return "hello";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用其注解方法的参数,传入对象.

    @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";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    jsp文件:

    <body>
    hello,${ayUser.name}
    body>
    
    • 1
    • 2
    • 3

    输入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";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    浏览器输入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.coregroupId>
                <artifactId>jackson-coreartifactId>
                <version>2.9.5version>
            dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.coregroupId>
                <artifactId>jackson-annotationsartifactId>
                <version>2.9.5version>
            dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.coregroupId>
                <artifactId>jackson-databindartifactId>
                <version>2.9.5version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Spring MVC主要利用类型转换器messageConverters将前台信息转换为开发者需要的格式,在controller方法接收参数前添加此注解进行数据转换:
    返回普通字符串

    @RequestMapping("/view")
        @ResponseBody
        public String testview() {
            return "View Test";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此时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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    此时输入相应的地址,会出现集合。

    @RequestBody注解用于读取Request请求的body部分数据,使用默认配置HttpMessageConverter进行解析,然后把相应的数据绑定到Controller参数上。

    @RequestMapping("/tereq")
        @ResponseBody
        public void tereq(@RequestBody AyUser ayUser) {
            System.out.println(ayUser.getName());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    信息转换详解

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在实际开发中,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岁");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在控制层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";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    然后创建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%>">
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里我纳闷的是,为什么可以直接接收到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.validatorgroupId>
                <artifactId>hibernate-validatorartifactId>
                <version>6.0.10.Finalversion>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

  • 相关阅读:
    【编程之路】面试必刷TOP101:动态规划(78-82,Python实现)
    Android 13.0 Launcher3定制之双层改单层(去掉抽屉式四)
    关于linux的一点好奇心(五):进程线程的创建
    SwiftUI 教程之如何呈现不同高度的sheet, 新的视图修改器使工作变得容易。
    数值法求解微分博弈问题(〇)——定义
    k8s--基础--25.1--Helm--介绍和安装
    计算机毕设(附源码)JAVA-SSM基于web的医院门诊管理系统
    记录一次较为完整的Jenkins发布流程
    ROS2报错 AttributeError: type object ‘type‘ has no attribute ‘_TYPE_SUPPORT‘
    函数式接口和方法引用
  • 原文地址:https://blog.csdn.net/QAZJOU/article/details/127673326