【问题标题】:Support only a limited number of languages仅支持有限数量的语言
【发布时间】:2016-08-20 15:45:05
【问题描述】:

我的 API 使用 Jersey 2,现在我想支持国际化。我知道我的客户应该指定Accept-Language 参数,但我想了解如何正确处理它。

假设我的 API 应该只处理 FRENCHENGLISH 语言。我知道我可以使用以下代码检索 首选 语言环境:

@GET
@Path("a-path")
public Response doSomething(@Context HttpServletRequest request) {
    Locale locale = request.getLocale();
    // ...
}

问题是我的 API 不支持 首选 语言环境。假设我的客户发给我Accept-Language: da, en-gb;q=0.8, en;q=0.7,根据w3c,它基本上意味着:"I prefer Danish, but will accept British English and other types of English."。由于 preferred 语言环境只返回最期望的语言环境,有没有办法通过我的 API 选择第一个支持的语言?我想在一个地方(即Filters)而不是在所有资源中处理它。

【问题讨论】:

    标签: java jersey servlet-filters jersey-2.0 http-accept-language


    【解决方案1】:

    获取语言环境的方法之一是使用HttpHeaders#getAcceptableLanguages()

    获取响应可接受的语言列表。

    如果未指定可接受的语言,则返回包含单个通配符 Locale 实例(语言字段设置为“*”)的只读列表。

    返回: 根据 q 值排序的可接受语言的只读列表,优先级最高。

    你几乎可以在任何地方注入HttpHeaders,使用@Context

    public Response doSomething(@Context HttpHeaders headers) {
        List<Locale> langs = headers.getAcceptableLanguages();
    

    如果您想在filter 中获取列表,您还可以从ContainerRequestContext 中获取语言环境列表列表

    @Override
    public void filter(ContainerRequestContext requestContext) throw .. {
        List<Locales> langs = requestContext.getAcceptableLanguages();
    }
    

    如果你想在资源方法中使用Locale,但又不想在方法中做所有的语言环境“解析”,你可以使用一些依赖注入,并创建一个Factory,在那里你可以注入他HttpHeaders 并在那里解析语言环境

    另请参阅: Dependency injection with Jersey 2.0

    下面是一个完整的测试用例示例,它结合了我提到的关于使用过滤器和沿Factory 的依赖注入的最后两点,这样您就可以将解析的Locale 注入资源方法中。该示例使用仅允许英语的虚拟语言环境解析器。解析语言环境后,我们将其设置为请求上下文属性,并从 Factory 内部检索,以便将其注入资源方法中

    @GET
    public String get(@Context Locale locale) {
       return locale.toString();
    }
    

    另请参阅: How to inject an object into jersey request context?

    如果您希望我解释有关示例的其他任何内容,请告诉我

    import java.io.IOException;
    import java.util.List;
    import java.util.Locale;
    import java.util.logging.Logger;
    import javax.inject.Inject;
    import javax.inject.Singleton;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.container.ContainerRequestContext;
    import javax.ws.rs.container.ContainerRequestFilter;
    import javax.ws.rs.container.PreMatching;
    import javax.ws.rs.core.Context;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.ext.Provider;
    
    import org.glassfish.hk2.api.Factory;
    import org.glassfish.hk2.utilities.binding.AbstractBinder;
    import org.glassfish.jersey.filter.LoggingFilter;
    import org.glassfish.jersey.process.internal.RequestScoped;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.glassfish.jersey.test.JerseyTest;
    import org.junit.Test;
    
    import static org.hamcrest.CoreMatchers.is;
    import static org.junit.Assert.assertThat;
    
    /**
     * Stack Overflow question https://stackoverflow.com/q/36871274/2587435
     * 
     * Run this like any other JUnit test. Only one required test dependency:
     * 
     *  <dependency>
     *      <groupId>org.glassfish.jersey.test-framework.providers</groupId>
     *      <artifactId>jersey-test-framework-provider-inmemory</artifactId>
     *      <version>${jersey2.version}</version>
     *  </dependency>
     *
     * @author Paul Samsotha
     */
    public class AcceptLanguageTest extends JerseyTest {
        
        @Path("language")
        public static class TestResource {
           
            @GET
            public String get(@Context Locale locale) {
               return locale.toString();
            }
        }
        
        public static interface LocaleResolver {
            Locale resolveLocale(List<Locale> locales);
        }
        
        // Note: if you look in the javadoc for getAcceptableLanguages()
        // you will notice that it says if there is not acceptable language
        // specified, that there is a default single wildcard (*) locale.
        // So this implementation sucks, as it doesn't check for that.
        // You will want to make sure to do so!
        public static class DefaultLocaleResolver implements LocaleResolver {
            
            @Override
            public Locale resolveLocale(List<Locale> locales) {
                if (locales.contains(Locale.ENGLISH)) {
                    return Locale.ENGLISH;
                }
                return null;
            }
        }
        
        @Provider
        @PreMatching
        public static class LocaleResolverFilter implements ContainerRequestFilter {
            
            static final String LOCALE_PROPERTY = "LocaleResolverFilter.localProperty";
            
            @Inject
            private LocaleResolver localeResolver;
            
            @Override
            public void filter(ContainerRequestContext context) throws IOException {
                List<Locale> locales = context.getAcceptableLanguages();
                Locale locale = localeResolver.resolveLocale(locales);
                if (locale == null) {
                    context.abortWith(Response.status(Response.Status.NOT_ACCEPTABLE).build());
                    return;
                }
                context.setProperty(LOCALE_PROPERTY, locale);
            }
        }
        
        public static class LocaleFactory implements Factory<Locale> {
            
            @Context
            private ContainerRequestContext context;
    
            @Override
            public Locale provide() {
                return (Locale) context.getProperty(LocaleResolverFilter.LOCALE_PROPERTY);
            }
    
            @Override
            public void dispose(Locale l) {} 
        }
        
        @Override
        public ResourceConfig configure() {
            return new ResourceConfig(TestResource.class)
                    .register(LocaleResolverFilter.class)
                    .register(new AbstractBinder() {
                        @Override
                        protected void configure() {
                            bindFactory(LocaleFactory.class)
                                    .to(Locale.class).in(RequestScoped.class);
                            bind(DefaultLocaleResolver.class)
                                    .to(LocaleResolver.class).in(Singleton.class);
                        }
                    })
                    .register(new LoggingFilter(Logger.getAnonymousLogger(), true));
        }
        
        @Test
        public void shouldReturnEnglish() {
            final String accept = "da, en-gb;q=0.8, en;q=0.7";
            final Response response = target("language").request()
                    .acceptLanguage(accept)
                    .get();
            assertThat(response.readEntity(String.class), is("en"));
        }
        
        @Test
        public void shouldReturnNotAcceptable() {
            final String accept = "da";
            final Response response = target("language").request()
                    .acceptLanguage(accept)
                    .get();
            assertThat(response.getStatus(), is(Response.Status.NOT_ACCEPTABLE.getStatusCode()));
        }
    }
    

    【讨论】:

      【解决方案2】:

      JAX-RS API 允许您使用 Request.selectVariant(List) 方法选择区域设置。

      在 REST 处理程序或 CDI bean 中尝试以下代码:

      import javax.ws.rs.core.Variant;
      import javax.ws.rs.core.Request;
      
          @Context
          private Request req;
      
          private Locale getResponseLocale(boolean throwIfNoneMatch) throws NotAcceptableException{
              // Put your supported languages here
              List<Variant> langVariants = Variant.languages(
                      new Locale("da"), 
                      new Locale("en-gb"), 
                      Locale.getDefault()).build();
      
              Locale locale = Locale.getDefault();
              Variant selectVariant = this.req.selectVariant(langVariants);
              if (selectVariant != null) {
                  locale = selectVariant.getLanguage();
              } else if (throwIfNoneMatch) {
                  throw new NotAcceptableException(Response.notAcceptable(langVariants).build());
              }
      
              return locale;
          }
      

      另见The Java EE Tutorial : Runtime Content Negotiation

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-02-08
        • 1970-01-01
        • 2016-10-22
        • 2019-07-10
        • 2011-11-08
        • 2018-03-17
        • 1970-01-01
        相关资源
        最近更新 更多