Spring MVC 学习

URL请求处理流程:

graph LR
id1(URL处理方法映射)-->id2(参数解析)
id2-->id3(执行方法)
id3-->id4(响应请求)

1. URL处理方法映射

1.1 Controller

页面控制器,内部可以包含处理具体请求的方法(@RequestMapping)。

  • @Controller:返回视图,如jsp或者html文件;
  • @RestController:返回JSON,如字符串,或者数组;

1.2 @RequestMapping

  • 作用:指定url请求的处理方法;
  • 作用范围:
    • 用在Controller类上:指定上级路径;
    • 用在Controller内部的方法上:url请求具体的处理方法;
  • 常用属性:
    • value:url路径,可以多个;
    • method:http方法,一般去get或者post;
  • 其他属性:
    • params:对url参数做限制,如必须(不)包含某个参数,以及参数取值限制等;
    • headers:读请求头做限制;
    • consumes:能处理的类型(media type);
    • produces:返回的类型;

1.3 示例:

  1. url匹配

hello()方法可以匹配的路径有:"/", "/hello", "/index";即在浏览器地址栏输入:
http://localhost:8080/
http://localhost:8080/hello
http://localhost:8080/index
都会进入到hello方法中处理。

@RestController
public class HelloController {

    @RequestMapping(value = {"/", "/hello", "/index"})
    public String hello() {
        return "hello你好";
    }
}
  1. Controller也可以指定路径

下面hello方法对应的url变为:/hello/index

@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("index")
    public String hello() {
        return "hello你好";
    }
}
  1. method示例

当不指定method属性时,方法可以同时处理get和post方法。

@RequestMapping(value = "/hello", method = RequestMethod.GET)
指定只能处理get方法,等于:@GetMapping("/hello")

@RequestMapping(value = "/hello", method = RequestMethod.POST)
指定只能处理post方法,等于:@PostMapping("/hello")

@RestController
public class HelloController {

    @RequestMapping(value = "/hello", method = RequestMethod.POST)
    public String hello() {
        return "hello你好";
    }

}

上述写法,在浏览器中访问localhost:8080/hello会提示出错,因为代码中指定的时post方法,而浏览器直接访问时,使用的时get方法。

HTTP Status 405 – 方法不允许
Type Status Report

消息 Request method ‘GET‘ not supported

描述 请求行中接收的方法由源服务器知道,但目标资源不支持

Apache Tomcat/9.0.30
  1. produces可以解决返回乱码

返回值在浏览器中出现乱码,一般是由于没有对返回内容指定编码,导致SpringMVC使用了默认的ISO编码。如下示例指定了使用utf-8编码。

@RequestMapping(value = "/hello", produces = "text/html;charset=utf-8")
public String hello() {
    return "hello你好";
}

不推荐使用produces解决乱码,因为使用转换器更方便。

2. 参数解析

2.1 基本原理

将请求参数解析成key=value对,然后根据方法中参数类型和名称,进行参数转换和填充。

例如:url请求为http://localhost/user/get?id=1&name=Jim,处理方法为:

  1. get(int id, String name),则参数会一一对应;
  2. get(int id),则只会填充id的值;
  3. get(int id, String name, String email),则email的值为null;
  4. get(int id, int age, String name),则会出错,因为age没有值,会设置为null,但是age又是一个基本类型,无法设置为null。可以将age设置为Integer,则不过抛错;
  5. get(User user),则会调用User的setter方法,id和name会被设置,user的其他属性为null;
  6. get(User user, int id),则user.id和id都会被赋值;

2.2 参数来源:请求头和请求体

get和post方法都可以在请求头(request head,即在url后面的参数)中设置参数,但是只有post方法有请求体(request body,表单提交的数据就在这里);

HTTP/1.1 并未规定get不能有请求体,但一般Web服务器不会处理get方法的请求体。

2.3 参数编码

2.3.1 请求头参数编码

的编码一般不需要关注,浏览器默认是utf-8,tomcat8,9 对url的编码也是utf-8。

@RequestMapping(value = "/hello")
public String hello(String msg) {
    System.out.println("msg: " + msg);
    return "hello你好";
}

当请求:GET http://localhost:8080/hello?msg=123中国时,idea中打印无乱码。

msg: 123中国
2.3.2 请求体参数编码

请求体长什么样?

POST http://localhost:8080/rest-user/add
Content-Type: application/x-www-form-urlencoded

