spring環境中Feign + Ribbon的使用

之前在feign更正確的使用方法--結合ribbon中介紹了非spring cloud環境中feign+ribbon的使用。簡化http客戶端的實現。

有朋友私信我,說demo都是在main方法中進行feign對象的組裝,而實際的java項目大部分都會用到spring IOC容器進行對象管理,能不能提供在spring環境中使用的示例。

于是有了這篇文章。

spring環境有很多類型,大致可分為

  • 傳統的,基于xml或Java config的spring應用。

  • spring boot應用。

  • spring cloud應用。

在spring cloud中,feign,ribbon等netflix產品已經與spring進行了集成,在程序員DD的系列文章中已有非常詳盡的說明。
在spring boot中使用@Bean對遠程調用接口定義即可。例如:
import java.io.IOException;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.netflix.client.ClientFactory;
import com.netflix.client.config.IClientConfig;
import com.netflix.config.ConfigurationManager;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import com.sam.demo.service.RemoteService;

import feign.Feign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.ribbon.LBClient;
import feign.ribbon.LBClientFactory;
import feign.ribbon.RibbonClient;

@Configuration
public class Config {

    public Config() {
        try {
            ConfigurationManager.loadPropertiesFromResources("sample-client.properties");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Bean
    public RemoteService remoteService() {

        RibbonClient client = RibbonClient.builder().lbClientFactory(new LBClientFactory() {

            @Override
            public LBClient create(String clientName) {
                IClientConfig config = ClientFactory.getNamedConfig(clientName);
                ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName);
                ZoneAwareLoadBalancer zb = (ZoneAwareLoadBalancer) lb;
                zb.setRule(zoneAvoidanceRule());
                return LBClient.create(lb, config);
            }
        }).build();

        RemoteService service = Feign.builder().client(client).encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder()).target(RemoteService.class, "http://sample-client/gradle-web");

        return service;
    }

    /**
     * Ribbon負載均衡策略實現
     * 使用ZoneAvoidancePredicate和AvailabilityPredicate來判斷是否選擇某個server,前一個判斷判定一個zone的運行性能是否可用,
     * 剔除不可用的zone(的所有server),AvailabilityPredicate用于過濾掉連接數過多的Server。
     * @return
     */
    private IRule zoneAvoidanceRule() {
        return new ZoneAvoidanceRule();
    }
    
    /**
     * Ribbon負載均衡策略實現
     * 隨機選擇一個server。
     * @return
     */
    private IRule randomRule() {
        return new RandomRule();
    }

}

RemoteService就是自定義的遠程調用接口。

import com.sam.demo.entity.User;

import feign.Headers;
import feign.RequestLine;

public interface RemoteService {
    
    @Headers({"Content-Type: application/json","Accept: application/json"})
    @RequestLine("POST /users/list")
    public User getOwner(User user);
}

這里順便用到了Ribbon的另一種負載均衡策略實現ZoneAvoidanceRule,并定義了RandomRule負載均衡策略實現,如果有多個遠程調用接口,可以根據業務情況,選擇不同的負載均衡策略實現。

需要注意的是,在組裝遠程接口的實現之前,需要讀取配置文件,這里將讀取動作簡單的放到了構造方法中。實際項目中應與其他讀取配置文件的代碼封裝在一起。

在需要使用遠程接口的地方,注入該接口即可,例如:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.sam.demo.entity.User;
import com.sam.demo.service.RemoteService;

@RestController
public class RemoteController {
    
    @Autowired
    private RemoteService remoteService;
    
    @RequestMapping(value="/remote",method={RequestMethod.GET})
    public String list() {
        
        User user = new User();
        user.setUsername("scott");
        user = remoteService.getOwner(user);
        
        return user.getUsername();
    }
}

將RemoteService當作普通的bean注入即可,使用上也完全與本地調用方式相同。

傳統spring應用中,處理思路與spring boot相同,僅僅的操作方式發生改變。
<bean id="remoteService" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetClass">
        <value>com.sam.demo.config.Config</value>
    </property>
    <property name="targetMethod">
        <value>remoteService</value>
    </property>
</bean>

同樣是將方法返回值定義為bean。下面是com.sam.demo.config.Config的實現,與spring boot的方式幾乎一致。只不過為了方便,改為靜態方法。

import java.io.IOException;

import com.netflix.client.ClientFactory;
import com.netflix.client.config.IClientConfig;
import com.netflix.config.ConfigurationManager;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.loadbalancer.ZoneAwareLoadBalancer;
import com.sam.demo.service.RemoteService;

import feign.Feign;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.ribbon.LBClient;
import feign.ribbon.LBClientFactory;
import feign.ribbon.RibbonClient;

public class Config {
    
    static {
        try {
            ConfigurationManager.loadPropertiesFromResources("sample-client.properties");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static RemoteService remoteService() {
        RibbonClient client = RibbonClient.builder().lbClientFactory(new LBClientFactory() {

            @Override
            public LBClient create(String clientName) {
                IClientConfig config = ClientFactory.getNamedConfig(clientName);
                ILoadBalancer lb = ClientFactory.getNamedLoadBalancer(clientName);
                ZoneAwareLoadBalancer zb = (ZoneAwareLoadBalancer) lb;
                zb.setRule(zoneAvoidanceRule());
                return LBClient.create(lb, config);
            }
        }).build();

        RemoteService service = Feign.builder().client(client).encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder()).target(RemoteService.class, "http://sample-client/gradle-web");

        return service;
    }

    /**
     * Ribbon負載均衡策略實現
     * 使用ZoneAvoidancePredicate和AvailabilityPredicate來判斷是否選擇某個server,前一個判斷判定一個zone的運行性能是否可用,
     * 剔除不可用的zone(的所有server),AvailabilityPredicate用于過濾掉連接數過多的Server。
     * @return
     */
    private static IRule zoneAvoidanceRule() {
        return new ZoneAvoidanceRule();
    }
    
    /**
     * Ribbon負載均衡策略實現
     * 隨機選擇一個server。
     * @return
     */
    private static IRule randomRule() {
        return new RandomRule();
    }
}

在需要調用遠程接口的類中,注入該接口即可。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.sam.demo.entity.User;
import com.sam.demo.service.RemoteService;

@RestController
public class RemoteController {
    
    @Autowired
    private RemoteService remoteService;
    
    @RequestMapping(value="/remote",method={RequestMethod.GET})
    public String list() {
        
        User user = new User();
        user.setUsername("scott");
        user = remoteService.getOwner(user);
        
        return user.getUsername();
    }
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容