[TOC]html
用慣了spring全家桶以後,試試dropwizard的Hello World也別有一帆風味。爲了加強對外訪問API的能力,須要引入open feign。這裏簡單在dropwizard中使用feign。java
Dropwizard is a Java framework for developing ops-friendly, high-performance, RESTful web services. react
Dropwizard
使成熟、穩定的java生態系統更加簡單、輕量(light-weight), 讓你更專一於業務邏輯。git
Dropwizard 爲配置(configuration)、統計(application metrics)、日誌(logging)、operational tools提供了開箱即用的能力。讓您和您的團隊可以在最短的時間內開發出具備生產環境的質量的Web服務。github
下面的簡介來自REST微服務架構之Dropwizardweb
DropWizard是由Yammer開發團隊貢獻的一個後臺服務開發框架,其集成了Java生態系統中各個問題域中最優秀的組件,幫助開發者快速的打造一個Rest風格的後臺服務。 spring
對開發者來講,使用DropWizard有以下好處:
一、和Maven集成良好,也就是說和Gradle集成也很良好;
二、開發迅速,部署簡單;
三、代碼結構好,可讀性高;
四、自動爲服務提供OM框架;
五、讓開發者天然的把一個應用拆分爲一個個的小服務 數據庫
DropWizard結構的Web服務組成
一、Configuration:用於設置該服務的配置,比方說在服務開放在哪一個端口,數據庫配置是怎樣的等等。
二、Application(即Service):該服務的主入口,定義該服務使用哪一個配置文件,開放哪些Resource,該服務須要哪些HealthCheck等等。
三、Resource:定義一個資源,包括如何獲取該資源,對該資源作Get/Post/Delete/Query時,對應的各類業務邏輯。
四、Representation:定義了一個服務返回值對象,當服務返回該對象時,會自動的把該對象按屬性值生成一個Json格式的字符串返回給服務調用者。
五、HealthCheck:在DropWizard爲每一個服務提供的OM框架中用到,經過它能夠隨時檢測當前服務是否可用。 apache
Web應用程序不能沒有HTTP,因此Dropwizard使用Jetty HTTP庫將一個使人難以置信的HTTP服務器直接嵌入到您的項目中。 Dropwizard項目不須要將應用程序交給一個複雜的應用程序服務器,而是一個main
方法,它會自動鏈接一個HTTP服務器。將應用程序做爲一個簡單的過程運行,消除了Java在生產中的一些很差的東西(沒有PermGen問題,沒有應用程序服務器配置和維護,沒有複雜的部署工具,沒有類加載器(class loader)故障,沒有隱藏的應用程序日誌,沒有嘗試調整一個垃圾收集器來處理多個應用程序工做負載),並容許您使用全部現有的Unix進程管理工具。json
吹完牛逼,開始幹活。
照例,首先本次測試(github.com/Ryan-Miao/l…
.
├── dependency-reduced-pom.xml
├── l4dropwizard.iml
├── pom.xml
├── readme.md
└── src
└── main
├── java
│ └── com
│ └── test
│ ├── HelloWorldApplication.java
│ ├── configuration
│ │ ├── HelloWorldConfiguration.java
│ │ └── modules
│ │ ├── ConnectAndReadConfig.java
│ │ └── GithubApiConfig.java
│ └── domain
│ ├── connect
│ │ ├── GithubClient.java
│ │ └── GithubConnector.java
│ ├── entiry
│ │ ├── GithubUser.java
│ │ └── Saying.java
│ ├── health
│ │ └── TemplateHealthCheck.java
│ └── resource
│ ├── GithubResource.java
│ └── HelloWorldResource.java
└── resources
└── config
└── dev.yml
14 directories, 16 files複製代碼
依舊是maven項目,pom中添加dropwizard
<properties>
<dropwizard.version>1.0.6</dropwizard.version>
<java.version>1.8</java.version>
<mainClass>com.test.HelloWorldApplication</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>${dropwizard.version}</version>
</dependency>
</dependencies>複製代碼
dropwizard採用yaml做爲配置文件,同時須要有個配置類對應yaml中的屬性。
建立config/dev.yml
template: Hello, %s!
defaultName: Stranger
server:
# softNofileLimit: 1000
# hardNofileLimit: 1000
applicationConnectors:
- type: http
port: 8080
#this requires the alpn-boot library on the JVM's boot classpath
#- type: h2
# port: 8445
# keyStorePath: example.keystore
# keyStorePassword: example
adminConnectors:
- type: http
port: 8082複製代碼
而後,新建對應的配置類com.test.configuration.HelloWorldConfiguration
package com.test.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import org.hibernate.validator.constraints.NotEmpty;
/** * Created by rmiao on 3/14/2017. */
public class HelloWorldConfiguration extends Configuration {
@NotEmpty
private String template;
@NotEmpty
private String defaultName = "Stranger";
@JsonProperty
public String getTemplate() {
return template;
}
@JsonProperty
public void setTemplate(String template) {
this.template = template;
}
@JsonProperty
public String getDefaultName() {
return defaultName;
}
@JsonProperty
public void setDefaultName(String name) {
this.defaultName = name;
}
}複製代碼
下一步就是啓動類:com.test.application.HelloWorldApplication
package com.test;
import com.test.domain.health.TemplateHealthCheck;
import com.test.domain.resource.HelloWorldResource;
import com.test.configuration.HelloWorldConfiguration;
import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import java.util.Map;
/** * Created by Ryan Miao on 3/14/2017. */
public class HelloWorldApplication extends Application<HelloWorldConfiguration> {
public static void main(String[] args) throws Exception {
new HelloWorldApplication().run(args);
}
@Override
public String getName() {
return "hello-world";
}
@Override
public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
// nothing to do yet
}
@Override
public void run(HelloWorldConfiguration configuration, Environment environment) throws Exception {
final HelloWorldResource resource = new HelloWorldResource(
configuration.getTemplate(),
configuration.getDefaultName()
);
final TemplateHealthCheck healthCheck =
new TemplateHealthCheck(configuration.getTemplate());
environment.healthChecks().register("template", healthCheck);
environment.jersey().register(resource);
environment.jersey().register(healthCheck);
}
}複製代碼
到此,配置基本完成,只須要添加接口resource
就好。
對應於springmvc中conroller, dropwizard採用jersey,使用resourc做爲接口類:com.test.com.test.resource.HelloWorldResource
package com.test.domain.resource;
import com.codahale.metrics.annotation.Timed;
import com.test.domain.entiry.Saying;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
/** * Created by rmiao on 3/14/2017. */
@Path("/hello-world")
@Produces(MediaType.APPLICATION_JSON)
public class HelloWorldResource {
private final String template;
private final String defaultName;
private final AtomicLong counter;
public HelloWorldResource(String template, String defaultName) {
this.template = template;
this.defaultName = defaultName;
this.counter = new AtomicLong();
}
@GET
@Timed
public Saying sayHello(@QueryParam("name") Optional<String> name) {
final String value = String.format(template, name.orElse(defaultName));
return new Saying(counter.incrementAndGet(), value);
}
}複製代碼
這裏的template沒啥意思,官網用在這裏就是爲了彰顯下讀取配置文件的能力: 經過configuration類來操做配置屬性。
另外,須要注意的是,resource並不能像Spring同樣自動掃描,須要手動去environment.jersey().register(resource);
。
啓動前還須要配置fat jar,同Spring-boot同樣,fat jar首選. 在pom配置:
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>${mainClass}</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>${mainClass}</mainClass>
<arguments>
<argument>server</argument>
<argument>target/classes/config/dev.yml</argument>
</arguments>
<systemProperties>
<systemProperty>
<key>application.name</key>
<value>HelloWorld</value>
</systemProperty>
<systemProperty>
<key>application.home</key>
<value>.</value>
</systemProperty>
<systemProperty>
<key>application.environment</key>
<value>dev</value>
</systemProperty>
</systemProperties>
</configuration>
</plugin>
</plugins>
</build>複製代碼
接下來,打包:
mvn package複製代碼
而後,run jar:
java -jar target\l4dropwizard-1.0-SNAPSHOT.jar server target/classes/config/dev.yml複製代碼
瀏覽器訪問http://localhost:8080/hello-world?name=Ryan
將獲得:
{
"id": 1,
"content": "Hello, Ryan!"
}複製代碼
至此,hello world完成。
Feign是一個網絡請求客戶端,簡化了網絡請求代碼,使得咱們能夠採用更加友好的方式發送請求,而且管理請求。Feign採用註解驅動模板,因此目前只支持text-based apis.
首先,添加依賴:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hystrix</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>${feign.version}</version>
</dependency>
<dependency>
<groupId>io.reactivex</groupId>
<artifactId>rxjava</artifactId>
<version>1.2.1</version>
<scope>compile</scope>
</dependency>複製代碼
Feign的配置主要有三個,一個是isolation.thread
線程存活時間。一個是connectTimeoutMillis
鏈接超時,一個是readTimeoutMillis
。
本次測試將採用github的公共API,獲取用戶信息。首先配置線程存活時間。在dev.yml中添加:
hystrixConfig:
hystrix.command.GithubConnector#getUserProfile(String).execution.isolation.thread.timeoutInMilliseconds: 7000複製代碼
而後是兩個超時配置:
githubApiConfig:
baseUrl: "https://api.github.com"
getUserProfile:
connectTimeoutMillis: 2000
readTimeoutMillis: 5000複製代碼
Dropwizard經過配置類和配置文件綁定的方式獲取配置內容。所以,須要對應的在配置類中建立對應的字段。
com.test.configuration.modules.ConnectAndReadConfig
package com.test.configuration.modules;
/** * Created by Ryan Miao on 9/14/17. */
public class ConnectAndReadConfig {
private int connectTimeoutMillis;
private int readTimeoutMillis;
public int getConnectTimeoutMillis() {
return connectTimeoutMillis;
}
public int getReadTimeoutMillis() {
return readTimeoutMillis;
}
}複製代碼
com.test.configuration.modules.GithubApiConfig
package com.test.configuration.modules;
/** * Created by Ryan Miao on 9/14/17. */
public class GithubApiConfig {
private String baseUrl;
private ConnectAndReadConfig getUserProfile;
public String getBaseUrl() {
return baseUrl;
}
public ConnectAndReadConfig getGetUserProfile() {
return getUserProfile;
}
}複製代碼
在com.test.configuration.HelloWorldConfiguration中添加:
@NotEmpty
private Map<String, Object> hystrixConfig;
@NotNull
private GithubApiConfig githubApiConfig;複製代碼
而後在application中配置好hystrix的配置:
在HelloWorldApplication#run方法中
//init hystrix config
Map<String, Object> hystrixConfig = configuration.getHystrixConfig();
for (final Map.Entry<String, Object> config : hystrixConfig.entrySet()) {
ConfigurationManager.getConfigInstance().setProperty(config.getKey(), config.getValue());
}複製代碼
建立接口com.test.domain.connect.GithubConnector:
package com.test.domain.connect;
import com.test.domain.entiry.GithubUser;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import rx.Observable;
/** * Created by ryan on 9/14/17. */
public interface GithubConnector {
/** * @param username * @return */
@RequestLine("GET /users/{username}")
@Headers({"Accept: application/vnd.github.v3+json"})
Observable<GithubUser> getUserProfile(@Param("username") String username);
}複製代碼
建立客戶端com.test.domain.connect.GithubClient
package com.test.domain.connect;
import com.test.configuration.modules.ConnectAndReadConfig;
import com.test.configuration.modules.GithubApiConfig;
import com.test.domain.entiry.GithubUser;
import feign.Request;
import feign.Response;
import feign.gson.GsonDecoder;
import feign.gson.GsonEncoder;
import feign.hystrix.HystrixFeign;
import feign.slf4j.Slf4jLogger;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import java.io.IOException;
import java.util.UUID;
/** * Created by Ryan Miao on 9/14/17. */
public class GithubClient {
public static final Logger LOGGER = LoggerFactory.getLogger(GithubClient.class);
private GithubApiConfig githubApiConfig;
public GithubClient(GithubApiConfig githubApiConfig) {
this.githubApiConfig = githubApiConfig;
}
public Observable<GithubUser> getUserProfile(String username) {
String baseUrl = githubApiConfig.getBaseUrl();
ConnectAndReadConfig getUserProfile = githubApiConfig.getGetUserProfile();
GithubConnector connector = HystrixFeign.builder()
.decoder(new GsonDecoder())
.encoder(new GsonEncoder())
.logger(new Slf4jLogger())
.options(new Request.Options(getUserProfile.getConnectTimeoutMillis(), getUserProfile.getReadTimeoutMillis()))
.errorDecoder((methodKey, response) -> {
StringBuilder msg = new StringBuilder("status=").append(response.status())
.append(";request_headers=").append(response.request().headers())
.append(";response_headers=").append(response.headers())
.append(";body=");
Response.Body body = response.body();
if (body != null && body.length() > 0) {
try {
msg.append(IOUtils.toString(body.asReader()));
} catch (IOException e) {
msg.append("can not read body,"+e.getMessage());
}
}
return new RuntimeException(msg.toString());
})
.requestInterceptor(template -> template.header("requestId", UUID.randomUUID().toString()))
.target(GithubConnector.class, baseUrl);
return connector.getUserProfile(username).onErrorReturn(error -> {
LOGGER.error("Get github user profile failed. ", error);
return null;
});
}
}複製代碼
最後,建立一個接口來測試下:
com.test.domain.resource.GithubResource
package com.test.domain.resource;
import com.codahale.metrics.annotation.Timed;
import com.test.configuration.modules.GithubApiConfig;
import com.test.domain.connect.GithubClient;
import com.test.domain.entiry.GithubUser;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
/** * Created by Ryan Miao on 9/14/17. */
@Path("/github")
@Produces(MediaType.APPLICATION_JSON)
public class GithubResource {
private GithubApiConfig githubApiConfig;
public GithubResource(GithubApiConfig githubApiConfig) {
this.githubApiConfig = githubApiConfig;
}
@GET
@Timed
@Path("/users/{username}")
public GithubUser getUserProfile(@PathParam("username") final String username){
GithubClient client = new GithubClient(githubApiConfig);
return client.getUserProfile(username).toBlocking().first();
}
}複製代碼
run main方法啓動。訪問localhost:8080/github/users/Ryan-Miao
就能夠獲得個人github信息了:
{
"login": "Ryan-Miao",
"id": 11866078,
"avatar_url": "https://avatars3.githubusercontent.com/u/11866078?v=4",
"url": "https://api.github.com/users/Ryan-Miao",
"name": "Ryan Miao",
"email": null,
"location": "中國深圳",
"blog": "https://ryan-miao.github.io/"
}複製代碼
至此,feign的簡單集成就搞定了。
feign採用hystrix的配置的時候,grop key是baseUrl.上慄中,grop Key爲https://api.github.com
, commandKey爲接口+方法和參數,上慄中爲GithubConnector#getUserProfile(String)
。所以,配置線程超時用了commandKey如上。若是要配置coreSize之類的,必須使用url作爲group key了。