之前在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();
}
}
网友评论
这里的:
RemoteService service = Feign.builder().client(client).encoder(new JacksonEncoder())
.decoder(new JacksonDecoder()).target(RemoteService.class, "http://sample-client/gradle-web");
terget()方法第二个参数除了将listServers写到配置文件中,还有什么好的方法吗?
receive.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
# refresh every minute
receive.ribbon.ServerListRefreshInterval=60000
# movieservice is the virtual address that the target server(s) uses to register with Eureka server
receive.ribbon.DeploymentContextBasedVipAddresses=receive.mydomain.net