id=1&name=Jim吉姆

上面的id=1&name=Jim就是请求体,也是key=value格式的。注意get方法的请求体会被忽略的。

上述请求,在服务器端会出现乱码。原因是方法体未设置编码时,会被默认设置为iso编码。

// Default character encoding to use when {@code request.getCharacterEncoding}
// returns {@code null}, according to the Servlet spec.
org.springframework.web.util.WebUtils.DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";

请求体可以通过Content-Type设置编码。

POST http://localhost:8080/rest-user/add
Content-Type: application/x-www-form-urlencoded;charset=utf-8

id=1&name=Jim吉姆

上述设置编码后,服务器端不会产生乱码。

2.3.3 通过过滤器或拦截器编码

如果不想在发送请求时设置编码格式,还可以通过过滤器或者拦截器设置编码。

  • 方法一:过滤器
    在web.xml的开头添加如下配置:
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceRequestEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <servlet-name>dispatcher</servlet-name>
</filter-mapping>
  • 方法二:拦截器

定义拦截器:

public class HelloInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setCharacterEncoding("utf-8");
        return true;
    }
}

配置拦截器:

<mvc:interceptors>
    <mvc:interceptor>
        <bean class="com.bailiban.mvc.interceptor.HelloInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

拦截器使用了HttpServletRequest,需要导入依赖:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

2.4 参数转换

浏览器中传递的传输是字符串,但我们方法中参数的类型却多种多样,它们是如何进行转换的呢?

2.4.1 常见情景

常见的转换SpringMVC已经帮我们处理了。
详见:org.springframework.core.convert.converter.Converter接口的实现类。例如:StringToNumber类实现了字符串到数字类型(int,short,float等)的转换。

2.4.2 String -> 自定义类

如2.1示例5所述,根据参数名和setter方法来转换。这也是SpringMVC处理的,不需要我们手动处理。

2.4.3 自定义转换方法

当我们需要将参数转换为Date类型时,SpringMVC无法处理,需要我们自定义转换方法。

public class StringToDateConverter implements Converter<String, Date> {

    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    @Override
    public Date convert(String source) {
        try {
            return format.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }
}

配置转换器:

<bean id="myConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.bailiban.mvc.converter.StringToDateConverter" />
        </set>
    </property>
</bean>

2.5 参数相关注解

  • @RequestParam:用在方法的参数上,可以省略。
  • @RequestBody:获取请求体,用在post方法中,也可以省略,SpringMVC会自动帮我们设置对象属性;
  • @PathVaribale:位置参数,restfull风格url使用。
  • @ModelAttribute:当请求参数不完整时,可以使用它注解的方法。注意,同一Controller下的所有方法在运行前,都会执行其注解的方法,有时候可能并不是你想要的。
2.5.1 @ModelAttribute 使用示例
  • 前端请求:
POST http://localhost:8080/rest-user/add
Accept: text/html;charset=utf-8
Content-Type: application/x-www-form-urlencoded;charset=utf-8

id=1&name=Jim123中国
  • 后端处理:
@PostMapping("add")
public String add(User user) {
    System.out.println(user);
    userList.add(user);
    return user.toString();
}
  • @ModelAttribute 方法:
@ModelAttribute
public User getUser(int id) {
    User user = userList.stream().filter(u -> u.getId().equals(id)).findAny().orElse(null);
    user.setFriends(Arrays.asList("Lily", "Lucy"))
        .setDate(new Date());
    return user;
}
  • 请求结果:
User(id=1, name=Jim123中国, friends=[Lily, Lucy], date=Sat Jan 04 21:00:27 CST 2020)

我们可以看到,user的其他属性也被赋值。而name属性使用的是请求参数设置的值。注意@ModelAttribute 方法在请求映射的方法之前运行。

2.6 前端请求参数设置

2.6.1 基本类型

普通参数只需要前后端参数名称一致即可。

2.6.2 自定义类

如2.1和2.4.2所述,与普通参数无异,只是SpringMVC使用了类的setter方法而已。

2.6.3 集合/类参数
  1. 使用多个同名参数:适用于List, Array, Set;
  2. 使用[](中括号):适用于List, Array(请求体中),Map;
  3. 使用.(点号):对象;
  4. 逗号分隔:适用于List, Array, Set;

示例:

1)包含对象属性

