快捷搜索:  汽车  科技

rest服务实现方案的特点(分布式系统核心)

rest服务实现方案的特点(分布式系统核心)从项目结构上来看,jersey-rest项目就是一个普通的Maven项目,拥有pom.xml文件、源代码目录以及测试目录。整体项目结构如下。我们可以用文本编辑器打开项目源代码,或者导入自己熟悉的IDE中来观察整个项目。我们使用Jersey提供的maven archetype来创建一个项目。只需执行下面的命令。mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false -DgroupId=com. waylau.jersey -DartifactId=jersey-rest -Dpackage=com.waylau.jersey -DarchetypeVersi

实战:基于Java实现REST API

本节,将基于市面上最为流行的3款Java框架——Jersey、ApacheCXF、Spring Web MVC——来分别演示如何实现REST API。

基于Jersey来构建REST服务

下面,我们将演示如何基于Jersey来构建REST服务。

1.创建一个新项目

使用Maven的工程创建一个Jersey项目是最方便的,下面我们将演示用这种方法来看一下它是怎么实现的。我们将创建一个新的Jersey项目,并运行在Grizzly容器里。

我们使用Jersey提供的maven archetype来创建一个项目。只需执行下面的命令。

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false -DgroupId=com. waylau.jersey -DartifactId=jersey-rest -Dpackage=com.waylau.jersey -DarchetypeVersion=2.30

这样,就完成了自动创建一个jersey-rest项目的过程。

2.探索项目

我们可以用文本编辑器打开项目源代码,或者导入自己熟悉的IDE中来观察整个项目。

从项目结构上来看,jersey-rest项目就是一个普通的Maven项目,拥有pom.xml文件、源代码目录以及测试目录。整体项目结构如下。

jersey-rest │ pom.xml│ └─ src ├─ main │ └─ java │ └─ com │ └─ waylau │ └─ jersey │ Main.java │ MyResource.java │ └─ test └─ java └─ com └─ waylau └─ jersey MyResourceTest.java

