当前位置: 代码网 > it编程>编程语言>Java > Spring国际化和Validation详解

Spring国际化和Validation详解

2024年11月25日 Java 我要评论
springboot国际化和validation融合场景在应用交互时,可能需要根据客户端得语言来返回不同的语言数据。前端通过参数、请求头等往后端传入locale相关得参数,后端获取参数,根据不同得lo

springboot国际化和validation融合

场景

在应用交互时,可能需要根据客户端得语言来返回不同的语言数据。

前端通过参数、请求头等往后端传入locale相关得参数,后端获取参数,根据不同得locale来获取不同得语言得文本信息返回给前端。

实现原理

springboot支持国际化和validation,主要通过messagesource接口和validator实现。

国际化配置‌

  • 编写国际化配置文件,如messages_en_us.propertiesmessages_zh_cn.properties,并置于resources/i18n目录下。
  • 配置application.ymlapplication.properties以指定国际化文件的位置,例如spring.messages.basename=i18n/messages
  • 配置localeresolver以解析当前请求的locale,常用的实现是acceptheaderlocaleresolver,它通过请求头accept-language获取当前的locale。

‌validation配置‌

引入spring-boot-starter-validation依赖以支持validation功能

		<dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-validation</artifactid>
        </dependency>

配置localvalidatorfactorybean以使用国际化的校验消息,需注入messagesource

示例

引入依赖

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-validation</artifactid>
</dependency>

<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-web</artifactid>
</dependency>

国际化配置文件

src/main/resources/i18n目录下创建两个文件:messages_en_us.propertiesmessages_zh_cn.properties

#messages_en_us.properties
welcome.message=welcome to our website!

#messages_zh_cn.properties
welcome.message=欢迎来到我们的网站!

配置messagesource‌

在spring boot的配置文件中(application.propertiesapplication.yml),配置messagesource以指定国际化文件的位置。

如果你打算使用validation的默认国际化文件,你实际上不需要为validation单独指定文件,因为localvalidatorfactorybean会自动查找validationmessages.properties

但是,你可以配置自己的国际化文件,并让messagesource同时服务于你的应用消息和validation消息。

# 国际化文件被放置在src/main/resources/i18n目录下,并以messages为前缀
spring.messages.basename=i18n/messages,org.hibernate.validator.validationmessages
spring.messages.encoding=utf-8

注意:上面的配置假设你的自定义消息文件位于i18n/messages.properties,而validation的默认消息文件是org.hibernate.validator.validationmessages.properties

实际上,validationmessages.properties文件位于hibernate validator的jar包中,所以你不需要显式地将它包含在你的资源目录中。spring boot会自动从classpath中加载它。

配置localvalidatorfactorybean‌

在你的配置类中,创建一个localvalidatorfactorybean的bean,并将messagesource注入到它中。

这样,localvalidatorfactorybean就会使用spring的messagesource来解析校验消息。

import org.hibernate.validator.hibernatevalidator;
import org.springframework.context.messagesource;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.validation.beanvalidation.localvalidatorfactorybean;

import java.util.properties;

@configuration
public class validateconfig {

    @bean
    public localvalidatorfactorybean validatorfactorybean(messagesource messagesource) {
        localvalidatorfactorybean factorybean = new localvalidatorfactorybean();
        factorybean.setvalidationmessagesource(messagesource);
        //        设置使用 hibernatevalidator 校验器
        factorybean.setproviderclass(hibernatevalidator.class);
//        设置 快速异常返回 只要有一个校验错误就立即返回失败,其他参数不在校验
        properties properties = new properties();
        properties.setproperty("hibernate.validator.fail_fast", "true");
        factorybean.setvalidationproperties(properties);
//        加载配置
        factorybean.afterpropertiesset();
        return factorybean;
    }
}

使用校验

import javax.validation.constraints.notnull;

public class mymodel {

    @notnull(message = "{not.null.message}")
    private string field;

    // getters and setters
}

messages.properties文件中,你可以添加

not.null.message=this field cannot be null.

而在hibernate validator的validationmessages.properties文件中,已经包含了默认的校验消息,如{javax.validation.constraints.notnull.message}的值。

自定义校验

  • 定义约束注解:创建一个注解,用@constraint标记,并定义messagegroupspayload属性
import javax.validation.constraint;
import javax.validation.payload;
import java.lang.annotation.*;


