对于服务器核心Java开发人员而言,向世界“展示”的唯一方法是使用API。 今天的帖子都是关于JAX-RS的 :使用Java编写和公开RESTful服务。
但是,我们不会使用涉及应用程序服务器,WAR打包以及诸如此类的传统的重量级方法来做到这一点。 相反,我们将使用很棒的Apache CXF框架,并且一如既往地依靠Spring将所有组件连接在一起。 当然,我们也不会停止,因为我们需要一个Web服务器来运行我们的服务。 使用胖子或一个罐子的概念,我们将Jetty服务器嵌入到我们的应用程序中,并使最终的JAR可重新分发(包括所有依赖项)并且可运行。
这是很多工作,所以让我们开始吧。 如前所述,我们将使用Apache CXF , Spring和Jetty作为构建块,因此让我们在POM文件中对其进行描述。 值得一提的另一个依赖项是出色的用于JSON处理的Jackson库。
4.0.0
com.example
spring-one-jar
0.0.1-SNAPSHOT
jar
UTF-8
2.7.2
3.2.0.RELEASE
8.1.8.v20121106
org.apache.cxf
cxf-rt-frontend-jaxrs
${org.apache.cxf.version}
javax.inject
javax.inject
1
org.codehaus.jackson
jackson-jaxrs
1.9.11
org.codehaus.jackson
jackson-mapper-asl
1.9.11
cglib
cglib-nodep
2.2
org.springframework
spring-core
${org.springframework.version}
org.springframework
spring-context
${org.springframework.version}
org.springframework
spring-web
${org.springframework.version}
org.eclipse.jetty
jetty-server
${org.eclipse.jetty.version}
org.eclipse.jetty
jetty-webapp
${org.eclipse.jetty.version
org.apache.maven.plugins
maven-compiler-plugin
3.0
1.6
org.apache.maven.plugins
maven-jar-plugin
com.example.Starter
org.dstovall
onejar-maven-plugin
1.4.4
0.97
onejar
one-jar
onejar-maven-plugin.googlecode.com
http://onejar-maven-plugin.googlecode.com/svn/mavenrepo
maven2-repository.dev.java.net
http://download.java.net/maven/2/
它有很多东西,但是应该很清楚。 现在,我们准备从简单的JAX-RS应用程序开始开发第一个JAX-RS服务。
package com.example.rs;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath( 'api' )
public class JaxRsApiApplication extends Application {
}
看起来很简单,我们的应用程序将/ api定义为JAX-RS服务的入口路径。 样本服务将管理由Person类代表的人员 。
package com.example.model;
public class Person {
private String email;
private String firstName;
private String lastName;
public Person() {
}
public Person( final String email ) {
this.email = email;
}
public String getEmail() {
return email;
}
public void setEmail( final String email ) {
this.email = email;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public void setFirstName( final String firstName ) {
this.firstName = firstName;
}
public void setLastName( final String lastName ) {
this.lastName = lastName;
}
}
并遵循基本的业务服务(为简单起见,不包括数据库或任何其他存储)。
package com.example.services;
import java.util.ArrayList;
import java.util.Collection;
import org.springframework.stereotype.Service;
import com.example.model.Person;
@Service
public class PeopleService {
public Collection< Person > getPeople( int page, int pageSize ) {
Collection< Person > persons = new ArrayList< Person >( pageSize );
for( int index = 0; index < pageSize; ++index ) {
persons.add( new Person( String.format( 'person+%[email protected]', ( pageSize * ( page - 1 ) + index + 1 ) ) ) );
}
return persons;
}
public Person addPerson( String email ) {
return new Person( email );
}
}
如您所见,我们将根据请求的页面动态生成人员列表。 标准Spring注释@Service将此类标记为服务bean。 我们的JAX-RS服务PeopleRestService将使用它来检索人员,如以下代码所示。
package com.example.rs;
import java.util.Collection;
import javax.inject.Inject;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import com.example.model.Person;
import com.example.services.PeopleService;
@Path( '/people' )
public class PeopleRestService {
@Inject private PeopleService peopleService;
@Produces( { 'application/json' } )
@GET
public Collection< Person > getPeople( @QueryParam( 'page') @DefaultValue( '1' ) final int page ) {
return peopleService.getPeople( page, 5 );
}
@Produces( { 'application/json' } )
@PUT
public Person addPerson( @FormParam( 'email' ) final String email ) {
return peopleService.addPerson( email );
}
}
尽管很简单,但该类需要更多说明。 首先,我们要向/ people端点公开我们的RESTful服务。 将其与/ api (我们的JAX-RS应用程序所在的位置)组合,则以/ api / people作为限定路径给出。
现在,只要有人向该路径发出HTTP GET ,就应该调用方法getPeople 。 此方法接受可选的参数页面 (默认值为1),并以JSON形式返回人员列表。 反过来,如果有人向同一路径发出HTTP PUT ,则应调用addPerson方法(使用必需的参数email ),并以JSON形式返回new person。
现在,让我们看一下应用程序的核心Spring配置。
package com.example.config;
import java.util.Arrays;
import javax.ws.rs.ext.RuntimeDelegate;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.codehaus.jackson.jaxrs.JacksonJsonProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.rs.JaxRsApiApplication;
import com.example.rs.PeopleRestService;
import com.example.services.PeopleService;
@Configuration
public class AppConfig {
@Bean( destroyMethod = 'shutdown' )
public SpringBus cxf() {
return new SpringBus();
}
@Bean
public Server jaxRsServer() {
JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint( jaxRsApiApplication(), JAXRSServerFactoryBean.class );
factory.setServiceBeans( Arrays.< Object >asList( peopleRestService() ) );
factory.setAddress( '/' + factory.getAddress() );
factory.setProviders( Arrays.< Object >asList( jsonProvider() ) );
return factory.create();
}
@Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
@Bean
public PeopleRestService peopleRestService() {
return new PeopleRestService();
}
@Bean
public PeopleService peopleService() {
return new PeopleService();
}
@Bean
public JacksonJsonProvider jsonProvider() {
return new JacksonJsonProvider();
}
}
它看起来并不复杂,但是在后台却发生了很多事情。 让我们将其分解为香料。 这里的两个关键组件是工厂JAXRSServerFactoryBean ,它为配置我们的JAX-RS服务器实例提供了所有繁重的工作,而SpringBus实例则将 Spring和Apache CXF无缝地粘合在一起。 所有其他组件代表常规的Spring Bean。
图片上还没有嵌入Jetty Web服务器实例。 我们的主要应用程序类Starter正是这样做的。
package com.example;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import com.example.config.AppConfig;
public class Starter {
public static void main( final String[] args ) throws Exception {
Server server = new Server( 8080 );
// Register and map the dispatcher servlet
final ServletHolder servletHolder = new ServletHolder( new CXFServlet() );
final ServletContextHandler context = new ServletContextHandler();
context.setContextPath( '/' );
context.addServlet( servletHolder, '/rest/*' );
context.addEventListener( new ContextLoaderListener() );
context.setInitParameter( 'contextClass', AnnotationConfigWebApplicationContext.class.getName() );
context.setInitParameter( 'contextConfigLocation', AppConfig.class.getName() );
server.setHandler( context );
server.start();
server.join();
}
}
查看此代码后,我们发现我们正在端口8080上运行Jetty服务器实例,我们正在配置Apache CXF servlet来处理/ rest / *路径上的所有请求(连同我们的JAX-RS应用程序和服务一起为我们提供了/ rest / api / people ),我们使用上面定义的配置添加了参数化的Spring上下文侦听器,最后启动了服务器。 此时,我们拥有的是托管我们的JAX-RS服务的功能完善的Web服务器。 让我们来看看它的作用。 首先,让我们将其包装为单个,可运行和可再分配的脂肪或一个罐子 :
mvn clean package
让我们从目标文件夹中拾取位并运行它们:
java -jar target/spring-one-jar-0.0.1-SNAPSHOT.one-jar.jar
我们应该看到这样的输出:
2013-01-19 11:43:08.636:INFO:oejs.Server:jetty-8.1.8.v20121106
2013-01-19 11:43:08.698:INFO:/:Initializing Spring root WebApplicationContext
Jan 19, 2013 11:43:08 AM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization started
Jan 19, 2013 11:43:08 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing Root WebApplicationContext: startup date [Sat Jan 19 11:43:08 EST 2013]; root of context hierarchy
Jan 19, 2013 11:43:08 AM org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider registerDefaultFilters
INFO: JSR-330 'javax.inject.Named' annotation found and supported for component scanning
Jan 19, 2013 11:43:08 AM org.springframework.web.context.support.AnnotationConfigWebApplicationContext loadBeanDefinitions
INFO: Successfully resolved class for [com.example.config.AppConfig]
Jan 19, 2013 11:43:09 AM org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
INFO: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
Jan 19, 2013 11:43:09 AM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1f8166e5:
defining beans [org.springframework.context.annotation.internal
ConfigurationAnnotationProcessor,
org.springframework.context.annotation.internalAutowiredAnnotationProcessor,
org.springframework.context.annotation.internalRequiredAnnotationProces
sor,org.springframework.context.annotation.internalCommonAnnotationProcessor,appConfig,
org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,c
xf,jaxRsServer,jaxRsApiApplication,peopleRestService,peopleService,jsonProvider]; root of factory hierarchy
Jan 19, 2013 11:43:10 AM org.apache.cxf.endpoint.ServerImpl initDestination
INFO: Setting the server's publish address to be /api
Jan 19, 2013 11:43:10 AM org.springframework.web.context.ContextLoader initWebApplicationContext
INFO: Root WebApplicationContext: initialization completed in 2227 ms
2013-01-19 11:43:10.957:INFO:oejsh.ContextHandler:started o.e.j.s.ServletContextHandler{/,null}
2013-01-19 11:43:11.019:INFO:oejs.AbstractConnector:Started [email protected]:8080
让我们的服务器启动并运行,让我们向它发出一些HTTP请求,以确保一切都按预期运行:
> curl http://localhost:8080/rest/api/people?page=2
[
{'email':'[email protected]','firstName':null,'lastName':null},
{'email':'[email protected]','firstName':null,'lastName':null},
{'email':'[email protected]','firstName':null,'lastName':null},
{'email':'[email protected]','firstName':null,'lastName':null},
{'email':'[email protected]','firstName':null,'lastName':null}
]
> curl http://localhost:8080/rest/api/people -X PUT -d '[email protected]'
{'email':'[email protected]','firstName':null,'lastName':null}
太棒了! 并且请注意,我们完全不使用XML ! 源代码: https : //github.com/reta/spring-one-jar/tree/jetty-embedded
在结束这篇文章之前,我想提到一个很棒的项目Dropwizard ,它使用了非常相似的概念,但是由于Yammer的支持,所以将其推向了出色的,精心设计的框架。
参考: 走向REST:在Andriy Redko {devmind}博客上,从我们的JCG合作伙伴 Andrey Redko 嵌入Spring和JAX-RS(Apache CXF)的Jetty 。
翻译自: https://www.javacodegeeks.com/2013/01/going-rest-embedding-jetty-with-spring-and-jax-rs-apache-cxf.html