SpringMVC数据校验、数据格式化处理、国际化设置

SpringMVC数据校验、数据格式化处理、国际化设置

1.数据验证

(1)使用JSR-303验证框架

JSR(Java Specification Requests),意思是Java 规范提案。JSR-303是JAVA EE 6中的一项子规范,叫做Bean Validation。JSR 303,Bean Validation规范 ,为Bean验证定义了元数据模型和API。默认的元数据模型是通过Annotations来描述的,使用规范定义的这些注解有效的替换了if-else冗长的校验代码。

引入依赖:

•validation-api

•hibernate-validator(附加了一些验证注解)

•jakarta.validation-api(Spring6以上引入此依赖)

JSR-303验证框架常用注解

使用案例:

@Data
public class SysUser {
    /*** 用户ID*/
    @NotNull(message = "用户id不能为空")
    private Long userId;
    /** 用户名*/
    @NotBlank(message = "用户名不能为空")
    @Length(max = 20, message = "用户名不能超过20个字符")
    @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")
    private String username;
    /** 手机号*/
    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手机号格式有误")
    private String mobile;
    /**性别*/
    private String sex;
    /** 邮箱*/
    @NotBlank(message = "联系邮箱不能为空")
    @Email(message = "邮箱格式不对")
    private String email;
    /** 密码*/
    private String password;
    /*** 创建时间 */
    @Future(message = "时间必须是将来时间")
    private Date startTime;
}

在controller层方法参数列表前加上@Valid或@Validated注解

@PostMapping("/save/valid")
@ResponseBody
public Result save(@RequestBody @Valid User user) {
    if(userService.save(user)>0)
        return Result.ok();
    else
        return Result.fail();
}

@Valid@Validated的区别 :

区别 @Valid @Validated
来源 标准JSR-303规范 Spring's JSR-303规范,是标准JSR-303的一个变种。
分组验证 不支持 支持(使用注解的group属性设定)
声明位置 可以用在方法、构造函数、方法参数和成员属性(字段)上,支持嵌套验证。 可以用在类型、方法和方法参数上,但不能用在成员属性(字段)上,从而不支持嵌套验证功能。

@Valid嵌套验证

@Data
public class Food {
    @Valid
    private Drink drink;
    @NotNull
    private String type;
}

@Data
public class Drink {
    @NotNull
    private String name;

    @Size(min = 1,max = 10000)
    private String describ;

    @Digits(integer = 2,fraction = 2)
    private Double price;
}
//controller层
@PostMapping("/food")
public String addFood(@Valid Food food){
    return food.getDrink().getName();
}

数据验证全局异常定义案例:

@Component  
@ResponseBody  
@ControllerAdvice
public class GlobalExceptionHandler {    
    /**方法参数校验(接收参数加上@RequestBody注解才会有这种异常) */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("方法参数校验失败", e);
        return Result.fail(Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
    }
    @ExceptionHandler(BindException.class)
    public Result bindException(BindException ex, HttpServletRequest request) {
        log.error("数据绑定异常", ex);
        try {// 拿到@NotNull,@NotBlank和 @NotEmpty等注解上的message值
            String msg = Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage();
            if (StringUtils.isNotEmpty(msg)) {
           return Result.fail(msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }        
    StringBuilder msg = new StringBuilder(); // 参数类型不匹配检验
        List<FieldError> fieldErrors = ex.getFieldErrors();
        fieldErrors.forEach((oe) ->
                msg.append("参数:[").append(oe.getObjectName()).append(".").append(oe.getField())
                        .append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配.")
        );
        return Result.fail(msg.toString());
    }
    /**ConstraintViolationException */
    @ExceptionHandler(ConstraintViolationException.class)
    public Result handleConstraintViolationException(ConstraintViolationException e) {
        log.error("注解校验异常", e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
        return Result.fail(message);
    }
}

自定义验证注解的步骤案例:

•定义注解接口

@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CardNoValidator.class)
public @interface CardNo {
    String message() default "{edu.cqie.ssm.cardNoErrorMessage}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

•创建ConstraintValidator接口实现类

public class CardNoValidator implements ConstraintValidator<CardNo, Object> {
    @Override
    public void initialize(CardNo constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
        if (o == null) {
            return true;//身份证号未传递时,不做校验
    }
        return IdCardValidatorUtils.isValidate18Idcard(o.toString());
    }
}

•使用注解

@Data
public class User {
    /*** 用户ID*/
    @NotNull(message = "用户id不能为空")
    private Long userId;

    /** 用户名*/
    @NotBlank(message = "用户名不能为空")
    @Length(max = 20, message = "用户名不能超过20个字符")
    @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "用户昵称限制:最多20字符,包含文字、字母和数字")
    private String username;

    @CardNo
    private String cardNo;
}

2.数据格式化

SpringMVC Formatter

•SpringMVC类型转换器提供了一个统一的ConversionService API以及一个强类型的Converter SPI,用于实现从一种类型另一种类型转换逻辑。例如,将Short强制转换为Long。

•在Spring MVC中,HTTP中的源数据都是String类型,数据绑定需要将String转换为其他类型,同时也可能需要将数据转换为具有本地格式的字符串样式进行展示,而Converter SPI不能直接满足这种格式要求。

•Spring 3 引入了一个方便的Formatter SPI,当在客户端环境(如Web应用程序)中工作并且需要解析和输出本地化字段值时,可以使用Formatter SPI。

(1)使用Converter转换
public class StringToDateConverter implements Converter<String, Date> {
    private String pattern;
    public StringToDateConverter(String pattern) {
        this.pattern = pattern;
    }
    @Override
    public Date convert(String source) {
        try {
            SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
            dateFormat.setLenient(false);
            return dateFormat.parse(source);
        } catch (ParseException e) {
            throw new IllegalArgumentException("invalid date format. Please use this pattern\"" + pattern + "\"");
        }
    }
}

注册bean

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="edu.cqie.ssm.converter.StringToDateConverter">
                <constructor-arg name="pattern" value="yyyy-MM-dd"/>
            </bean>
        </set>
    </property>
</bean>

<mvc:annotation-driven conversion-service="conversionService"/>
(2)使用Formatter转换
public class DateFormatter implements Formatter<Date> {
    private SimpleDateFormat sdf;
    public DateFormatter(String pattern) {
        this.sdf = new SimpleDateFormat(pattern);
        sdf.setLenient(false);
    }
    @Override
    public Date parse(String s, Locale locale) throws IllegalArgumentException {
        try {
            return this.sdf.parse(s);
        } catch (ParseException e) {
            throw new IllegalArgumentException("invalid date format. Please use this pattern\"" + this.sdf.toPattern() + "\"");
        }
    }
    @Override
    public String print(Date date, Locale locale) {
        return this.sdf.format(date);
    }
}


<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
        <set>
            <bean class="edu.cqie.ssm.formatter.DateFormatter">
                <constructor-arg name="pattern" value="yyyy-MM-dd"/>
            </bean>
        </set>
    </property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"/>
(3)内置Formatter转换器
类型 说明
NumberFormatter 实现 Number 与 String 之间的解析与格式化
CurrencyFormatter 实现 Number 与 String 之间的解析与格式化(带货币符号)
PercentFormatter 实现 Number 与 String 之间的解析与格式化(带百分数符号)
DateFormatter 实现 Date 与 String 之间的解析与格式化

两个格式化注解

•@NumberFormat

•@DateTimeFormat

使用注解格式化

@DateTimeFormat(pattern="yyyy/MM/dd")
private Date birthday;

@NumberFormat(style = NumberFormat.Style.CURRENCY )
private Double balance;//货币金额  ¥5000

@NumberFormat(pattern = "#,###.##")
private Double salary; //工资  10,000.00

@NumberFormat(style = NumberFormat.Style.PERCENT) 
private Double percent;//不加%按p*100来显示,加上按提交精度来显示

3.国际化

什么是国际化?

国际化(也叫 i18n),由于国际化英文单词是 internationalization,在 i 和 n 之间有 18 个字母,因此国际化又叫做 i18n。国际化是指程序在不做任何修改的情况下,就可以在不同的国家或地区和不同的语言环境下,按照当地的语言和格式习惯的显示字符,例如MyBatis官方网站等。

国际化设置场景:

•Spring标签国际化

•接口方法国际化

LocaleResolver

•AcceptHeaderLocaleResolver:根据请求头中的 Accept-Language 字段来确定当前的区域语言。

•SessionLocaleResolver:根据请求参数来确定区域语言,确定后会保存在 Session 中,只要 Session不变,Locale 对象就一直有效。

•CookieLocaleResolver:根据请求参数来确定区域语言,确定后会保存在Cookie中,只要Cookie不变Locale对象就一直有效。

•FixedLocaleResolver:配置时直接提供一个 Locale 对象,以后不能修改。


(1)AcceptHeaderLocaleResolver

步骤一:添加多语言配置文件


步骤二:修改****spring-mvc.xml

<bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="i18n.message"/>
    <property name="defaultEncoding" value="UTF-8"/>
</bean>

步骤三:在页面中引用

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
    <title><spring:message code="login.title"/></title>
</head>
<body>
    <form action="toRegister" method="post">
        <div><spring:message code="login.content.title"/></div>
        <div>
            <span><spring:message code="login.account"/></span>
            <input type="tel" name="phone" maxlength="11" placeholder='<spring:message code="login.account.holder"/>'/>
        </div>
        <div>
            <span><spring:message code="login.password"/></span>
            <input type="password" name="pwd" minlength="8" maxlength="20" placeholder='<spring:message code="login.password.holder"/>'/>
        </div>
        <div>
            <input type="submit" value='<spring:message code="login.submit"/>'/>
        </div>
    </form>
</body>
</html>

步骤四:在接口中引用

@Controller
public class LoginController {
    @Autowired
    MessageSource messageSource;
    @GetMapping("/login")
    public String login() {
        String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale());
        String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale());
        System.out.println("username = " + username);
        System.out.println("password = " + password);
        return "login";
    }
}
(2)SessionLocaleResolver