@Data
public class User {

    private Integer id;
    private String name;
    private Set<String> friends;
    private Date date;
    private Account account;
    private Account[] accountList;
}

请求:

POST http://localhost:8080/rest-user/add
Accept: text/html;charset=utf-8
Content-Type: application/x-www-form-urlencoded;charset=utf-8

id=5&name=Tim&friends=lily,kate&account.money=100&accountList[0].money=1&accountList[1].money=2

结果:

User(id=5, name=Tim, friends=[lily, kate], date=null, account=Account(money=100.0), accountList=[Account(money=1.0), Account(money=2.0)])

2)逗号分割

GET http://localhost:8080/hello?msg=1,2,3
Accept: text/html
@RequestMapping(value = "/hello")
public String hello(String[] msg) {
    return String.join("###" , msg);
}

结果:

1###2###3

3)数组下标

POST http://localhost:8080/rest-user/add
Accept: text/html;charset=utf-8
Content-Type: application/x-www-form-urlencoded;charset=utf-8

id=5&name=Tim&friends[0]=lily&friends[1]=kate

3. 方法执行

通过映射找到url处理方法,该方法除了业务逻辑外,还需要考虑以何种方式响应。

4. 响应请求

4.1 返回json格式

简单的字符串可以直接返回,但很多时候,我们直接返回的是对象。此时需要工具帮我们将类转换为json字符串。

只需要加入如下依赖即可,SpringMVC会帮我们接收json格式参数(转换对象),并将对象转换为json返回,甚至会帮我们把字符编码设置为utf-8。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>${jackson-version}</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jackson-version}</version>
</dependency>

相关源码(AnnotationDrivenBeanDefinitionParser.java):

	jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
					ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
---
    if (jackson2Present) {
		beanDef.getPropertyValues().add("requestBodyAdvice",
				new RootBeanDefinition(JsonViewRequestBodyAdvice.class));
	}
---
	if (jackson2Present) {
		beanDef.getPropertyValues().add("responseBodyAdvice",
				new RootBeanDefinition(JsonViewResponseBodyAdvice.class));
	}
---
    if (jackson2Present || gsonPresent) {
		defaultMediaTypes.put("json", MediaType.APPLICATION_JSON_VALUE);
	}
---
	if (jackson2Present) {
		Class<?> type = MappingJackson2HttpMessageConverter.class;
		RootBeanDefinition jacksonConverterDef = createConverterDefinition(type, source);
		GenericBeanDefinition jacksonFactoryDef = createObjectMapperFactoryDefinition(source);
		jacksonConverterDef.getConstructorArgumentValues().addIndexedArgumentValue(0, jacksonFactoryDef);
		messageConverters.add(jacksonConverterDef);
	}

为了使我们的映射方法能够返回 json,需要:

  • 在类上添加@RestController注解;
  • 或者类上为@Controller注解,然后在方法上添加@ResponseBody注解;

4.2 响应页面

直接返回页面名即可。但一般我们使用了模板引擎时,会向模板文件传递数据,如何传递呢?

为了能够正确找到文件,一般需要如下配置:

   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" >
       <property name="prefix" value="/WEB-INF/pages/" />
       <property name="suffix" value=".jsp" />
   </bean>

在映射方法中,添加一个ModelMap类型的数据,就可以帮我们向模板文件传递数据了。

示例代码:

@RequestMapping(value = "get")
public String get(int id, ModelMap model) {
    model.addAttribute("user",
            userList.stream().filter(u -> u.getId().equals(id)).findAny().orElse(null));
    return "user";
}

该代码做了什么:

  • 添加参数:ModelMap model;
  • 向model添加属性:model.addAttribute("user", xxx);
  • 返回user对应于web\WEB-INF\pages\user.jsp文件;

user.jsp 中,通过${user.id}${user.name}获取user的id和name值:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page import="java.util.Date" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<html>
<head>
    <title>Title</title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>

<c:if test="${!empty user}">
    <form action="${pageContext.request.contextPath}/user/update" method="post">
        <div><label>ID:<input name="id" value="${user.id}"></label></div>
        <div><label>Name:<input name="name" value="${user.name}"></label></div>
        <div><input type="submit" value="submit"></div>
    </form>
</c:if>

</body>
</html>

注意,方法中的参数也会默认传给页面。下面方法中,参数user会到页面中。