其中,pom.xml定义内容如下。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.waylau.jersey</groupId> <artifactId>jersey-rest</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>jersey-rest</name> <dependencyManagement> <dependencies> <dependency> <groupId>org.glassfish.jersey</groupId> <artifactId>jersey-bom</artifactId> <version>${jersey.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-grizzly2-http</artifactId> </dependency> <dependency> <groupId>org.glassfish.jersey.inject</groupId> <artifactId>jersey-hk2</artifactId> </dependency> <!-- uncomment this to get JSON support: <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-json-binding</artifactId> </dependency>--> <dependency> <groupId>JUnit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.5.1</version> <inherited>true</inherited> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <goals> <goal>java</goal> </goals> </execution> </executions> <configuration> <mainClass>com.waylau.jersey.Main</mainClass> </configuration> </plugin> </plugins> </build> <properties> <jersey.version>2.30</jersey.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> </project>

还有一个Main类,主要是负责承接Grizzly容器,同时也为这个容器配置和部署JAX-RS应用。

package com.waylau.jersey; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.server.ResourceConfig; import java.io.IOException; import java.net.URI; /*** Main class. * */ public class Main { // Base URI the Grizzly HTTP server will listen on public static final String BASE_URI = "http://localhost:8080/myapp/"; /** * Starts Grizzly HTTP server exposing JAX-RS resources defined in this application. * @return Grizzly HTTP server. */ public static HttpServer startServer() { // create a resource config that scans for JAX-RS resources and providers // in com.waylau.jersey package final ResourceConfig rc = new ResourceConfig().packages("com.waylau.jersey"); // create and start a new instance of grizzly http server // exposing the Jersey application at BASE_URI return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI) rc); } /** * Main method. * @param args * @throws IOException */ public static void main(String[] args) throws IOException { final HttpServer server = startServer(); System.out.println(String.format("Jersey app started with WADL available at " "%sapplication.wadl\nHit enter to stop it..." BASE_URI)); System.in.read(); server.stop(); } }

MyResource是一个资源类,定义了所有REST API服务。

package com.waylau.jersey; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; /** * Root resource (exposed at "myresource" path) */ @Path("myresource") public class MyResource { /** * Method handling HTTP GET requests. The returned object will be sent * to the client as "text/plain" media type. * * @return String that will be returned as a text/plain response. */ @GET @Produces(MediaType.TEXT_PLAIN) public String getIt() { return "Got it!";} }

在我们的例子中,MyResource资源暴露了一个公开的方法,能够处理绑定在“/myresource”URI路径下的HTTP GET请求,并可以产生媒体类型为“text/plain”的响应消息。在这个示例中,资源返回相同的“Gotit!”应对所有客户端的要求。

在src/test/java目录下的MyResourceTest类是对MyResource的单元测试,它们具有相同的包名“com.waylau.jersey”。

package com.waylau.jersey; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.WebTarget; import org.glassfish.grizzly.http.server.HttpServer; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; public class MyResourceTest { private HttpServer server; private WebTarget target; @Before public void setUp() throws Exception { // start the server server = Main.startServer(); // create the client Client c = ClientBuilder.newClient(); // uncomment the following line if you want to enable // support for JSON in the client (you also have to uncomment // dependency on jersey-media-json module in pom.xml and Main.startServer()) // -- // c.configuration().enable(new org.glassfish.jersey.media.json. JsonJaxbFeature()); target = c.target(Main.BASE_URI); } @After public void tearDown() throws Exception { server.stop(); } /** * Test to see that the message "Got it!" is sent in the response. */ @Test public void testGetIt() { String responseMsg = target.path("myresource").request().get(String.class); assertEquals("Got it!" responseMsg); } }

在这个单元测试中,测试用到了JUnit,静态方法Main.startServer首先将Grizzly容器启动,而后服务器应用部署到测试中的setUp方法。接下来,一个JAX-RS客户端组件在相同的测试方法中创建,先是一个新的JAX-RS客户端实例生成,接着JAX-RS web target部件指向我们部署的应用程序上下文的根“http://localhost:8080/myapp/”(Main.BASE_URI的常量值)。

在testGetIt方法中,JAX-RS客户端API用来连接并发送HTTP GET请求到MyResource资源类所侦听的/myresource的URI。在测试方法的第2行,响应的内容(从服务器返回的字符串)与测试断言预期短语进行比较。

3.运行项目

有了项目,进入项目的根目录先测试运行。

$ mvn clean test

如果一切正常,能在控制台看到以下输出内容。

D:\workspaceGithub\distributed-java\samples\jersey-rest>mvn clean test [INFO] Scanning for projects... [INFO] [INFO] ---------------< com.waylau.jersey:jersey-rest >------------ [INFO] Building jersey-rest 1.0-SNAPSHOT [INFO] ----------------------------[ jar ]------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ jersey-rest --- [INFO] Deleting D:\workspaceGithub\distributed-java\samples\jersey-rest\target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ jersey-rest --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.waylau.jersey.MyResourceTest 1月 20 2020 10:08:26 下午 org.glassfish.grizzly.http.server.NetworkListener start 信息: Started listener bound to [localhost:8080]1月 20 2020 10:08:26 下午 org.glassfish.grizzly.http.server.HttpServer start 信息: [HttpServer] Started. 1月 20 2020 10:08:27 下午 org.glassfish.grizzly.http.server.NetworkListener shutdownNow 信息: Stopped listener bound to [localhost:8080] Tests run: 1 Failures: 0 Errors: 0 Skipped: 0 Time elapsed: 1.526 sec Results : Tests run: 1 Failures: 0 Errors: 0 Skipped: 0 [INFO] ------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------ [INFO] Total time: 6.726 s [INFO] Finished at: 2020-01-20T22:08:27 08:00 [INFO] ------------------------------------------------------------

为了节省篇幅,上述代码只保留了输出的核心内容。

测试通过,下面我们用标准模式运行项目。

$ mvn exec:java

运行结果如下。

D:\workspaceGithub\distributed-java\samples\jersey-rest>mvn exec:java [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.waylau.jersey:jersey-rest >-------- [INFO] Building jersey-rest 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------- [INFO] [INFO] >>> exec-maven-plugin:1.2.1:java (default-cli) > validate @ jersey-rest >>> [INFO] [INFO] <<< exec-maven-plugin:1.2.1:java (default-cli) < validate @ jersey-rest <<< [INFO] [INFO] [INFO] --- exec-maven-plugin:1.2.1:java (default-cli) @ jersey-rest --- Downloading from nexus-aliyun: http://maven.aliyun.com/nexus/content/groups/public/ org/apache/commons/commons-exec/1.1/commons-exec-1.1.pom Downloaded from nexus-aliyun: http://maven.aliyun.com/nexus/content/groups/public/ org/apache/commons/commons-exec/1.1/commons-exec-1.1.pom (11 kB at 9.1 kB/s) Downloading from nexus-aliyun: http://maven.aliyun.com/nexus/content/groups/public/ org/apache/commons/commons-exec/1.1/commons-exec-1.1.jar Downloaded from nexus-aliyun: http://maven.aliyun.com/nexus/content/groups/public/ org/apache/commons/commons-exec/1.1/commons-exec-1.1.jar (53 kB at 95 kB/s) 1月 20 2020 10:10:15 下午 org.glassfish.grizzly.http.server.NetworkListener start 信息: Started listener bound to [localhost:8080] 1月 20 2020 10:10:15 下午 org.glassfish.grizzly.http.server.HttpServer start 信息: [HttpServer] Started. Jersey app started with WADL available at http://localhost:8080/myapp/application.wadl

Hit enter to stop it...项目已经运行,项目的WADL描述存放于http://localhost:8080/myapp/application.wadl的URI中,将该URI在控制台以curl命令执行或者在浏览器中运行,就能看到该WADL描述以XML格式展示。

<application xmlns="http://wadl.dev.java.net/2009/02"> <doc xmlns:jersey="http://jersey.java.net/" jersey:generatedBy="Jersey: 2.30 2020-01-10 07:34:57"/> <doc xmlns:jersey="http://jersey.java.net/" jersey:hint="This is simplified WADL with user and core resources only. To get full WADL with extended resources use the query parameter detail. Link: http://localhost:8080/myapp/application.wadl?detail=true"/> <grammars/> <resources base="http://localhost:8080/myapp/"> <resource path="myresource"> <method id="getIt" name="GET"> <response> <representation mediaType="text/plain"/> </response> </method> </resource> </resources> </application>

接下来,我们可以尝试与部署在/myresource下面的资源进行交互。

将资源的URL输入浏览器,或者在控制台用curl命令执行,可以看到如下内容输出。

$ curl http://localhost:8080/myapp/myresource Got it!

可以看到,使用Jersey构建REST服务非常简便。它内嵌Grizzly容器,可以使应用自启动,而无须部署到额外的容器中,非常适合构建微服务。

本节示例,可以在jersey-rest项目下找到。

基于Apache CXF来构建REST服务

下面,我们将演示如何基于Apache CXF来构建REST服务。

1.创建一个新项目

使用Maven的工程创建一个Apache CXF项目是最方便的。与创建Jersey项目类似,我们使用Apache CXF提供的maven archetype来创建一个项目。只需执行下面的命令。

mvn archetype:generate -DarchetypeArtifactId=cxf-jaxrs-service -DarchetypeGroupId= org.apache.cxf.archetype -DgroupId=com.waylau.cxf -DartifactId=cxf-rest -Dpackage=com. waylau.cxf -DarchetypeVersion=3.3.5

这就完成了自动创建一个cxf-rest项目的过程。

2.探索项目

我们可以用文本编辑器打开项目源代码,或者导入自己熟悉的IDE中来观察整个项目。

从项目结构上来看,cxf-rest项目就是一个普通的Maven项目,拥有pom.xml文件、源代码目录以及测试目录。整体项目结构如下。

cxf-rest │ pom.xml │ ├─ .settings └─ src ├─ main │ ├─ java │ │ └─ com │ │ └─ waylau │ │ └─ cxf │ │ HelloWorld.java │ │ JsonBean.java │ │ │ └─ webapp │ ├─ META-INF │ │ context.xml │ │ │ └─ WEB-INF │ beans.xml │ web.xml │└─ test └─ java └─ com └─ waylau └─ cxf HelloWorldIT.java 其中,pom.xml定义内容如下。 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.waylau.cxf</groupId> <artifactId>cxf-rest</artifactId> <version>Y</version> <packaging>war</packaging> <name>Simple CXF JAX-RS webapp service using spring configuration</name> <description>Simple CXF JAX-RS webapp service using spring configuration </description> <properties> <jackson.version>1.8.6</jackson.version> </properties> <dependencies> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxrs</artifactId> <version>3.3.5</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-rs-client</artifactId> <version>3.3.5</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-core-asl</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-jaxrs</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId><version>5.1.12.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.0</version> <executions> <execution> <id>default-cli</id> <goals> <goal>run</goal> </goals> <configuration> <port>13000</port> <path>/jaxrs-service</path> <useSeparateTomcatClassLoader>true </useSeparateTomcatClassLoader> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <configuration> <projectNameTemplate>[artifactId]-[version]</projectNameTemplate> <wtpmanifest>true</wtpmanifest> <wtpapplicationxml>true</wtpapplicationxml> <wtpversion>2.0</wtpversion> </configuration> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>1.5</version> <executions><execution> <id>reserve-network-port</id> <goals> <goal>reserve-network-port</goal> </goals> <phase>process-test-resources</phase> <configuration> <portNames> <portName>test.server.port</portName> </portNames> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <executions> <execution> <id>start-tomcat</id> <goals> <goal>run-war</goal> </goals> <phase>pre-integration-test</phase> <configuration> <port>${test.server.port}</port> <path>/jaxrs-service</path> <fork>true</fork> <useSeparateTomcatClassLoader>true </useSeparateTomcatClassLoader> </configuration> </execution> <execution> <id>stop-tomcat</id> <goals> <goal>shutdown</goal> </goals> <phase>post-integration-test</phase> <configuration> <path>/jaxrs-service</path> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>2.22.2</version> <executions> <execution> <id>integration-test</id> <goals> <goal>integration-test</goal> </goals> <configuration> <systemPropertyVariables> <service.url>http://localhost:${test.server.port}/jaxrs-service</service.url> </systemPropertyVariables> </configuration> </execution> <execution> <id>verify</id> <goals> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>

从依赖配置可以看出,这个项目相对jersey-rest而言,依赖了比较多的第三方框架,如Spring、Jackson、Tomcat等。

这是一个典型的Java EE项目,所以有一个web.xml文件来配置应用。

<?xml version="1.0" encoding="utf-8"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <display-name>JAX-RS Simple Service</display-name> <description>JAX-RS Simple Service</description> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/beans.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class> org.apache.cxf.transport.servlet.CXFServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>

同时,cxf-rest是依赖于Spring框架来提供bean实例的管理,所以上下文配置在beans.xml中。

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd"> <import resource="classpath:META-INF/cxf/cxf.xml"/> <context:property-placeholder/> <context:annotation-config/> <bean class="org.springframework.web.context.support. ServletContextPropertyPlaceholderConfigurer"/> <bean class="org.springframework.beans.factory.config. PreferencesPlaceholderConfigurer"/> <jaxrs:server id="services" address="/"> <jaxrs:serviceBeans> <bean class="com.waylau.cxf.HelloWorld"/> </jaxrs:serviceBeans> <jaxrs:providers> <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/> </jaxrs:providers> </jaxrs:server> </beans>

com.waylau.cxf.HelloWorld就是提供REST服务的资源类。

package com.waylau.cxf; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Response; @Path("/hello") public class HelloWorld { @GET @Path("/echo/{input}") @Produces("text/plain") public String ping(@PathParam("input") String input) { return input; } @POST @Produces("application/json") @Consumes("application/json")@Path("/jsonBean") public Response modifyJson(JsonBean input) { input.setVal2(input.getVal1()); return Response.ok().entity(input).build(); } }

该资源类定义了两个REST API。其中:

·GET/hello/echo/{input}将会返回input变量的内容。 ·POST/hello/jsonBean则是返回传入的JsonBean对象。 JsonBean就是一个典型的POJO。 package com.waylau.cxf; public class JsonBean { private String val1; private String val2; public String getVal1() { return val1; } public void setVal1(String val1) { this.val1 = val1; } public String getVal2() { return val2; } public void setVal2(String val2) { this.val2 = val2; } }

HelloWorldIT是应用的测试类。

package com.waylau.cxf; import static org.junit.Assert.assertEquals; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.ws.rs.core.Response; import org.apache.cxf.helpers.IOUtils; import org.apache.cxf.jaxrs.client.WebClient; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.map.MappingJsonFactory; import org.junit.BeforeClass; import org.junit.Test; public class HelloWorldIT { private static String endpointUrl; @BeforeClass public static void beforeClass() {endpointUrl = System.getProperty("service.url"); } @Test public void testPing() throws Exception { WebClient client = WebClient.create(endpointUrl "/hello/echo/SierraTangoNevada"); Response r = client.accept("text/plain").get(); assertEquals(Response.Status.OK.getStatusCode() r.getStatus()); String value = IOUtils.toString((InputStream)r.getEntity()); assertEquals("SierraTangoNevada" value); } @Test public void testJsonRoundtrip() throws Exception { List<Object> providers = new ArrayList<>(); providers.add(new org.codehaus.jackson.jaxrs.JacksonJsonProvider()); JsonBean inputBean = new JsonBean(); inputBean.setVal1("Maple"); WebClient client = WebClient.create(endpointUrl "/hello/jsonBean" providers); Response r = client.accept("application/json") .type("application/json") .post(inputBean); assertEquals(Response.Status.OK.getStatusCode() r.getStatus()); MappingJsonFactory factory = new MappingJsonFactory(); JsonParser parser = factory.createJsonParser((InputStream)r.getEntity()); JsonBean output = parser.readValueAs(JsonBean.class); assertEquals("Maple" output.getVal2()); } }

3.运行项目

进入项目的根目录先测试运行。

$ mvn clean test

如果一切正常,能在控制台看到以下输出内容。

D:\workspaceGithub\distributed-java\samples\cxf-rest>mvn clean test [INFO] Scanning for projects... [INFO] [INFO] ------------------< com.waylau.cxf:cxf-rest >--------------- [INFO] Building Simple CXF JAX-RS webapp service using spring configuration Y [INFO] ----------------------------[ war ]------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ cxf-rest --- [INFO] Deleting D:\workspaceGithub\distributed-java\samples\cxf-rest\target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ cxf-rest ---[WARNING] Using platform encoding (GBK actually) to copy filtered resources i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\workspaceGithub\distributed-java\ samples\cxf-rest\src\main\resources [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ cxf-rest --- [INFO] Changes detected - recompiling the module! [WARNING] File encoding has not been set using platform encoding GBK i.e. build is platform dependent! [INFO] Compiling 2 source files to D:\workspaceGithub\distributed-java\samples\ cxf-rest\target\classes [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ cxf-rest --- [WARNING] Using platform encoding (GBK actually) to copy filtered resources i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\workspaceGithub\distributed-java\samples\ cxf-rest\src\test\resources [INFO] ... [INFO] Reserved port 49189 for test.server.port [INFO] [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ cxf-rest --- [INFO] Changes detected - recompiling the module! [WARNING] File encoding has not been set using platform encoding GBK i.e. build is platform dependent! [INFO] Compiling 1 source file to D:\workspaceGithub\distributed-java\samples\ cxf-rest\target\test-classes [INFO] [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ cxf-rest --- [INFO] ------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------ [INFO] Total time: 19.335 s [INFO] Finished at: 2020-01-20T21:59:53 08:00 [INFO] ------------------------------------------------------------

为了节省篇幅,上述代码只保留了输出的核心内容。

由于项目内嵌了Tomcat运行插件,所以,可以直接执行以下命令来启动项目。

$ mvn tomcat7:run

运行结果如下。

D:\workspaceGithub\cloud-native-book-demos\samples\ch02\cxf-rest>mvn tomcat7:run [INFO] Scanning for projects... [INFO] [INFO] ------------------------------------------------------------[INFO] Building Simple CXF JAX-RS webapp service using spring configuration 1.0-SNAPSHOT [INFO] ------------------------------------------------------------ [INFO] [INFO] >>> tomcat7-maven-plugin:2.0:run (default-cli) > compile @ cxf-rest >>> [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ cxf-rest --- [WARNING] Using platform encoding (GBK actually) to copy filtered resources i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\workspaceGithub\cloud-native-book-demos\ samples\ch02\cxf-rest\src\main\resources [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ cxf-rest --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] <<< tomcat7-maven-plugin:2.0:run (default-cli) < compile @ cxf-rest <<< [INFO] [INFO] [INFO] --- tomcat7-maven-plugin:2.0:run (default-cli) @ cxf-rest --- [INFO] Running war on http://localhost:13000/jaxrs-service [INFO] Using existing Tomcat server configuration at D:\workspaceGithub\cloud-native- book-demos\samples\ch02\cxf-rest\target\tomcat [INFO] create webapp with contextPath: /jaxrs-service 六月 07 2019 12:18:11 上午 org.apache.coyote.AbstractProtocol init 信息: Initializing ProtocolHandler ["http-bio-13000"] 六月 07 2019 12:18:11 上午 org.apache.catalina.core.StandardService startInternal 信息: Starting service Tomcat 六月 07 2019 12:18:11 上午 org.apache.catalina.core.StandardEngine startInternal 信息: Starting Servlet Engine: Apache Tomcat/7.0.30 六月 07 2019 12:18:13 上午 org.apache.catalina.core.ApplicationContext log 信息: No Spring WebApplicationInitializer types detected on classpath 六月 07 2019 12:18:13 上午 org.apache.catalina.core.ApplicationContext log 信息: Initializing Spring root WebApplicationContext 六月 07 2019 12:18:13 上午 org.springframework.web.context.ContextLoader initWebApplicationContext 信息: Root WebApplicationContext: initialization started 六月 07 2019 12:18:13 上午 org.springframework.context.support. AbstractApplicationContext prepareRefresh 信息: Refreshing Root WebApplicationContext: startup date [Thu Jun 07 00:18:13 CST 2018]; root of context hierarchy 六月 07 2019 12:18:13 上午 org.springframework.beans.factory.xml. XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from ServletContext resource [/WEB-INF/beans.xml] 六月 07 2019 12:18:13 上午 org.springframework.beans.factory.xml. XmlBeanDefinitionReader loadBeanDefinitions 信息: Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml] 六月 07 2019 12:18:14 上午 org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor <init> 信息: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring 六月 07 2019 12:18:14 上午 org.apache.cxf.endpoint.ServerImpl initDestination 信息: Setting the server's publish address to be / 六月 07 2019 12:18:14 上午 org.springframework.web.context.ContextLoader initWebApplicationContext 信息: Root WebApplicationContext: initialization completed in 1083 ms 六月 07 2019 12:18:14 上午 org.apache.coyote.AbstractProtocol start 信息: Starting ProtocolHandler ["http-bio-13000"]项目启动后,就可以尝试与部署在“/hello”下面的资源进行交互。

将资源的URL输入浏览器,或者在控制台用curl命令执行,可以看到以下内容输出。

$ curl http://localhost:13000/jaxrs-service/hello/echo/waylau waylau $ curl -H "Content-type: application/json" -X POST -d '{"val1":"hello" "val2":"world"}' http://localhost:13000/jaxrs-service/hello/jsonBean {"val1": "hello" "val2": "hello"}

官方提供的Maven项目源代码存在bug,执行过程中可能存在错误。读者可以参阅笔者修改后的源代码内容。

本小节示例,可以在cxf-rest项目下找到。

基于Spring Web MVC来构建REST服务

下面将演示如何通过Spring Web MVC来实现REST服务。

1.接口设计

我们将创建一个名为spring-rest的项目,实现简单的REST风格的API。

我们将会在系统中实现两个API。

·GET http://localhost:8080/hello。

·GET http://localhost:8080/hello/way。

其中,第一个接口“/hello”将会返回“Hello World!”的字符串;而第二个接口“/hello/way”则会返回一个包含用户信息的JSON字符串。

2.创建一个新项目

新创建的spring-rest项目,pom.xml配置如下。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.waylau.spring</groupId> <artifactId>spring-rest</artifactId> <version>1.0.0</version> <name>spring-rest</name> <packaging>jar</packaging> <organization> <name>waylau.com</name> <url>https://waylau.com</url> </organization> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>5.0.6.RELEASE</spring.version> <jetty.version>9.4.10.v20180503</jetty.version> <jackson.version>2.9.5</jackson.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> <version>${jetty.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> </dependencies> </project>

其中:

·spring-webmvc是为了使用Spring MVC的功能。

·jetty-servlet是为了提供内嵌的Servlet容器,这样我们就无须依赖外部的容器,可以直接运行我们的应用。

·jackson-core和jackson-databind为我们的应用提供JSON序列化的功能。

创建一个User类,代表用户信息。User是一个POJO类。

public class User { private String username; private Integer age; public User(String username Integer age) { this.username = username; this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }

创建HelloController用于处理用户的请求。

@RestController public class HelloController { @RequestMapping("/hello") public String hello() { return "Hello World! Welcome to visit waylau.com!"; } @RequestMapping("/hello/way") public User helloWay() { return new User("Way Lau" 30); } }

其中,映射到“/hello”的方法将会返回“Hello World!”的字符串;而映射到“/hello/way”的方法则会返回一个包含用户信息的JSON字符串。

3.应用配置

在本项目中,我们采用基于Java注解的配置。

AppConfiguration是我们的主应用配置。

import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @Configuration @ComponentScan(basePackages = { "com.waylau.spring" }) @Import({ MvcConfiguration.class }) public class AppConfiguration { }

AppConfiguration会扫描“com.waylau.spring”包下的文件,并自动将相关的bean进行注册。

AppConfiguration同时又引入了MVC的配置类MvcConfiguration。 @EnableWebMvc @Configuration public class MvcConfiguration implements WebMvcConfigurer { public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new MappingJackson2HttpMessageConverter()); } }

MvcConfiguration配置类一方面启用了MVC的功能,另一方面添加了Jackson JSON的转换器。

最后,我们需要引入Jetty服务器JettyServer。

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.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; import com.waylau.spring.mvc.configuration.AppConfiguration; public class JettyServer { private static final int DEFAULT_PORT = 8080; private static final String CONTEXT_PATH = "/"; private static final String MAPPING_URL = "/*"; public void run() throws Exception { Server server = new Server(DEFAULT_PORT); server.setHandler(servletContextHandler(webApplicationContext())); server.start(); server.join(); } private ServletContextHandler servletContextHandler( WebApplicationContext context) { ServletContextHandler handler = new ServletContextHandler();handler.setContextPath(CONTEXT_PATH); handler.addServlet(new ServletHolder(new DispatcherServlet(context)) MAPPING_URL); handler.addEventListener(new ContextLoaderListener(context)); return handler; } private WebApplicationContext webApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(AppConfiguration.class); return context; } } JettyServer将在Application类中启动。 public class Application { public static void main(String[] args) throws Exception { new JettyServer().run();; } }

4.运行项目

在编辑器中,直接运行Application类即可。启动之后,应能看到以下控制台信息。

2018-06-07 22:19:23.572:INFO::main: Logging initialized @305ms to org.eclipse. jetty.util.log.StdErrLog 2018-06-07 22:19:23.982:INFO:oejs.Server:main: jetty-9.4.10.v20180503; built: 2018-05-03T15:56:21.710Z; git: daa59876e6f384329b122929e70a80934569428c; jvm 1.8.0_162-b12 2018-06-07 22:19:24.096:INFO:oejshC.ROOT:main: Initializing Spring root WebApplicationContext 六月 07 2018 10:19:24 下午 org.springframework.web.context.ContextLoader initWebApplicationContext 信息: Root WebApplicationContext: initialization started 六月 07 2018 10:19:24 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh 信息: Refreshing Root WebApplicationContext: startup date [Thu Jun 07 22:19:24 CST 2018]; root of context hierarchy 六月 07 2018 10:19:24 下午 org.springframework.web.context.support. AnnotationConfigWebApplicationContext loadBeanDefinitions 信息: Registering annotated classes: [class com.waylau.spring.mvc.configuration. AppConfiguration] 六月 07 2018 10:19:25 下午 org.springframework.web.servlet.handler. AbstractHandlerMethodMapping$MappingRegistry register 信息: Mapped "{[/hello]}" onto public java.lang.String com.waylau.spring.mvc. controller.HelloController.hello() 六月 07 2018 10:19:25 下午 org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry register 信息: Mapped "{[/hello/way]}" onto public com.waylau.spring.mvc.vo.User com.waylau. spring.mvc.controller.HelloController.helloWay() 六月 07 2018 10:19:26 下午 org.springframework.web.servlet.mvc.method.annotation. RequestMappingHandlerAdapter initControllerAdviceCache 信息: Looking for @ControllerAdvice: Root WebApplicationContext: startup date [Thu Jun 07 22:19:24 CST 2018]; root of context hierarchy 六月 07 2018 10:19:26 下午 org.springframework.web.context.ContextLoader initWebApplicationContext 信息: Root WebApplicationContext: initialization completed in 2073 ms 2018-06-07 22:19:26.191:INFO:oejshC.ROOT:main: Initializing Spring FrameworkServlet 'org.springframework.web.servlet.DispatcherServlet-246ae04d' 六月 07 2018 10:19:26 下午 org.springframework.web.servlet.FrameworkServlet initServletBean 信息: FrameworkServlet 'org.springframework.web.servlet.DispatcherServlet-246ae04d': initialization started 六月 07 2018 10:19:26 下午 org.springframework.web.servlet.FrameworkServlet initServletBean 信息: FrameworkServlet 'org.springframework.web.servlet.DispatcherServlet-246ae04d': initialization completed in 31 ms 2018-06-07 22:19:26.226:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@4ae9cfc1{/ null AVAILABLE} 2018-06-07 22:19:26.610:INFO:oejs.AbstractConnector:main: Started ServerConnector@5bf0fe62{HTTP/1.1 [http/1.1]}{0.0.0.0:8080} 2018-06-07 22:19:26.611:INFO:oejs.Server:main: Started @3346ms

分别在浏览器中访问“http://localhost:8080/hello”和“http://localhost:8080/hello/way”地址进行测试,能看到图8-2和图8-3所示的响应效果。

rest服务实现方案的特点(分布式系统核心)(1)

图8-2 “/hello”接口的返回内容

rest服务实现方案的特点(分布式系统核心)(2)

图8-3 “/hello/way”接口的返回内容

本小节示例,可以在spring-rest项目下找到。

本篇小结

本章介绍了REST风格的架构,其中包括REST风格的概念、REST设计原则、REST成熟度模型、REST API管理等方面的内容。同时,针对Java领域,着重讲解了Java实现REST所需要的常用技术,并列举了丰富的案例。

本文给大家讲解的内容是分布式系统核心:REST风格架构实战:基于Java实现REST API
  1. 下篇文章给大家讲解的是分布式系统核心:微服务架构;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!

猜您喜欢: