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