Skip to content

Commit 339f3b7

Browse files
author
Dave Syer
committed
Add autoconfig support for Jersey (2)
Jersey 2 has some Spring support built in but it's a bit awkward to use in a Boot app, so autoconfiguration seems appropriate. The tests and sample show how to use it, but the short story is that any @component can define JAX-RS endpoints via @get etc. There's a sample for Jersey 1 as well (pay careful attention to the plugin configuration if you want to build an executable jar) Fixes spring-projectsgh-1651
1 parent de8a2a7 commit 339f3b7

File tree

19 files changed

+770
-0
lines changed

19 files changed

+770
-0
lines changed

spring-boot-autoconfigure/pom.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@
6060
<artifactId>flyway-core</artifactId>
6161
<optional>true</optional>
6262
</dependency>
63+
<dependency>
64+
<groupId>org.glassfish.jersey.core</groupId>
65+
<artifactId>jersey-server</artifactId>
66+
<optional>true</optional>
67+
</dependency>
68+
<dependency>
69+
<groupId>org.glassfish.jersey.containers</groupId>
70+
<artifactId>jersey-container-servlet-core</artifactId>
71+
<optional>true</optional>
72+
</dependency>
73+
<dependency>
74+
<groupId>org.glassfish.jersey.containers</groupId>
75+
<artifactId>jersey-container-servlet</artifactId>
76+
<optional>true</optional>
77+
</dependency>
78+
<dependency>
79+
<groupId>org.glassfish.jersey.ext</groupId>
80+
<artifactId>jersey-spring3</artifactId>
81+
<optional>true</optional>
82+
</dependency>
6383
<dependency>
6484
<groupId>commons-dbcp</groupId>
6585
<artifactId>commons-dbcp</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2012-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jersey;
18+
19+
import javax.annotation.PostConstruct;
20+
import javax.servlet.ServletContext;
21+
import javax.servlet.ServletException;
22+
import javax.servlet.ServletRegistration;
23+
import javax.ws.rs.ApplicationPath;
24+
25+
import org.glassfish.jersey.server.ResourceConfig;
26+
import org.glassfish.jersey.server.spring.SpringComponentProvider;
27+
import org.glassfish.jersey.servlet.ServletContainer;
28+
import org.glassfish.jersey.servlet.ServletProperties;
29+
import org.springframework.beans.factory.ListableBeanFactory;
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
33+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
34+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
35+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
36+
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
37+
import org.springframework.boot.context.embedded.ServletRegistrationBean;
38+
import org.springframework.context.annotation.Bean;
39+
import org.springframework.context.annotation.Configuration;
40+
import org.springframework.core.Ordered;
41+
import org.springframework.core.annotation.AnnotationUtils;
42+
import org.springframework.core.annotation.Order;
43+
import org.springframework.web.WebApplicationInitializer;
44+
import org.springframework.web.filter.RequestContextFilter;
45+
46+
/**
47+
* @author Dave Syer
48+
*
49+
*/
50+
@Configuration
51+
@ConditionalOnClass({ SpringComponentProvider.class, ServletRegistration.class })
52+
@ConditionalOnBean(ResourceConfig.class)
53+
@ConditionalOnWebApplication
54+
@Order(Ordered.HIGHEST_PRECEDENCE)
55+
@AutoConfigureBefore(DispatcherServletAutoConfiguration.class)
56+
public class JerseyAutoConfiguration implements WebApplicationInitializer {
57+
58+
@Autowired
59+
private ListableBeanFactory context;
60+
61+
@Autowired
62+
private ResourceConfig config;
63+
64+
private String path;
65+
66+
@PostConstruct
67+
public void path() {
68+
path = findPath(AnnotationUtils.findAnnotation(config.getClass(),
69+
ApplicationPath.class));
70+
}
71+
72+
@Bean
73+
@ConditionalOnMissingBean
74+
public RequestContextFilter requestContextFilter() {
75+
return new RequestContextFilter();
76+
}
77+
78+
@Bean
79+
@ConditionalOnMissingBean(name = "jerseyServletRegistration")
80+
public ServletRegistrationBean jerseyServletRegistration() {
81+
Class<? extends ResourceConfig> configType = config.getClass();
82+
ServletRegistrationBean registration = new ServletRegistrationBean(
83+
new ServletContainer(), path);
84+
registration.addInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS,
85+
configType.getName());
86+
registration.setName("jerseyServlet");
87+
return registration;
88+
}
89+
90+
@Override
91+
public void onStartup(ServletContext servletContext) throws ServletException {
92+
// We need to switch *off* the Jersey WebApplicationInitializer because it
93+
// will try and register a ContextLoaderListener which we don't need
94+
servletContext.setInitParameter("contextConfigLocation", "<NONE>");
95+
}
96+
97+
private static String findPath(ApplicationPath annotation) {
98+
// Jersey doesn't like to be the default servlet, so map to /* as a fallback
99+
if (annotation == null) {
100+
return "/*";
101+
}
102+
String path = annotation.value();
103+
return path.isEmpty() || path.equals("/") ? "/*" : path + "/*";
104+
}
105+
106+
}

spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchAutoConfigurat
3434
org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchDataAutoConfiguration,\
3535
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
3636
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
37+
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
3738
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
3839
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
3940
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2012-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jersey;
18+
19+
import static org.junit.Assert.assertEquals;
20+
21+
import java.lang.annotation.Documented;
22+
import java.lang.annotation.ElementType;
23+
import java.lang.annotation.Retention;
24+
import java.lang.annotation.RetentionPolicy;
25+
import java.lang.annotation.Target;
26+
27+
import javax.ws.rs.ApplicationPath;
28+
import javax.ws.rs.GET;
29+
import javax.ws.rs.Path;
30+
31+
import org.glassfish.jersey.server.ResourceConfig;
32+
import org.junit.Test;
33+
import org.junit.runner.RunWith;
34+
import org.springframework.beans.factory.annotation.Value;
35+
import org.springframework.boot.SpringApplication;
36+
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
37+
import org.springframework.boot.autoconfigure.jersey.CustomServletPathTests.Application;
38+
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
39+
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
40+
import org.springframework.boot.test.IntegrationTest;
41+
import org.springframework.boot.test.SpringApplicationConfiguration;
42+
import org.springframework.boot.test.TestRestTemplate;
43+
import org.springframework.context.annotation.Import;
44+
import org.springframework.http.HttpStatus;
45+
import org.springframework.http.ResponseEntity;
46+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
47+
import org.springframework.test.context.web.WebAppConfiguration;
48+
import org.springframework.web.client.RestTemplate;
49+
50+
@RunWith(SpringJUnit4ClassRunner.class)
51+
@SpringApplicationConfiguration(classes = Application.class)
52+
@IntegrationTest("server.port=0")
53+
@WebAppConfiguration
54+
public class CustomServletPathTests {
55+
56+
@Value("${local.server.port}")
57+
private int port;
58+
59+
private RestTemplate restTemplate = new TestRestTemplate();
60+
61+
@Test
62+
public void contextLoads() {
63+
ResponseEntity<String> entity = restTemplate.getForEntity("http://localhost:" + port + "/rest/hello", String.class);
64+
assertEquals(HttpStatus.OK, entity.getStatusCode());
65+
}
66+
67+
@MinimalWebConfiguration
68+
@ApplicationPath("/rest")
69+
@Path("/hello")
70+
public static class Application extends ResourceConfig {
71+
72+
@Value("${message:World}")
73+
private String msg;
74+
75+
@GET
76+
public String message() {
77+
return "Hello " + msg;
78+
}
79+
80+
public Application() {
81+
register(Application.class);
82+
}
83+
84+
public static void main(String[] args) {
85+
SpringApplication.run(Application.class, args);
86+
}
87+
88+
}
89+
90+
@Target(ElementType.TYPE)
91+
@Retention(RetentionPolicy.RUNTIME)
92+
@Documented
93+
@Import({ EmbeddedServletContainerAutoConfiguration.class,
94+
ServerPropertiesAutoConfiguration.class, JerseyAutoConfiguration.class,
95+
PropertyPlaceholderAutoConfiguration.class })
96+
protected static @interface MinimalWebConfiguration {
97+
}
98+
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2012-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jersey;
18+
19+
import static org.junit.Assert.assertEquals;
20+
21+
import java.lang.annotation.Documented;
22+
import java.lang.annotation.ElementType;
23+
import java.lang.annotation.Retention;
24+
import java.lang.annotation.RetentionPolicy;
25+
import java.lang.annotation.Target;
26+
27+
import javax.ws.rs.GET;
28+
import javax.ws.rs.Path;
29+
30+
import org.glassfish.jersey.server.ResourceConfig;
31+
import org.junit.Test;
32+
import org.junit.runner.RunWith;
33+
import org.springframework.beans.factory.annotation.Value;
34+
import org.springframework.boot.SpringApplication;
35+
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
36+
import org.springframework.boot.autoconfigure.jersey.DefaultServletPathTests.Application;
37+
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
38+
import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration;
39+
import org.springframework.boot.test.IntegrationTest;
40+
import org.springframework.boot.test.SpringApplicationConfiguration;
41+
import org.springframework.boot.test.TestRestTemplate;
42+
import org.springframework.context.annotation.Import;
43+
import org.springframework.http.HttpStatus;
44+
import org.springframework.http.ResponseEntity;
45+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
46+
import org.springframework.test.context.web.WebAppConfiguration;
47+
import org.springframework.web.client.RestTemplate;
48+
49+
@RunWith(SpringJUnit4ClassRunner.class)
50+
@SpringApplicationConfiguration(classes = Application.class)
51+
@IntegrationTest("server.port=0")
52+
@WebAppConfiguration
53+
public class DefaultServletPathTests {
54+
55+
@Value("${local.server.port}")
56+
private int port;
57+
58+
private RestTemplate restTemplate = new TestRestTemplate();
59+
60+
@Test
61+
public void contextLoads() {
62+
ResponseEntity<String> entity = restTemplate.getForEntity("http://localhost:"
63+
+ port + "/hello", String.class);
64+
assertEquals(HttpStatus.OK, entity.getStatusCode());
65+
}
66+
67+
@MinimalWebConfiguration
68+
@Path("/hello")
69+
public static class Application extends ResourceConfig {
70+
71+
@Value("${message:World}")
72+
private String msg;
73+
74+
public Application() {
75+
register(Application.class);
76+
}
77+
78+
@GET
79+
public String message() {
80+
return "Hello " + msg;
81+
}
82+
83+
public static void main(String[] args) {
84+
SpringApplication.run(Application.class, args);
85+
}
86+
87+
}
88+
89+
@Target(ElementType.TYPE)
90+
@Retention(RetentionPolicy.RUNTIME)
91+
@Documented
92+
@Import({ EmbeddedServletContainerAutoConfiguration.class,
93+
ServerPropertiesAutoConfiguration.class, JerseyAutoConfiguration.class,
94+
PropertyPlaceholderAutoConfiguration.class })
95+
protected static @interface MinimalWebConfiguration {
96+
}
97+
98+
}

spring-boot-dependencies/pom.xml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
<jetty-jsp.version>2.2.0.v201112011158</jetty-jsp.version>
8686
<jaxen.version>1.1.6</jaxen.version>
8787
<jdom2.version>2.0.5</jdom2.version>
88+
<jersey.version>2.7</jersey.version>
8889
<joda-time.version>2.4</joda-time.version>
8990
<jolokia.version>1.2.2</jolokia.version>
9091
<json-path.version>0.9.1</json-path.version>
@@ -261,6 +262,11 @@
261262
<artifactId>spring-boot-starter-jdbc</artifactId>
262263
<version>1.2.0.BUILD-SNAPSHOT</version>
263264
</dependency>
265+
<dependency>
266+
<groupId>org.springframework.boot</groupId>
267+
<artifactId>spring-boot-starter-jersey</artifactId>
268+
<version>1.2.0.BUILD-SNAPSHOT</version>
269+
</dependency>
264270
<dependency>
265271
<groupId>org.springframework.boot</groupId>
266272
<artifactId>spring-boot-starter-jetty</artifactId>
@@ -856,6 +862,26 @@
856862
<artifactId>flyway-core</artifactId>
857863
<version>${flyway.version}</version>
858864
</dependency>
865+
<dependency>
866+
<groupId>org.glassfish.jersey.core</groupId>
867+
<artifactId>jersey-server</artifactId>
868+
<version>${jersey.version}</version>
869+
</dependency>
870+
<dependency>
871+
<groupId>org.glassfish.jersey.containers</groupId>
872+
<artifactId>jersey-container-servlet-core</artifactId>
873+
<version>${jersey.version}</version>
874+
</dependency>
875+
<dependency>
876+
<groupId>org.glassfish.jersey.containers</groupId>
877+
<artifactId>jersey-container-servlet</artifactId>
878+
<version>${jersey.version}</version>
879+
</dependency>
880+
<dependency>
881+
<groupId>org.glassfish.jersey.ext</groupId>
882+
<artifactId>jersey-spring3</artifactId>
883+
<version>${jersey.version}</version>
884+
</dependency>
859885
<dependency>
860886
<groupId>org.hamcrest</groupId>
861887
<artifactId>hamcrest-core</artifactId>

spring-boot-samples/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
<module>spring-boot-sample-flyway</module>
3838
<module>spring-boot-sample-hornetq</module>
3939
<module>spring-boot-sample-integration</module>
40+
<module>spring-boot-sample-jersey</module>
41+
<module>spring-boot-sample-jersey1</module>
4042
<module>spring-boot-sample-jetty</module>
4143
<module>spring-boot-sample-jta-atomikos</module>
4244
<module>spring-boot-sample-jta-bitronix</module>

0 commit comments

Comments
 (0)