标签:resolve form rac filter json nat 转换方法 else 信息
URL请求处理流程:
页面控制器,内部可以包含处理具体请求的方法(@RequestMapping)。
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你好";
}
}
下面hello方法对应的url变为:/hello/index
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("index")
public String hello() {
return "hello你好";
}
}
当不指定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
返回值在浏览器中出现乱码,一般是由于没有对返回内容指定编码,导致SpringMVC使用了默认的ISO编码。如下示例指定了使用utf-8编码。
@RequestMapping(value = "/hello", produces = "text/html;charset=utf-8")
public String hello() {
return "hello你好";
}
不推荐使用produces解决乱码,因为使用转换器更方便。
将请求参数解析成key=value对,然后根据方法中参数类型和名称,进行参数转换和填充。
例如:url请求为http://localhost/user/get?id=1&name=Jim
,处理方法为:
get(int id, String name)
,则参数会一一对应;get(int id)
,则只会填充id的值;get(int id, String name, String email)
,则email的值为null;get(int id, int age, String name)
,则会出错,因为age没有值,会设置为null,但是age又是一个基本类型,无法设置为null。可以将age设置为Integer,则不过抛错;get(User user)
,则会调用User的setter方法,id和name会被设置,user的其他属性为null;get(User user, int id)
,则user.id和id都会被赋值;get和post方法都可以在请求头(request head,即在url后面的参数)中设置参数,但是只有post方法有请求体(request body,表单提交的数据就在这里);
HTTP/1.1 并未规定get不能有请求体,但一般Web服务器不会处理get方法的请求体。
的编码一般不需要关注,浏览器默认是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中国
请求体长什么样?
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吉姆
上述设置编码后,服务器端不会产生乱码。
如果不想在发送请求时设置编码格式,还可以通过过滤器或者拦截器设置编码。
<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>
浏览器中传递的传输是字符串,但我们方法中参数的类型却多种多样,它们是如何进行转换的呢?
常见的转换SpringMVC已经帮我们处理了。
详见:org.springframework.core.convert.converter.Converter
接口的实现类。例如:StringToNumber
类实现了字符串到数字类型(int,short,float等)的转换。
如2.1示例5所述,根据参数名和setter方法来转换。这也是SpringMVC处理的,不需要我们手动处理。
当我们需要将参数转换为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>
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
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.1和2.4.2所述,与普通参数无异,只是SpringMVC使用了类的setter方法而已。
示例:
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
通过映射找到url处理方法,该方法除了业务逻辑外,还需要考虑以何种方式响应。
简单的字符串可以直接返回,但很多时候,我们直接返回的是对象。此时需要工具帮我们将类转换为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,需要:
直接返回页面名即可。但一般我们使用了模板引擎时,会向模板文件传递数据,如何传递呢?
为了能够正确找到文件,一般需要如下配置:
<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";
}
该代码做了什么:
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>
核心:解决参数丢失
重定向会使浏览器再发送一次(不同url)请求,第一次提交的参数会丢失。 解决办法:
使用 RedirectAttributes 参数,并使用 addFlashAttribute 方法保存参数值。
示例:
页面代码:
// 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的值,该如何处理呢?后续会在登录模块介绍。
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映射到相同的方法。
通过对用户登录的学习,我们可以进一步看到SpringMVC提供了哪些其他有用的功能。
在
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";
}
在之前的处理中,我们把登录验证放在方法里面,如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";
}
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,也可以通过正则表达式和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注解即可。
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
注解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
注解即可。在参数校验中,我们直接抛出了异常。这些异常也可以交给Spring MVC处理。
标签:resolve form rac filter json nat 转换方法 else 信息
原文地址:https://www.cnblogs.com/cheng18/p/12152957.html