zoukankan      html  css  js  c++  java
  • 在dropwizard中使用feign,使用hystrix



    i18n

    前言

    用惯了spring全家桶之后,试试dropwizard的Hello World也别有一帆风味。为了增强对外访问API的能力,需要引入open feign。这里简单在dropwizard中使用feign。

    1. 什么Dropwizard

    Dropwizard is a Java framework for developing ops-friendly, high-performance, RESTful web services.

    Dropwizard使成熟、稳定的java生态系统更加简单、轻量(light-weight), 让你更专注于业务逻辑。

    Dropwizard 为配置(configuration)、统计(application metrics)、日志(logging)、operational tools提供了开箱即用的能力。让您和您的团队能够在最短的时间内开发出具有生产环境的质量的Web服务。

    下面的简介来自REST微服务架构之Dropwizard

    DropWizard是由Yammer开发团队贡献的一个后台服务开发框架,其集成了Java生态系统中各个问题域中最优秀的组件,帮助开发者快速的打造一个Rest风格的后台服务。

    对开发者来说,使用DropWizard有如下好处:
    1、和Maven集成良好,也就是说和Gradle集成也很良好;
    2、开发迅速,部署简单;
    3、代码结构好,可读性高;
    4、自动为服务提供OM框架;
    5、让开发者自然的把一个应用拆分为一个个的小服务

    DropWizard结构的Web服务组成
    1、Configuration:用于设置该服务的配置,比方说在服务开放在哪个端口,数据库配置是怎样的等等。
    2、Application(即Service):该服务的主入口,定义该服务使用哪个配置文件,开放哪些Resource,该服务需要哪些HealthCheck等等。
    3、Resource:定义一个资源,包括如何获取该资源,对该资源做Get/Post/Delete/Query时,对应的各种业务逻辑。
    4、Representation:定义了一个服务返回值对象,当服务返回该对象时,会自动的把该对象按属性值生成一个Json格式的字符串返回给服务调用者。
    5、HealthCheck:在DropWizard为每个服务提供的OM框架中用到,通过它可以随时检测当前服务是否可用。

    Dropwizard内置了Jetty

    Web应用程序不能没有HTTP,所以Dropwizard使用Jetty HTTP库将一个令人难以置信的HTTP服务器直接嵌入到您的项目中。 Dropwizard项目不需要将应用程序交给一个复杂的应用程序服务器,而是一个main方法,它会自动连接一个HTTP服务器。将应用程序作为一个简单的过程运行,消除了Java在生产中的一些不好的东西(没有PermGen问题,没有应用程序服务器配置和维护,没有复杂的部署工具,没有类加载器(class loader)故障,没有隐藏的应用程序日志,没有尝试调整一个垃圾收集器来处理多个应用程序工作负载),并允许您使用所有现有的Unix进程管理工具。

    Dropwizard 使用Jersey提供Rest能力

    Dropwizard 使用Jackson来处理json

    Dropwizard 提供了Metrics类库

    2. Hello World For Dropwizard

    吹完牛逼,开始干活。
    照例,首先本次测试(https://github.com/Ryan-Miao/l4dropwizard)的完整结构图如下:

    .
    ├── 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
    
    

    2.1 添加依赖

    依旧是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>
    

    2.2 添加配置中心

    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就好。

    2.3 创建第一个API

    对应于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);

    2.4 启动

    启动前还需要配置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 targetl4dropwizard-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是一个网络请求客户端,简化了网络请求代码,使得我们可以采用更加友好的方式发送请求,并且管理请求。Feign采用注解驱动模板,所以目前只支持text-based apis.

    Dropwizard with Feign

    依赖

    首先,添加依赖:

    <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());
    }
    

    创建Feign的connector接口

    创建接口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了。

    source

    https://github.com/Ryan-Miao/l4dropwizard

    参考

  • 相关阅读:
    【HDOJ】2102 A计划
    this关键字
    static(静态、修饰符)
    手势抽取过程&代码复用
    Android:padding和android:layout_margin的区别
    平移动画
    读取系统联系人
    获取sim卡序列号
    图片选择器
    md5加密过程
  • 原文地址:https://www.cnblogs.com/woshimrf/p/dropwizard-feign.html
Copyright © 2011-2022 走看看