【问题标题】:Using Spring property placeholders with Jersey @Path and @ApplicationPath将 Spring 属性占位符与 Jersey @Path 和 @ApplicationPath 一起使用
【发布时间】:2016-01-22 09:51:18
【问题描述】:

我在我的项目中使用 Jersey 和 Spring。 'jersey-spring3' 用于它们之间的集成。我想让我的资源类更灵活,并在 @Path 注释中使用属性,例如:

@Path("${some.property}/abc/def")

但是 Spring 不能将 some.property 注入 Jersey 的注解 @Path@ApplicationPath

有没有办法在 Jersey 资源的 @Path 值中设置一些可配置的(使用属性文件)值?

(我意识到用 Spring MVC 替换 Jersey 会更容易,但不幸的是我没有这个选择。)

【问题讨论】:

    标签: java spring spring-mvc jersey jersey-2.0


    【解决方案1】:

    所以这是答案的一半(或者可能是完整的答案,具体取决于解决 @ApplicationPath 对您的重要性)。

    要了解以下解决方案,您应该首先了解一下 Jersey 的内部结构。当我们加载我们的应用程序时,Jersey 会构建所有资源的模型。资源的所有信息都封装在此模型中。 Jersey 使用此模型来处理请求,而不是尝试在每个请求上处理资源,将有关资源的所有信息保存在模型中并处理模型会更快。

    通过这种架构,Jersey 还允许我们使用与内部用于保存模型属性的 API 相同的 API build resources programmatically。除了构建资源模型,我们还可以modify existing models,使用ModelProcessors。

    ModelProcessor 中,我们可以注入Spring 的PropertyResolver,然后以编程方式解析占位符,并将旧的资源模型路径替换为已解析的路径。例如

    @Autowired
    private PropertyResolver propertyResolver;
    
    private ResourceModel processResourceModel(ResourceModel resourceModel) {
        ResourceModel.Builder newResourceModelBuilder = new ResourceModel.Builder(false);
        for (final Resource resource : resourceModel.getResources()) {
            final Resource.Builder resourceBuilder = Resource.builder(resource);
            String resolvedResourcePath = processPropertyPlaceholder(resource);
            resourceBuilder.path(resolvedResourcePath);
    
            // handle child resources
            for (Resource childResource : resource.getChildResources()) {
                String resolvedChildPath = processPropertyPlaceholder(childResource);
                final Resource.Builder childResourceBuilder = Resource.builder(childResource);
                childResourceBuilder.path(resolvedChildPath);
                    resourceBuilder.addChildResource(childResourceBuilder.build());
            }
            newResourceModelBuilder.addResource(resourceBuilder.build());
        }
        return newResourceModelBuilder.build();
    }
    
    private String processPropertyPlaceholder(Resource resource) {
        String ogPath = resource.getPath();
        return propertyResolver.resolvePlaceholders(ogPath);
    }
    

    就资源模型 API 而言

    • 这是Resource

      @Path("resource")
      public class SomeResource {
          @GET
          public String get() {}
      }
      

      没有注解@Path的资源方法是ResourceMethods

    • 这是上述Resource Resource,因为它带有@Path 注释。

      @GET
      @Path("child-resource")
      public String get() {}
      

    这些信息应该让您对上述实现的工作原理有所了解。

    下面是一个完整的测试,使用Jersey Test Framework。使用了以下类路径属性文件

    app.properties

    resource=resource
    sub.resource=sub-resource
    sub.resource.locator=sub-resource-locator
    

    您可以像运行任何其他 JUnit 测试一样运行以下内容。

    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.core.Response;
    
    import org.glassfish.jersey.filter.LoggingFilter;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.glassfish.jersey.server.model.ModelProcessor;
    import org.glassfish.jersey.server.model.Resource;
    import org.glassfish.jersey.server.model.ResourceModel;
    import org.glassfish.jersey.test.JerseyTest;
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.core.env.PropertyResolver;
    
    import static org.hamcrest.CoreMatchers.equalTo;
    import static org.hamcrest.CoreMatchers.is;
    import static org.junit.Assert.assertThat;
    
    /**
     * Stack Overflow http://stackoverflow.com/q/34943650/2587435
     * 
     * Run it like any other JUnit test. Required dependencies are as follows:
     * 
     * <dependency>
     *     <groupId>org.glassfish.jersey.test-framework.providers</groupId>
     *     <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
     *     <version>2.22.1</version>
     *     <scope>test</scope>
     * </dependency>
     * <dependency>
     *     <groupId>org.glassfish.jersey.ext</groupId>
     *     <artifactId>jersey-spring3</artifactId>
     *     <version>2.22.1</version>
     *     <scope>test</scope>
     * </dependency>
     * <dependency>
     *     <groupId>commons-logging</groupId>
     *     <artifactId>commons-logging</artifactId>
     *     <version>1.1</version>
     *     <scope>test</scope>
     * </dependency>
     * 
     * @author Paul Samsotha
     */
    public class SpringPathResolverTest extends JerseyTest {
    
        @Path("${resource}")
        public static class TestResource {
    
            @GET
            public String get() {
                return "Resource Success!";
            }
    
            @GET
            @Path("${sub.resource}")
            public String getSubMethod() {
                return "Sub-Resource Success!";
            }
    
            @Path("${sub.resource.locator}")
            public SubResourceLocator getSubResourceLocator() {
                return new SubResourceLocator();
            }
    
            public static class SubResourceLocator {
    
                @GET
                public String get() {
                    return "Sub-Resource-Locator Success!";
                }
            }
        }
    
        @Configuration
        @PropertySource("classpath:/app.properties")
        public static class SpringConfig {
        }
    
        public static class PropertyPlaceholderPathResolvingModelProcessor
                implements ModelProcessor {
    
            @Autowired
            private PropertyResolver propertyResolver;
    
            @Override
            public ResourceModel processResourceModel(ResourceModel resourceModel,
                    javax.ws.rs.core.Configuration configuration) {
                return processResourceModel(resourceModel);
            }
    
            @Override
            public ResourceModel processSubResource(ResourceModel subResourceModel,
                    javax.ws.rs.core.Configuration configuration) {
                return subResourceModel;
            }
    
            private ResourceModel processResourceModel(ResourceModel resourceModel) {
                ResourceModel.Builder newResourceModelBuilder = new ResourceModel.Builder(false);
                for (final Resource resource : resourceModel.getResources()) {
                    final Resource.Builder resourceBuilder = Resource.builder(resource);
                    String resolvedResourcePath = processPropertyPlaceholder(resource);
                    resourceBuilder.path(resolvedResourcePath);
    
                    // handle child resources
                    for (Resource childResource : resource.getChildResources()) {
                        String resolvedChildPath = processPropertyPlaceholder(childResource);
                        final Resource.Builder childResourceBuilder = Resource.builder(childResource);
                        childResourceBuilder.path(resolvedChildPath);
                        resourceBuilder.addChildResource(childResourceBuilder.build());
                    }
                    newResourceModelBuilder.addResource(resourceBuilder.build());
                }
                return newResourceModelBuilder.build();
            }
    
            private String processPropertyPlaceholder(Resource resource) {
                String ogPath = resource.getPath();
                return propertyResolver.resolvePlaceholders(ogPath);
            }
        }
    
        @Override
        public ResourceConfig configure() {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            return new ResourceConfig(TestResource.class)
                    .property("contextConfig", ctx)
                    .register(PropertyPlaceholderPathResolvingModelProcessor.class)
                    .register(new LoggingFilter(Logger.getAnonymousLogger(), true));
        }
    
        @Test
        public void pathPlaceholderShouldBeResolved() {
            Response response = target("resource").request().get();
            assertThat(response.getStatus(), is(200));
            assertThat(response.readEntity(String.class), is(equalTo("Resource Success!")));
            response.close();
    
            response = target("resource/sub-resource").request().get();
            assertThat(response.getStatus(), is(200));
            assertThat(response.readEntity(String.class), is(equalTo("Sub-Resource Success!")));
            response.close();
    
            response = target("resource/sub-resource-locator").request().get();
            assertThat(response.getStatus(), is(200));
            assertThat(response.readEntity(String.class), is(equalTo("Sub-Resource-Locator Success!")));
            response.close();
        }
    }
    

    现在我想起来了,我可以看到一种使用解析@ApplicationPath 的方法,但它涉及创建 Jersey servlet 容器 以编程方式在 Spring WebAppInitializer 中。老实说,我认为这将比它的价值更麻烦。我只是把它吸起来,然后将@ApplicationPath 保留为静态字符串。


    UDPATE

    如果您使用的是 Spring boot,那么应用程序路径肯定是可配置的,通过 spring.jersey.applicationPath 属性。 Spring boot 加载 Jersey 的方式几乎就是我在上一段中想到的想法,您自己创建 Jersey servlet 容器,并设置 servlet 映射。这就是 Spring Boot 的配置方式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-05
      • 2011-09-22
      • 1970-01-01
      • 1970-01-01
      • 2011-12-10
      • 1970-01-01
      • 2014-07-16
      • 2021-01-02
      相关资源
      最近更新 更多