@RequestMapping(value = "get2")
public String get(User user) {
    int id = user.getId();
    User user1 = userList.stream().filter(u -> u.getId().equals(id)).findAny().orElse(null);
    user.setName(user1.getName());
    return "user";
}

为了正常使用jstl/core标签,需要添加依赖:

<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

4.3 重定向

核心:解决参数丢失

重定向会使浏览器再发送一次(不同url)请求,第一次提交的参数会丢失。
解决办法:

使用 RedirectAttributes 参数,并使用 addFlashAttribute 方法保存参数值。

示例:

graph LR
id1(login)-- 重定向 -->id2(home)

页面代码:

// login.jsp
    <form action="${pageContext.request.contextPath}/user/login" method="post">
        <label>ID: <input name="id" value=""></label><br>
        <label>Name: <input name="name" value=""></label><br>
        <input type="submit" value="Login">
    </form>
// user.jsp
    <div id="user">
        <label>ID: ${user.id}</label><br>
        <label>Name: ${user.name}</label><br>
    </div>

Controller代码:

    @RequestMapping("login")
    public String login(User user, RedirectAttributes redirectAttributes) {
        System.out.println(user);
        if (user.getId() != null) {
            if (user.getId() != null &&
                (userList.stream().anyMatch(u -> u.getId().equals(user.getId()) &&
                        u.getName().equals(user.getName())))) {
                redirectAttributes.addFlashAttribute(user);
                return "redirect:/user/home";
            }
        }
        return "login";
    }

    @RequestMapping("home")
    public String home(User user) {
        if (user.getId() == null) {
            return "redirect:/user/login";
        }
        return "home";
    }

重点:

  • login(User user, RedirectAttributes redirectAttributes):方法添加RedirectAttributes参数;
  • redirectAttributes.addFlashAttribute(user);:传递参数;
  • return "redirect:/user/home";:重定向到home页面;
  • home(User user):home方法会从redirectAttributes中获取user对象;

注意:完成重定向后,user值会清空。可自行刷新home页面,看看会发生什么!

如果想一直保存user的值,该如何处理呢?后续会在登录模块介绍。

4.4 forward

forward相对简单,因为参数没有丢失。

    @RequestMapping("login1")
    public String login(User user) {
        return "forward:/user/login";
    }

当访问 /user/login1时,会丢给/user/login处理,user参数也会自动传递。

效果跟下面写法是一样的:

    @RequestMapping({"login", "login1"})
    public String login(User user, RedirectAttributes redirectAttributes) {
    // ...
    }

说明:@RequestMapping可以指定多个url映射到相同的方法。

5. 登录处理

通过对用户登录的学习,我们可以进一步看到SpringMVC提供了哪些其他有用的功能。

5.1 @SessionAttributes

4.3重定向的学习中,我们使用了RedirectAttributes参数保存user信息,但是在刷新home页面后,user信息丢失了。

@SessionAttributes 可以保存在整个Session使用的数据,很适合保存登录信息。

使用示例:

  • Controller上使用@SessionAttributes,并通过类型(User.class)指定Session保存的的是用户信息。
@Controller
@RequestMapping("/user")
@SessionAttributes(types = {User.class})
public class UserController {
}
  • login处理中,将user信息保存的model中model.addAttribute(user);
    @RequestMapping({"login", "login2"})
//    public String login(User user, RedirectAttributes redirectAttributes) {
    public String login2(User user, ModelMap model) {
        System.out.println(user);
        if (user.getId() != null) {
            if (user.getId() != null &&
                (userList.stream().anyMatch(u -> u.getId().equals(user.getId()) &&
                        u.getName().equals(user.getName())))) {
//                redirectAttributes.addFlashAttribute(user);
                model.addAttribute(user);
                return "redirect:/user/home";
            }
        }
        return "login";
    }

此时,在home页面就可以正常访问user用户信息,且不会出现刷新丢失的问题。

  • logout中,可以清除登录信息:sessionStatus.setComplete();
    @RequestMapping("logout")
    public String logout(SessionStatus sessionStatus) {
        // 清除session,即清除user对象0
        sessionStatus.setComplete();
        return "redirect:/user/login";
    }

5.2 登录验证

在之前的处理中,我们把登录验证放在方法里面,如home方法通过条件:if (user.getId() == null)来判断是否登录:

    @RequestMapping("home")
    public String home(User user) {
        if (user.getId() == null) {
            return "redirect:/user/login";
        }
        return "home";
    }

