使用带有Jersey @Path和@ApplicationPath的Spring属性占位符

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

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

但Spring无法向Jersey的注释@Path@ApplicationPath注入some.property

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

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

所以这里只有答案的一半(或者可能是一个完整的答案取决于解决@ApplicationPath对你的重要性)。

要了解以下解决方案,您首先应该了解一下泽西的内部情况。 当我们加载我们的应用程序时,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() {} } 

    没有使用@Path注释的资源方法是ResourceMethod

  • 这是上述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: * *  * org.glassfish.jersey.test-framework.providers * jersey-test-framework-provider-grizzly2 * 2.22.1 * test *  *  * org.glassfish.jersey.ext * jersey-spring3 * 2.22.1 * test *  *  * commons-logging * commons-logging * 1.1 * test *  * * @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 ,但它涉及在Spring WebAppInitializer中以编程方式创建Jersey servlet容器。 老实说,我认为这比它的价值更麻烦。 我只是把它@ApplicationPath了,把@ApplicationPath静态字符串。


UDPATE

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