**步骤一:添加多语言配置文件(与上面一样)

步骤二:修改****spring-mvc.xml

<bean id="messageSource"
          class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="i18n.message"/>
    <property name="defaultEncoding" value="UTF-8"/>
</bean>
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
            <property name="paramName" value="locale"/>
        </bean>
    </mvc:interceptor>
</mvc:interceptors>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
</bean>

步骤三:在接口中使用

@Controller
public class LoginController {
    @Autowired
    MessageSource messageSource;
    @GetMapping("/login")
    public String login(String locale,HttpSession session) {
        if ("zh-CN".equals(locale)) {
            session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, new Locale("zh", "CN"));
        } else if ("en-US".equals(locale)) {
            session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, new Locale("en", "US"));
        }
        String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale());
        String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale());
        System.out.println("username = " + username);
        System.out.println("password = " + password);
        return "login";
    }
}
(3)CookieLocaleResolver

**步骤一:添加多语言配置文件(与上面一样)

步骤二:修改****spring-mvc.xml

<bean id="messageSource"
   class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="i18n.message"/>
    <property name="defaultEncoding" value="UTF-8"/>
</bean>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

步骤三:在接口中使用

@GetMapping("/login")
public String login(String locale, HttpServletRequest req, HttpServletResponse resp) {
    CookieLocaleResolver resolver = new CookieLocaleResolver();
    if ("zh-CN".equals(locale)) {
        resolver.setLocale(req, resp, new Locale("zh", "CN"));
    } else if ("en-US".equals(locale)) {
        resolver.setLocale(req, resp, new Locale("en", "US"));
    }
    String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale());
    String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale());
    System.out.println("username = " + username);
    System.out.println("password = " + password);
    return "login";
}


    resolver.setLocale(req, resp, new Locale("zh", "CN"));
} else if ("en-US".equals(locale)) {
    resolver.setLocale(req, resp, new Locale("en", "US"));
}
String username = messageSource.getMessage("login.username", null, LocaleContextHolder.getLocale());
String password = messageSource.getMessage("login.password", null, LocaleContextHolder.getLocale());
System.out.println("username = " + username);
System.out.println("password = " + password);
return "login";

}

复制代码
相关推荐
Mr_Xuhhh27 分钟前
递归搜索与回溯算法
c语言·开发语言·c++·算法·github
文军的烹饪实验室28 分钟前
ValueError: Circular reference detected
开发语言·前端·javascript
B20080116刘实33 分钟前
CTF攻防世界小白刷题自学笔记13
开发语言·笔记·web安全·网络安全·php
Qter_Sean1 小时前
自己动手写Qt Creator插件
开发语言·qt
何曾参静谧2 小时前
「QT」文件类 之 QIODevice 输入输出设备类
开发语言·qt
爱吃生蚝的于勒3 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
小白学大数据4 小时前
Python爬虫开发中的分析与方案制定
开发语言·c++·爬虫·python
失落的香蕉5 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
红中马喽6 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习