手写springcloud|服务调用 feign
github示例代码
github对应代码仓库地址:https://github.com/huajiexiewenfeng/spring-cloud-project-learn
spring-application 手写spring cloud系列
spring-cloud-client-appliaction 客户端
eventBus
spring-cloud-server-application 服务端
spring-boot-2.0-samples SpringBoot编程思想系列
spring-cloud-application 手写spring cloud系列 基础组件
- spring-cloud-servlet-gateway 网关
- spring-cloud-config-server 配置中心服务端
- spring-cloud-config-client 配置中心客户端
REST框架对比
REST框架 | 使用场景 | 请求映射注解 | 请求参数 |
---|---|---|---|
Feign | 客户端声明 | @ReqeustLine | @Param |
Spring Cloud Open Feign | 客户端声明 | @RequestMapping | @RequestParam |
JAX-RS | 客户端、服务端声明 | @Path | @*Param |
Spring Web MVC | 服务端声明 | @RequestMapping | @RequestParam |
Spring Cloud Open Feign 利用Feign高扩展性,使用标准Spring Web MVC 来声明客户端 Java接口
- Feign
- 注解扩展性
- HTTP请求处理
- Rest请求元信息解析
- Spring Cloud Open Feign
- 提供Spring Web MVC 注解处理
- 提供Feign自动装配
Spring Cloud Open Feign 是通过Java接口的方式来声明 REST 服务提供者的请求元信息,通过调用Java接口的方式来实现HTTP/REST通讯
实现细节猜想
- java接口(以及方法)与Rest 提供者
@FeignClient
注解所指定的应用(服务)名称可能用到了服务发现,一个服务可以对用多个服务实例(HOST:PORT)@EnableFeignClients
注解是如何感知或者加载标注@FeignClient
的配置类(Bean)- Feign请求和响应的内容是如何序列化和反序列化对用的POJO的
Spring Cloud Open Feign 实现细节
实现策略:Enable 模块驱动
具体实现:org.springframework.context.annotation.ImportBeanDefinitionRegistrar
主要工作:
- 注册默认配置
- 注册所有标注
@FeignClient
配置类
注册默认配置
org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerDefaultConfiguration
注册所有标注@FeignClient
配置类
org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerFeignClients
通过ClassPathScanningCandidateComponentProvider
扫描指定的basePackages
集合中的类是否标注了@FeignClient
,如果有的话作为AnnotatedBeanDefinition
返回,其中包含@FeignClient
属性元数据,来至于AnnotationMetadata
,再重新注册FeignClientFactoryBean
·的BeanDefinition
@EnableFeignClient
->@FeignClient
元信息->标注接口定义的FeignClientFactoryBean
->形成被标注接口的代理对应
Spring Cloud Open Feign 大致流程
- Spring Web MVC 注解元信息解析
- 通过
@FeignClient
所生产代理对象的方法调用实现HTTP调用 - 通过SpringDecoder实现Response与接口返回类型的反序列化
- 负载均衡
Spring Cloud 替换了Client实现-LoadBalanceFeignClient - 重试
Spring Cloud 在外部包装Feign接口 - 熔断
feign.hystrix.HystrixFeign
调用的过程
- 通过接口上面
@FeignClient
注解获取服务名称 - 再通过注册中心获取服务器地址
- 发起
rest
远程调用
手写实现步骤
该版本集成了 负载均衡 loadbalanced ,在服务调用的阶段集成负载均衡。
@RestClient
注解 代替@FeignClient
@EnableRestClients
注解用来驱动加载标注@RestClient
的配置类- 当客户端调用该接口(标有
@RestClient
注解)的方法时,调用的是动态代理的类,将服务名称,调用的方法名称传入到动态代理类中做后续相应的处理,接口本身并不需要实现类。 - 动态代理中,封装完整URL,在服务调用阶段使用
loadBalanceRestTemplate
进行远程访问,再拦截到LoadBalancedRequestInterceptor
负载均衡的逻辑中 - 最后调用服务端的对应方法,接收返回信息
1.@RestClient
可以类比为spring cloud open feign中的@FeignClient
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestClient {
/**
* REST 服务应用名称
* @return
*/
String name();
}
2.@EnableRestClients注解驱动
这一块可以参考 Spring boot 编程思想|@Enable 模块驱动
本文用的是实现ImportBeanDefinitionRegistrar
接口这种方式实现
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RestClientsRegistrar.class})
public @interface EnableRestClients {
/**
* 指定@RestClient接口
* @return
*/
Class<?>[] clients() default {};
}
RestClientsRegistrar
- 在spring容器启动时,将
@RestClient
接口代理实现注册为Bean - 从
metadata
获取注解相关的元信息 - 将接口封装成动态代理
Proxy.newProxyInstance()
- 通过
registry
将bean
注册到spring
的beanDefinition
中 - 实现
BeanFactoryAware
获取spring
的BeanFactory
对象,获取restTemplate
对象 - 实现
EnvironmentAware
获取spring
的environment
对象,完成占位符的功能拓展
public class RestClientsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware , EnvironmentAware {
private BeanFactory beanFactory;
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassLoader classLoader = metadata.getClass().getClassLoader();
//将@RestClient接口代理实现注册为Bean(@Autowired)
//@RestClient->attributes->{clients:ServiceName}
Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableRestClients.class.getName());
Class<?>[] clientsClasses = (Class<?>[]) attributes.get("clients");
//筛选所有的接口
Stream.of(clientsClasses)
.filter(Class::isInterface)//过滤接口
.filter(interfaceClass -> {//过滤@RestClient
// return interfaceClass.isAnnotationPresent(RestClient.class);
return AnnotationUtils.findAnnotation(interfaceClass, RestClient.class) != null;
})
.forEach(restClientClass -> {
//获取@RestClient元信息
RestClient restClient = AnnotationUtils.findAnnotation(restClientClass, RestClient.class);
//获取应用名称(处理占位符)
String serviceName = environment.resolvePlaceholders(restClient.name());
//目的-》使用restTemplate->serviceName/uri?param...
//@RestClient接口变成动态代理
Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{restClientClass},
new RequestMappingMethodInvocationHandler(serviceName, beanFactory));
//注册bean
String beanName = "RestClient." + serviceName;
if(registry instanceof SingletonBeanRegistry){
SingletonBeanRegistry singletonBeanRegistry = (SingletonBeanRegistry)registry;
singletonBeanRegistry.registerSingleton(serviceName,proxy);
}
// registryBeanDefinition(beanName, registry, restClientClass, proxy);
});
}
private void registryBeanDefinition(String beanName, BeanDefinitionRegistry registry, Class restClientClass, Object proxy) {
BeanDefinition beanDefinition = null;
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RestClientClassFactory.class);
beanDefinitionBuilder.addConstructorArgValue(restClientClass);
beanDefinitionBuilder.addConstructorArgValue(proxy);
registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
}
static class RestClientClassFactory implements FactoryBean {
private final Class<?> restClientClass;
private final Object proxy;
public RestClientClassFactory(Class<?> restClientClass, Object proxy) {
this.restClientClass = restClientClass;
this.proxy = proxy;
}
@Override
public Object getObject() throws Exception {
return proxy;
}
@Override
public Class<?> getObjectType() {
return restClientClass;
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
3.RequestMappingMethodInvocationHandler动态代理
- 拼接完整的url带参数
- 从
beanFactory
获取customLoadBalanceRestTemplate
负载均衡的RestTemplate
对象 - 远程调用,被
LoadBalancedRequestInterceptor
拦截
public class RequestMappingMethodInvocationHandler implements InvocationHandler {
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
private final String serviceName;
private final BeanFactory beanFactory;
public RequestMappingMethodInvocationHandler(String serviceName, BeanFactory beanFactory) {
this.serviceName = serviceName;
this.beanFactory = beanFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (requestMapping != null) {
String[] uri = requestMapping.value();
StringBuilder urlBuilder = new StringBuilder("/").append(serviceName).append("/").append(uri[0]);
//获取方法参数的数据
int count = method.getParameterCount();
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
StringBuilder queryString = new StringBuilder();
for (int i = 0; i < count; i++) {
Class<?> parameterType = parameterTypes[i];
//获取方法参数上面的注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
RequestParam requestParam = (RequestParam)parameterAnnotations[i][0];
if (requestParam != null) {
String value = requestParam.value();
String requestparamName = StringUtils.hasText(value) ? value : parameterNames[0];
String requestparamValue = String.class.equals(parameterType) ? (String) args[i] : String.valueOf(args[i]);
queryString.append("&").append(requestparamName).append("=").append(requestparamValue);
}
}
if (StringUtils.hasText(queryString.toString())) {
urlBuilder.append("?").append(queryString);
}
String url = urlBuilder.toString();
RestTemplate restTemplate = beanFactory.getBean("customLoadBalanceRestTemplate", RestTemplate.class);
return restTemplate.getForObject(url, method.getReturnType());
}
return null;
}
}
测试类
@RestClient(name = "${saying.rest.service.name}")
public interface SayingRestService {
@RequestMapping("say")
public String say(@RequestParam("message") String message);
}
yml
spring:
application:
name: spring-cloud-client-application
server:
name: spring-cloud-server-application
server:
port: 8082
saying:
rest:
service:
name: ${spring.server.name}
ClientController客户端调用类
@RestController
public class ClientController {
@Autowired
@CustomedLoadBalance
private RestTemplate restTemplate;
@Autowired
private SayingRestService sayingRestService;
@GetMapping("/rest/say")
public Object restSay(@RequestParam("message") String message) {
return sayingRestService.say(message);
}
@Bean
public ClientHttpRequestInterceptor interceptor() {
return new LoadBalancedRequestInterceptor();
}
@Bean
@CustomedLoadBalance
public RestTemplate customLoadBalanceRestTemplate() {
return new RestTemplate();
}
@Bean
@Autowired
public Object customizer(@CustomedLoadBalance Collection<RestTemplate> restTemplates, ClientHttpRequestInterceptor interceptor) {
restTemplates.forEach(restTemplate -> {
//增加拦截器
restTemplate.setInterceptors(Arrays.asList(interceptor));
}
);
return new Object();
}
}
服务端关键部分代码
...
@GetMapping("/say")
public String say(@RequestParam("message") String message) throws InterruptedException {
int value = random.nextInt(200);
System.out.println("say() cost " + value + "ms");
Thread.sleep(value);
System.out.println("接收到消息-say:" + message);
return "Hello," + message;
}
...
测试
1.浏览器输入地址
http://127.0.0.1:8082/rest/say?message=helloword11
2.被拦截到RequestMappingMethodInvocationHandler
因为调用SayingRestService
接口实际上是调用的SayingRestService
的动态代理RequestMappingMethodInvocationHandler
@EnableRestClients
注解驱动在注册阶段使用RequestMappingMethodInvocationHandler
代替SayingRestService
注册到spring
中的beanDefinition
3.拼接完整url
4.被拦截到LoadBalancedRequestInterceptor
因为使用beanFactory.getBean("customLoadBalanceRestTemplate", RestTemplate.class);
获取到customLoadBalanceRestTemplate
,而customLoadBalanceRestTemplate
增加了拦截器
5.服务端注册两个实例,选择其中一台发送消息
返回结果:
感谢
- 小马哥的分享 博客地址https://mercyblitz.github.io/