但如果所有页面都需要登录只会才能访问,在每个映射方法都添加登录验证是不可取的。可以使用拦截器解决。

创建LoginInterceptor拦截器

public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 不拦截 login
        if (request.getRequestURI().contains("/login")) {
            return true;
        }
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        if (user == null || user.getId() == null) {
            // 转发
//            request.getRequestDispatcher("/user/login").forward(request, response);
            // 重定向
            response.sendRedirect("/user/login");
            return false;
        }
        return true;
    }
}

login中设置Session级别的user信息:

login方法中添加HttpSession参数,调用其session.setAttribute("session_user", user);方法添加user参数;
示例:

    @RequestMapping("login")
    public String login2(User user, HttpSession session) {
        if (session.getAttribute("user") != null)
            return "redirect:/user/home";

        if (user.getId() != null &&
                (userList.stream().anyMatch(u -> u.getId().equals(user.getId()) &&
                        u.getName().equals(user.getName())))) {
                session.setAttribute("user", user);
                return "redirect:/user/home";
        }
        return "login";
    }

home页面使用user信息,使用@SessionAttribute("user")

    @RequestMapping("home")
    public String home(@SessionAttribute("user") User user) {
        return "home";
    }

更新user

    @PostMapping("update")
    public String update(User user, HttpSession session) {
        System.out.println(user);
        for (int i=0; i<userList.size(); i++) {
            if (userList.get(i).getId().equals(user.getId())) {
                userList.set(i, user);
            }
        }
        session.setAttribute("user", user);
        return "redirect:/user/home";
    }

6. 参数校验

6.1 使用Annotaion JSR-303标准的验证

导入Hibernate Validator依赖:

    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.1.0.Final</version>
    </dependency>

可使用如下注解对属性进行限制:

限制 说明
@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

示例代码:

  • User属性添加限制@NotEmpty(message="用户名不能为空!")
public class User {

    private Integer id;
    @NotEmpty(message="用户名不能为空!")
    private String name;
}
  • 参数校验@Validated User user
    @PostMapping("update")
    public String update(@Validated User user, HttpSession session){
    }

当提交的用户名为空时,会抛异常:

org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver.logException Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object ‘user‘ on field ‘name‘: rejected value []; codes [NotEmpty.user.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [用户名不能为空!]]

关于嵌套校验:属性上添加@Valid注解即可。

6.2 自定义校验器

public class UserValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        // 判断是否为User类
        return User.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        User user = (User) target;
        // 判断name是否为空
        if (StringUtils.isEmpty(user.getName())) {
            errors.rejectValue("name", "-1", "用户名不能为空!");
        }
    }
}
  • 添加校验器,在UserController中添加如下代码:
    @InitBinder
    public void initBinder(DataBinder binder){
        binder.replaceValidators(new UserValidator());
    }

@InitBinder方法也可以添加到@ControllerAdvice注解的类中,或者设置到<annotation-driven>validator属性中。

  • 使用时,user参数也需要添加@Validated注解

6.3 自定义注解校验器

  • 定义校验器:NameValidator
public class NameValidator implements ConstraintValidator<NameValidation, String> {
    @Override
    public void initialize(NameValidation constraintAnnotation) {

    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (StringUtils.isEmpty(value))
            return false;
        return true;
    }
}
  • 定义校验器注解:NameValidation
