在 Jersey @Path 和 @ApplicationPath 中使用 Spring 属性 占位符
Using Spring property placeholders with Jersey @Path and @ApplicationPath
我在项目中使用 Jersey 和 Spring。 'jersey-spring3'用于它们之间的整合。我想让我的资源 类 更灵活,并在 @Path
注释中使用属性,例如:
@Path("${some.property}/abc/def")
但是Spring无法将some.property
注入Jersey的注解@Path
和@ApplicationPath
。
有没有什么方法可以在 Jersey 资源的 @Path
值中包含一些可配置的(使用 属性 文件)值?
(我意识到用 Spring MVC 替换 Jersey 会更容易,但不幸的是,就我而言,我没有这个选择。)
所以这是一半的答案(或者可能是完整的答案,这取决于解决 @ApplicationPath
对您的重要性)。
要理解下面的解决方案,您应该首先了解一些 Jersey 的内部结构。当我们加载应用程序时,Jersey 会构建所有资源的模型。资源的所有信息都封装在这个模型中。 Jersey 使用这个模型来处理请求,而不是尝试在每个请求上处理资源,将有关资源的所有信息保存在模型中并处理模型会更快。
有了这个架构,Jersey 还允许我们 build resources programmatically, using the same APIs that it uses internally to hold the model properties. Aside from just building resource models, we can also modify existing models, using a ModelProcessor
s。
在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
s
这是上面Resource
的childResource
,因为它被注解了@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
*
* 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
的方法,但它涉及创建 Jersey servlet 容器
以编程方式在 Spring WebAppInitializer
中。老实说,我认为这会比它的价值更麻烦。我只是接受它,并将 @ApplicationPath
保留为静态字符串。
UDPATE
如果你使用的是Spring引导,那么应用程序路径肯定是可以配置的,通过spring.jersey.applicationPath
属性。 Spring 引导加载 Jersey 的方式几乎就是我在上一段中想到的想法,您自己在其中创建 Jersey servlet 容器,并设置 servlet 映射。这就是使用 Spring Boot.
进行配置的方式
我在项目中使用 Jersey 和 Spring。 'jersey-spring3'用于它们之间的整合。我想让我的资源 类 更灵活,并在 @Path
注释中使用属性,例如:
@Path("${some.property}/abc/def")
但是Spring无法将some.property
注入Jersey的注解@Path
和@ApplicationPath
。
有没有什么方法可以在 Jersey 资源的 @Path
值中包含一些可配置的(使用 属性 文件)值?
(我意识到用 Spring MVC 替换 Jersey 会更容易,但不幸的是,就我而言,我没有这个选择。)
所以这是一半的答案(或者可能是完整的答案,这取决于解决 @ApplicationPath
对您的重要性)。
要理解下面的解决方案,您应该首先了解一些 Jersey 的内部结构。当我们加载应用程序时,Jersey 会构建所有资源的模型。资源的所有信息都封装在这个模型中。 Jersey 使用这个模型来处理请求,而不是尝试在每个请求上处理资源,将有关资源的所有信息保存在模型中并处理模型会更快。
有了这个架构,Jersey 还允许我们 build resources programmatically, using the same APIs that it uses internally to hold the model properties. Aside from just building resource models, we can also modify existing models, using a ModelProcessor
s。
在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
s这是上面
Resource
的childResource
,因为它被注解了@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
*
* 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
的方法,但它涉及创建 Jersey servlet 容器
以编程方式在 Spring WebAppInitializer
中。老实说,我认为这会比它的价值更麻烦。我只是接受它,并将 @ApplicationPath
保留为静态字符串。
UDPATE
如果你使用的是Spring引导,那么应用程序路径肯定是可以配置的,通过spring.jersey.applicationPath
属性。 Spring 引导加载 Jersey 的方式几乎就是我在上一段中想到的想法,您自己在其中创建 Jersey servlet 容器,并设置 servlet 映射。这就是使用 Spring Boot.