FeignClient的使用大家可能非常的熟悉
@FeignClient(name = "xxx")
public interface ItemFeignClient {
@RequestMapping(value = "/api/items/{id}", method = RequestMethod.GET)
ItemDto getItem(@PathVariable("id") String itemId);
}
如上,我们定义了一个FeignClient的模板,在服务里面可以直接的调用并访问xxx服务里面的/api/items/{id}方法了。
但是如果说我们现在有十个服务,每个服务都提供了/api/items/{id}的api,那是不是我们要定义十个FeignClient呢,如下
@FeignClient(name = "xxx1")
public interface ItemFeignClient1 {
@RequestMapping(value = "/api/items/{id}", method = RequestMethod.GET)
ItemDto getItem(@PathVariable("id") String itemId);
}
@FeignClient(name = "xxx2")
public interface ItemFeignClient2 {
@RequestMapping(value = "/api/items/{id}", method = RequestMethod.GET)
ItemDto getItem(@PathVariable("id") String itemId);
}
......
@FeignClient(name = "xxx10")
public interface ItemFeignClient10 {
@RequestMapping(value = "/api/items/{id}", method = RequestMethod.GET)
ItemDto getItem(@PathVariable("id") String itemId);
}
肯定有点弱,既然FeignClient是一个调用的模板而已,那我们是不是也可以定义一个FeignClient的模板的模板来解决上面的这种情况呢,当然是可以的,我们先给出解决方案,然后再分析里面的原理。代码如下
public class OutsideAccessorUtils{
private static final Cache<Pair<String, Class<?>>, Object> cache = CacheBuilder.newBuilder().weakValues().build();
@SuppressWarnings("unchecked")
public static <T> T buildAccessor(String serverName, Class<T> accessInterface) {
val pair = Pair.<String, Class<?>>of(serverName, accessInterface);
try{
return (T) cache.get(pair, () ->construct(serverName, accessInterface));
} catch(ExecutionException e) {
throw new SystemException(e.getMessage(), e);
}
}
@SuppressWarnings("unchecked")
private static <T> T construct(String name, Class<T> accessInterface) throws Exception{
val clazz = Class.forName("org.springframework.cloud.openfeign.FeignClientFactoryBean");
val instance = ClassUtils.newInstance(clazz);
setField(clazz, "type", instance, accessInterface);
setField(clazz, "name", instance, name);
//其要求path值非null
setField(clazz, "path", instance, "");
//设置上下文
ApplicationContext applicationContext = ApplicationContextUtils.getReadyApplicationContext();
((ApplicationContextAware) instance).setApplicationContext(applicationContext);
val obj =((FactoryBean) instance).getObject();
return (T) obj;
}
private static void setField(Class clazz, String fieldName, Object obj, Object value) {
val field = ReflectionUtils.findField(clazz, fieldName);
assert field !=null;
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, obj, value);
}
// public static RestTemplate wfRestTemplate() {
// return getRestTemplate();
// }
}
public interface ItemFeignClient {
@RequestMapping(value = "/api/items/{id}", method = RequestMethod.GET)
ItemDto getItem(@PathVariable("id") String itemId);
}
//调用方式如下
//调用xxx1服务的/api/items/{id}方法
ItemFeignClient client = OutsideAccessorUtils.buildAccessor("xxx1",ItemFeignClient.class);
client.getItem(122);
//调用xxx2服务的/api/items/{id}方法
client = OutsideAccessorUtils.buildAccessor("xxx2",ItemFeignClient.class);
client.getItem(122);
上面是实现动态FeignClient的核心的代码,其中buildAccessor方法会返回一个T 这个T就是我们定义的FeignClient模板的模板ItemFeignClient,缓存的加持,性能上面也有了提升。后面我们在分析下实现的原理。
如何更加优雅的实现动态feignClient的能力
借助于aop和配置中心的能力,我们可以实现的更加灵活
可以定义如下的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DyFeignClient {
//属性可以扩展添加
}
使用方式如下
@FeignClient(name="xx1")
public interface CommonFeignClientProxy {
@DyFeignClient
@RequestMapping(value = "/api/{boType}/search", method = GET)
ResponseEntity<?> boSearch(@PathVariable("boType") String boType,
@RequestHeader(RequestHeaderInfo.X_TENANT_ID) String tenant_id);
}
如上代码的意义是,在执行boSearch的时候,aop会拦截DyFeignClient注解,如果找不到相应的路由信息,就默认到xx1服务里面去执行。
相关的aop代码如下
@Aspect
@Component
@Slf4j
public class DyFeignClientAspect {
@Autowired
private RoutingMapper routingMapper;
private AntPathMatcher antMatcher = new AntPathMatcher();
@Pointcut("@annotation(DyFeignClient)")
public void annotation1(DyFeignClient dyFeignClient) {
}
@Pointcut("@annotation(requestMapping)")
public void annotation2(RequestMapping requestMapping) {
}
@Around("annotation1(dyFeignClient) && annotation2(requestMapping)")
public Object aroundFeignClientRequest(ProceedingJoinPoint point, DyFeignClient dyFeignClient, RequestMapping requestMapping) throws Throwable {
Object result;
String uri = requestMapping.value()[0];
String serviceName = findRouteService(routingMapper.getUriRouteMap(), requestMapping.value()[0]);
MethodSignature methodSignature = (MethodSignature)point.getSignature();
//serviceName not found. Try to resolve path variable and look for service again
if (Strings.isEmpty(serviceName)) {
Annotation[][] annotations = methodSignature.getMethod().getParameterAnnotations();
for (int i=0; i<annotations.length; i++) {
Annotation[] temp = annotations[i];
for (int j=0; j<temp.length; j++) {
Annotation annotation = temp[j];
if(PathVariable.class.isAssignableFrom(annotation.getClass())) {
PathVariable pathVariable = (PathVariable)annotation;
uri = uri.replaceAll(new StringBuilder().append("\\{").append(pathVariable.value()).append("\\}").toString(), point.getArgs()[i].toString());
}
}
}
serviceName = findRouteService(routingMapper.getUriRouteMap(), uri);
}
try {
if (!Strings.isEmpty(serviceName)) {
LOG.info("the service name is:{}", serviceName);
Object feignClient = OutsideAccessorUtils.buildAccessor(serviceName, methodSignature.getMethod().getDeclaringClass());
Method method = feignClient.getClass().getDeclaredMethod(methodSignature.getName(), methodSignature.getParameterTypes());
method.setAccessible(true);
result = method.invoke(feignClient, point.getArgs());
} else {//service name is still not found
LOG.info("service name is still not found, call original feign client");
if (point.getArgs() == null || point.getArgs().length == 0) {
result = point.proceed();
} else {
result = point.proceed(point.getArgs());
}
}
} catch (Exception e) {
Throwable throwable = parseExceptionRootCause(e, FeignException.class);
if (throwable != null) {
throw throwable;
} else {
throw e;
}
}
return result;
}
private String findRouteService(Map<String, String> urlRouteMap, String uri) {
try {
String matchedUri = urlRouteMap.keySet().stream().filter(key->antMatcher.match(key, uri)).findFirst().orElse(null);
if (matchedUri != null) {
return urlRouteMap.get(matchedUri);
}
} catch (Throwable e) {
LOG.warn("findRouteService failed, the uri:{} cannot find its service", uri);
}
LOG.info("uri:{} cannot find its service", uri);
return null;
}
private Throwable parseExceptionRootCause(Exception ex, Class<? extends Throwable> expectedRootCauseClass) {
int maxDepth = 10;
if (ex.getClass() == expectedRootCauseClass) {
return ex;
}
Throwable rootCause = ex.getCause();
for (int i = 0; i < 10; i++) {
if (rootCause == null) {
return null;
}
if (rootCause.getClass() == expectedRootCauseClass) {
return rootCause;
}
if(rootCause.getCause() == null) {
return null;
}
rootCause = rootCause.getCause();
}
return null;
}
}
如上,相当于所有的路由信息,都在RoutingMapper里面,等于说在服务启动的时候,RoutingMapper必须加载到所有的路由信息,现在配置中心可以派上用场了,如果在配置中心里面已经有了所有的路由配置信息,那一切就ok了。
等于说现在的压力都到了RoutingMapper这个bean的身上,大致代码如下
public class RoutingMapper implements ApplicationListener<ApplicationReadyEvent> {
@Getter
private static Map<String, String> uriRouteMap = new HashMap();
@Autowired
private ApiRoutingRulesConfigLoader configLoader;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
LOG.info("start to load the api routing rules with ApplicationReadyEvent");
synchronized(uriRouteMap) {
uriRouteMap.clear();
loadFromConfig(null, null)
}
}
public void refreshApiRouteMap(ConfigProperty lastConfigProperty, ConfigProperty newConfigProperty) {
LOG.info("refresh the api routing rules with ConfigChangeCallback");
synchronized(uriRouteMap) {
loadFromConfig(lastConfigProperty, newConfigProperty);
}
}
private void loadFromConfig(ConfigProperty lastConfigItem, ConfigProperty currentConfigItem) {
if (lastConfigItem != null && currentConfigItem != null) {
List[] lastAndCurrentRules = configLoader.getApiRoutingRules(lastConfigItem, currentConfigItem);
List<ApiRoutingRulesConfigLoader.ApiRoutingRule> lastRules = lastAndCurrentRules[0];
List<ApiRoutingRulesConfigLoader.ApiRoutingRule> currentRules = lastAndCurrentRules[1];
removeUriRouteMap(lastRules);
addUriRouteMap(currentRules);
} else {
addUriRouteMap(configLoader.getApiRoutingRules());
}
}
private void addUriRouteMap(List<configLoader.ApiRoutingRule> rules) {
if (rules != null) {
rules.stream().forEach(rule->{
if (uriRouteMap.get(rule.getUri()) != null) {
rule.getUri(), uriRouteMap.get(rule.getUri()), rule.getServiceId());
}
uriRouteMap.put(rule.getUri(), rule.getServiceId());
});
}
}
private void removeUriRouteMap(List<ApiRoutingRulesConfigLoader.ApiRoutingRule> rules) {
if (rules != null) {
rules.stream().forEach(rule->{
if (StringUtils.equals(uriRouteMap.get(rule.getUri()), rule.getServiceId())) {
uriRouteMap.remove(rule.getUri());
}
});
}
}
}
还一个场景是refresh场景,每个注册中心的sdk不一样,就不贴代码了
图片示例.png
整个的原理非常的清晰,以上!
ps 很多人要ApplicationContextUtils的源码,我就贴这里了
@Component
@Slf4j
public class ApplicationContextUtils implements ApplicationContextAware, BeanFactoryPostProcessor, ApplicationListener<ApplicationReadyEvent> {
private static final Lock LOCKER = new ReentrantLock();
private static final Condition CONDITION = LOCKER.newCondition();
@Getter
private static volatile boolean contextReady;
private static ApplicationContext applicationContext;
private static final Map<Class<?>, String> NAME_MAP = Maps.newHashMap();
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) {
//do nothing
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContextUtils.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
LOG.info("init spring context success!");
LOCKER.lock();
try{
contextReady = true;
CONDITION.signalAll();
} finally {
LOCKER.unlock();
}
}
public static ApplicationContext getApplicationContext() {
if(!contextReady) {
LOG.warn("spring context not init yet check stack", new Exception());
}
return applicationContext;
}
public static ApplicationContext getReadyApplicationContext() {
waitContextReady();
return applicationContext;
}
/** 等待spring容器准备好,如果没有准备好,则阻塞当前线程 */
public static void waitContextReady() {
//快速判断,避免加锁
if(contextReady) {
return;
}
LOCKER.lock();
try{
while(!contextReady) {
ExceptionUtils.doActionLogE(() -> CONDITION.await(3, TimeUnit.SECONDS));
if(!contextReady) {
LOG.debug("waiting context init");
}
}
} finally {
LOCKER.unlock();
}
}
@SuppressWarnings("unchecked")
public static <T> T getReadyBean(Class<T> clazz) {
waitContextReady();
return getReadyBean(clazz, () -> {
throw new SystemException("class:" + clazz + "no definition in spring");
});
}
@SuppressWarnings("unchecked")
public static <T> T getReadyBean(Class<T> clazz, Supplier<T> ifAbsent) {
waitContextReady();
String beanName = NAME_MAP.get(clazz);
if(beanName == null) {
String[] beanNames = applicationContext.getBeanNamesForType(clazz);
if(beanNames.length == 0) {
return ifAbsent.get();
}
if(beanNames.length > 1) {
throw new SystemException("class:" + clazz + "more than on bean definition" + Arrays.toString(beanNames));
}
beanName = beanNames[0];
NAME_MAP.put(clazz, beanName);
}
return (T) applicationContext.getBean(beanName);
}
@SuppressWarnings("unchecked")
public static <T> T getReadyBean(String beanName) {
waitContextReady();
return (T) applicationContext.getBean(beanName);
}
@SuppressWarnings("unchecked")
public static <T> T getReadyBean(String beanName, Supplier<T> ifAbsent) {
waitContextReady();
if(!applicationContext.containsBean(beanName)) {
return ifAbsent.get();
}
return (T) applicationContext.getBean(beanName);
}
public static <T> void injectReadyBean(T bean) {
waitContextReady();
val factory = applicationContext.getAutowireCapableBeanFactory();
factory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
factory.initializeBean(bean, null);
}
}
网友评论