一尘不染

将Spring属性占位符与Jersey @Path和@ApplicationPath一起使用

spring-mvc

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

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

但是Spring无法注入some.propertyJersey的注释@Path@ApplicationPath

有什么方法可以@Path在Jersey资源的值内包含一些可配置的(使用属性文件)值?

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


阅读 624

收藏
2020-06-01

共1个答案

一尘不染

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

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

借助这种架构,Jersey还允许我们使用与内部使用的相同API
来以编程方式构建资源,以保存模型属性。除了 构建 资源模型之外,我们还可以使用来 修改
现有模型
ModelProcessor

在中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() {}
}

注释的资源方法@PathResourceMethods

  • 这是上述内容的 孩子 ResourceResource因为带有注释@Path
@GET
@Path("child-resource")
public String get() {}

此信息应使您对上述实现的工作原理有所了解。

以下是使用Jersey测试框架的完整测试。使用以下类路径属性文件

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();
    }
}

同样,现在我考虑了一下,我可以看到一种解决resolve的方法@ApplicationPath,但是它涉及在Spring中以编程方式创建Jersey
servlet容器WebAppInitializer。老实说,我认为这会带来更多麻烦。我会把它吸起来,然后将其保留@ApplicationPath为静态字符串。


UDPATE

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

2020-06-01