@documented
@target({elementtype.parameter, elementtype.field, elementtype.type})
@retention(retentionpolicy.runtime)
@constraint(validatedby = myvalidatecontent.class)
public @interface myvalidate {
    string message() default "";
    class<?>[] groups() default {};
    class<? extends payload>[] payload() default {};
}
  • 实现约束验证器**:创建一个实现了constraintvalidator接口的类,并重写isvalid方法
  • isvalid方法中使用constraintvalidatorcontext**‌:如果验证失败,使用constraintvalidatorcontextbuildconstraintviolationwithtemplate方法来构建constraintviolation
import com.example.dto.paramvo;

import javax.validation.constraintvalidator;
import javax.validation.constraintvalidatorcontext;


public class myvalidatecontent implements constraintvalidator<myvalidate, paramvo> {
    @override
    public void initialize(myconstraint constraintannotation) {
        // 初始化代码(如果需要的话)
    }
    @override
    public boolean isvalid(paramvo paramvo, constraintvalidatorcontext constraintvalidatorcontext) {
        if ("n".equals(paramvo.getsex())) {
            if (paramvo.getage() < 18) {
                buildmessage(constraintvalidatorcontext, "template1");
                return false;
            }
        } else {
            if (paramvo.getage() <  20) {
                buildmessage(constraintvalidatorcontext, "template2");
                return false;
            }
        }
        return true;
    }

    private void buildmessage(constraintvalidatorcontext context, string key) {
        string template = ('{'+key+'}').intern();
        context.buildconstraintviolationwithtemplate(template)
                .addconstraintviolation();
    }
}

在这个例子中,如果sexn并且age小于18,验证器将使用constraintvalidatorcontext来构建一个带有错误消息的constraintviolation

消息模板"{template1}"将会在验证失败时被解析,并替换为你在myvalidate注解中定义的默认消息或你在messages.properties文件中定义的国际化消息。

确保你的myvalidate注解定义了一个message属性,并且你在messages.properties文件中有一个对应的条目例如:

template1=男性要大于18
template2=女性要大于20
import com.example.validate.myvalidate;
import lombok.getter;
import lombok.setter;
import org.hibernate.validator.constraints.length;

import javax.validation.constraints.notblank;
import javax.validation.constraints.notnull;

@getter
@setter
@myvalidate
public class paramvo {
    @notblank(message = "{javax.validation.constraints.notnull.message}")
    private string sex;
    @notnull(message = "age 不能为空")
    private integer age;
    @notblank(message = "{name.not.null}")
    @length(max = 3,message = "{name.length.max}")
    private string name;
}

controller层异常处理

import org.springframework.http.httpstatus;
import org.springframework.http.responseentity;
import org.springframework.validation.bindingresult;
import org.springframework.validation.fielderror;
import org.springframework.web.bind.methodargumentnotvalidexception;
import org.springframework.web.bind.annotation.exceptionhandler;
import org.springframework.web.bind.annotation.restcontrolleradvice;

import java.util.hashmap;
import java.util.map;


@restcontrolleradvice
public class globalexception {
    @exceptionhandler(methodargumentnotvalidexception.class)
    public responseentity<object> handlevalidationexceptions(methodargumentnotvalidexception ex) {
        map<string, string> errors = new hashmap<>();
        bindingresult result = ex.getbindingresult();
        for (fielderror error : result.getfielderrors()) {
            errors.put(error.getfield(), error.getdefaultmessage());
        }
        // 这里可以根据实际需求定制返回的错误信息结构
        map<string, object> response = new hashmap<>();
        response.put("status", httpstatus.bad_request.value());
        response.put("errors", errors);
        return new responseentity<>(response, httpstatus.bad_request);
    }
}

内部方法校验

import org.springframework.validation.annotation.validated;
import javax.validation.constraints.min;
import javax.validation.constraints.notnull;
//@validated 
@validated
public interface serviceintface {
    //校验返回值,校验入参
    @notnull object hello(@notnull @min(10) integer id, @notnull string name);
}
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.service;

@slf4j
@service
public class serviceimpl implements serviceintface {
    @override
    public object hello(integer id, string name) {
        return null;
    }
}
import org.springframework.http.httpstatus;
import org.springframework.http.responseentity;
import org.springframework.web.bind.annotation.exceptionhandler;
import org.springframework.web.bind.annotation.restcontrolleradvice;

import javax.validation.constraintviolation;
import javax.validation.constraintviolationexception;
import java.util.hashmap;
import java.util.map;
import java.util.set;


