自定义BeanPostProcessor之Feign组件服务间优雅调用
Feign是什么
feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。Spring Cloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
Feign怎么使用
@FeignClient(value = "service-name")
public interface CartFeignClient {
@PostMapping("/cart/{productId}")
Long addCart(@PathVariable("productId")Long productId);
}
上面是最简单的feign client的使用,声明完为feign client后,其他spring管理的类,如service就可以直接注入使用了,例如:
//这里直接注入feign client
@Autowired
private CartFeignClient cartFeignClient;
@PostMapping("/toCart/{productId}")
public ResponseEntity addCart(@PathVariable("productId") Long productId){
Long result = cartFeignClient.addCart(productId);
return ResponseEntity.ok(result);
}
可以看到,使用feign之后,我们调用eureka 注册的其他服务,在代码中就像各个service之间相互调用那么简单。
常规Feign服务间调用
在本地研发环境开发时候可能需要调用其他服务,例如通过service-name的方式调用,但是如果经过注册中心,并且服务有多实例的情况下会出现超时的情况。可以通过指定服务ip的方式调用。
@FeignClient(value="service-name", url = "http://127.0.0.1:2607/")
@FeignClient(value="service-name-1", url = "http://1.2.3.4:2607/")
Feign服务间调用更容易
如果引用的其他服务比较多,需要修改很多的url,这样比较浪费时间,通过重写@FeignClient的url,可进行统一处理,而不用一个一个的去修改。也可以配置研发环境,本地环境等多个环境的服务地址。
具体的实现方式是通过自定义BeanPostProcessor的方式,在bean初始化的时候动态替换。
BeanPostProcessor的原理可以看spring相关文章。
@Component
public class FeignClientsServiceNameAppendBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
private AtomicInteger atomicInteger = new AtomicInteger();
// 需要替换的服务名称和ip集合
public static final Map<String, String> SERVICE_MAP = new HashMap<>();
private static String beanNameOfFeignClientFactoryBean = "org.springframework.cloud.netflix.feign.FeignClientFactoryBean";
static {
SERVICE_MAP.put("service-name", "http://127.0.0.1:2607");
SERVICE_MAP.put("service-name-1", "http://1.2.3.4:8081");
}
@Override
public Object postProcessBeforeInitialization(Object bean, String s) throws BeansException {
return bean;
}
@SneakyThrows
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (atomicInteger.getAndIncrement() == 0) {
Class beanNameClz = Class.forName(beanNameOfFeignClientFactoryBean);
applicationContext.getBeansOfType(beanNameClz).forEach((feignBeanName, beanOfFeignClientFactoryBean) -> {
try {
setField(beanNameClz, "url", beanOfFeignClientFactoryBean);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(feignBeanName + "-->" + beanOfFeignClientFactoryBean);
});
}
return bean;
}
private void setField(Class clazz, Object obj) throws Exception {
// 获取FeignClientFactoryBean的name属性,找到服务名称。例如:service-name-1
Field name = ReflectionUtils.findField(clazz, "name");
Object nameValue = null;
if (Objects.nonNull(name)) {
ReflectionUtils.makeAccessible(name);
nameValue = name.get(obj);
}
// 获取FeignClientFactoryBean的url字段
Field field = ReflectionUtils.findField(clazz, "url");
if (Objects.nonNull(field) && Objects.nonNull(nameValue)) {
ReflectionUtils.makeAccessible(field);
Object value = field.get(obj);
// 找到指定配置替换url的值
if (Objects.nonNull(value) && SERVICE_MAP.containsKey(nameValue)) {
value = SERVICE_MAP.get(nameValue);
ReflectionUtils.setField(field, obj, value);
}
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}