@Constraint(validatedBy = NameValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface NameValidation {

    public String message() default "名称不合法";
    public Class<?>[] groups() default {};
    public Class<? extends Payload>[] payload() default {};

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
    @interface List {
        NameValidation[] value();
    }
}
  • User.name上添加注解:@NameValidation
public class User {

    private Integer id;
    @NameValidation(message="自定义注解校验器:用户名不能为空!")
    private String name;
}
  • 使用时,与前面两种方法相同。user参数添加@Validated注解即可。

7. 异常处理

在参数校验中,我们直接抛出了异常。这些异常也可以交给Spring MVC处理。

8. 静态文件访问

9. 文件上传

原文地址:https://www.cnblogs.com/cheng18/p/12152957.html

时间: 2024-08-27 22:08:39

Spring MVC 学习的相关文章

Spring MVC 学习笔记(二):@RequestMapping用法详解

一.@RequestMapping 简介 在Spring MVC 中使用 @RequestMapping 来映射请求,也就是通过它来指定控制器可以处理哪些URL请求,相当于Servlet中在web.xml中配置 <servlet>     <servlet-name>servletName</servlet-name>     <servlet-class>ServletClass</servlet-class> </servlet>

[Spring MVC]学习笔记--DispatcherServlet

在上一篇我们介绍了Servlet,这一篇主要来看一下MVC中用到的DispatcherServlet(继承自HttpServlet). 1. DispatcherServlet在web.xml中被声明. <web-app> <servlet> <servlet-name>example</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet&l

《Spring MVC学习指南》一书具有很大的欺骗性

2016年6月21日 最近,因为工作需要,我从网上买了一本<Spring MVC学习指南>,ISBN编号: 978-7-115-38639-7,定价:49.00元.此书是[美]Paul Deck著于2014年,林催二人于2015年翻译,2016年第6次印刷.我在淘宝上购买时就询问是哪个版本,店小二确实搞不清.我看了淘宝店此书的编辑推荐.目录.内容推荐,仍然看不出是哪个版本. 书买来,我稍微翻了一下,确认是基于Spring MVC 2.5的.这就问题大了,现在3.1版以后,方法完全不同变化很大,

Spring MVC学习笔记(一)--------准备篇

这一系列笔记将带你一步一步的进入Spring MVC,高手勿喷. 首先你得安装以下的工具: JDK,虽然JDK8已经发布了一段时间了,但是由于我们并不会使用到里面的新特性,所以JDK6以上版本皆可以(需加入到PATH环境变量中): Servlet Container,为了能运行WEB应用程序,因此需要一个Web Container,这里我们建议Tomcat即可: IDE,一个好的IDE不仅能提高你开发的效率,还能降低你学习的成本,我们选择的是IntelliJ: 构建工具,推荐使用Gradle,它

[转]Spring MVC 学习笔记 json格式的输入和输出

Spring mvc处理json需要使用jackson的类库,因此为支持json格式的输入输出需要先修改pom.xml增加jackson包的引用 <!-- json --> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-core-lgpl</artifactId> <version>1.8.1</version>

Spring MVC学习之三:处理方法返回值的可选类型

转自:http://www.cnblogs.com/cuizhf/p/3810652.html ———————————————————————————————————————————————————————————— spring mvc处理方法支持如下的返回方式:ModelAndView, Model, ModelMap, Map,View, String, void.下面将对具体的一一进行说明: ModelAndView @RequestMapping("/show1") publ

[Spring MVC]学习笔记--基础Servlet

Servlet是一个用Java编写的应用程序,在服务器上运行,处理请求的信息并将其发送到客户端. Servlet的客户端提出请求并获得该请求的响应. 对于所有的客户端请求,只需要创建Servlet的实例一次(这是和CGI(Common Gateway Interface)的重要区别,CGI是每个请求创建一个新实例),因此节省了大量的内存. Servlet在初始化后即驻留内存中,因此每次作出请求时无需加载. 下面通过一个例子来介绍如何编写一个简单的Servlet. 准备工作: 1. 下载并启动To

[Spring MVC]学习笔记--表单标签的使用

github例子地址: https://github.com/lemonbar/spring-mvc-jsp 效果图 关于spring mvc的标签的讲解, 有一篇blog已经讲的很细了. http://haohaoxuexi.iteye.com/blog/1807330 官方文档地址: http://docs.spring.io/spring/docs/4.0.6.RELEASE/spring-framework-reference/htmlsingle/#view-jsp 而且我在上面的例子

[Spring MVC]学习笔记--@RequestMapping

@RequestMapping是用来将请求的url,映射到整个类,或者里面的方法. @Controller @RequestMapping("/test") public class TestController { private final TestService service; @Autowired public TestController(TestService testService) { this.service = testService; } @RequestMap

[Spring MVC]学习笔记--FreeMarker的使用

还是先贴出该例子存于github上的位置 https://github.com/lemonbar/spring-mvc-freemarker Sping-Framework 的官方文档简单列出了在spring-mvc中如何使用freemarker, 但是相对来说提供的信息和例子太少, 所以在这给出一个详细的例子. 注:我是在maven基础上进行的构建, 很多解释已经在代码中加了, 所以尽量贴代码. FreeMarker Site: http://freemarker.org/ 1. 整个文件夹结