@restcontrolleradvice
public class globalexception {

    @exceptionhandler(constraintviolationexception.class)
    public responseentity<object> handlevalidationexceptions(constraintviolationexception ex) {
        map<string, string> errors = new hashmap<>();
        set<constraintviolation<?>> constraintviolations = ex.getconstraintviolations();
        for (constraintviolation<?> constraintviolation : constraintviolations) {
            string key = constraintviolation.getpropertypath().tostring();
            string message = constraintviolation.getmessage();
            errors.put(key, message);
        }

        // 这里可以根据实际需求定制返回的错误信息结构
        map<string, object> response = new hashmap<>();
        response.put("status", httpstatus.bad_request.value());
        response.put("errors", errors);
        return new responseentity<>(response, httpstatus.bad_request);
    }
}
  • 校验写在接口上的,抛出异常javax.validation.constraintviolationexception
  • 校验写在具体实现,抛出异常javax.validation.constraintdeclarationexception

注意点

代码中国际化使用

代码里响应,手动获取使用messagesource的getmessage方法即可,也就是spring容器中的getmessage()

# messages_en_us.properties
welcome.message=welcome to our website!

# messages_zh_cn.properties
welcome.message=欢迎来到我们的网站!

#定义消息,并使用占位符{0}、{1}等表示参数位置
#welcome.message=欢迎{0}来到{1}
//创建一个配置类来配置localeresolver,以便根据请求解析当前的语言环境:
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.web.servlet.localeresolver;
import org.springframework.web.servlet.config.annotation.webmvcconfigurer;
import org.springframework.web.servlet.i18n.sessionlocaleresolver;

import java.util.locale;

@configuration
public class webconfig implements webmvcconfigurer {

    @bean
    public localeresolver localeresolver() {
        sessionlocaleresolver sessionlocaleresolver = new sessionlocaleresolver();
        sessionlocaleresolver.setdefaultlocale(locale.us); // 设置默认语言
        return sessionlocaleresolver;
    }
}
//创建一个控制器来使用国际化的消息
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.context.messagesource;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.restcontroller;

import javax.servlet.http.httpservletrequest;
import java.util.locale;

@restcontroller
@requestmapping("/hello")
public class hellocontroller {

    @autowired
    private messagesource messagesource;

    @getmapping
    public string hello(httpservletrequest request) {
        locale locale = (locale) request.getattribute(org.springframework.web.servlet.localeresolver.locale_resolver_attribute);
         //messagesource.getmessage("welcome.message", new object[]{"张三", "中国"}, locale.china)。
        return messagesource.getmessage("welcome.message", null, locale);
    }
}

locale获取

默认情况下spring注册的messagesource对象为resourcebundlemessagesource,会读取spring.message配置。

请求中locale的获取是通过localeresolver进行处理,默认是acceptheaderlocaleresolver,通过webmvcautoconfiguration注入,从accept-language请求头中获取locale信息。

此时前端可以在不同语言环境时传入不同的请求头accept-language即可达到切换语言的效果

accept-language: en-us
accept-language: zh-cn

默认情况下前端请求中的不用处理,如果约定其他信息传递local,使用自定义的i18nlocaleresolver替换默认的acceptheaderlocaleresolver,重写resolvelocale方法就可以自定义locale的解析逻辑。

import cn.hutool.core.util.strutil;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.web.servlet.localeresolver;

import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.util.locale;

/**
 *
 */
@configuration
public class i18nconfig {
    @bean
    public localeresolver localeresolver() {
        return new i18nlocaleresolver();
    }

    /**
     * 获取请求头国际化信息
     * 使用自定义的i18nlocaleresolver替换默认的acceptheaderlocaleresolver,重写resolvelocale方法就可以自定义locale的解析逻辑。
     *
     * 自定义后使用content-language传locale信息,使用_划分语言个地区。
     * content-language: en_us
     * content-language: zh_cn
     */
    static class i18nlocaleresolver implements localeresolver {

        @override
        public locale resolvelocale(httpservletrequest httpservletrequest) {
            string language = httpservletrequest.getheader("content-language");
            locale locale = locale.getdefault();
            if (strutil.isnotblank(language)) {
                string[] split = language.split("_");
                locale = new locale(split[0], split[1]);
            }
            return locale;
        }

        @override
        public void setlocale(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, locale locale) {

        }
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com