• Feign封装请求基本原理(启动和注入)


    通过一个例子看基于Feign的http请求原理。对Feign的操作主要分为:项目启动时处理、IOC容器注入时处理;实际的调用处理。目录:
    1、例子;
    2、项目启动时:Feign引入;
    3、Bean的注入:@Autowired等;
    4、接口方法的调用;

    1. 例子

    例子是一个用Maven管理的Spring Boot项目。

    pom.xml添加feign依赖

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-openfeign</artifactId>
    	<version>2.2.2.RELEASE</version>
    </dependency>
    

    这里只引用一个依赖,它会自动依赖其他Feign相关项。

    FeignClient调用接口,添加@FeignClient注解

    package com.mingo.exp.feign.remote;
    
    import com.mingo.exp.feign.TestFeignRequestInterceptor;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    /**
     * test 1
     *
     * @author Doflamingo
     */
    @FeignClient(name = "test1", url = "http://hq.sinajs.cn", configuration = TestFeignRequestInterceptor.class)
    public interface FeignTest1Service {
    
        /**
         * 获取<>hq.sinajs.cn</>网站数据
         *
         * @param list
         * @return
         */
        @GetMapping("/")
        String getStockInfo(@RequestParam("list") String list);
    }
    

    这里还添加了一个Feign请求拦截器

    package com.mingo.exp.feign;
    
    import feign.RequestInterceptor;
    import feign.RequestTemplate;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * Feign请求拦截
     *
     * @author Doflamingo
     */
    @Configuration
    public class TestFeignRequestInterceptor implements RequestInterceptor {
    
        /**
         * @param requestTemplate 请求模板
         */
        @Override
        public void apply(RequestTemplate requestTemplate) {
            // 这里我未操作requestTemplate 只打印一行文本
            System.out.println("Feign请求拦截...");
        }
    }
    

    测试类

    package com.mingo.exp.feign;
    
    import com.mingo.exp.feign.remote.FeignTest1Service;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class FeignRequestTest {
    
        @Autowired
        private FeignTest1Service feignTest1Service;
    
        @Test
        public void Test_1() {
            String result = feignTest1Service.getStockInfo("sz000063");
            System.out.println(result);
        }
    }
    

    在启动类上要添加@EnableFeignClients注解

    package com.mingo.exp;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    /**
     * @author Doflamingo
     */
    @SpringBootApplication
    @EnableFeignClients
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    

    运行结果

    Feign请求拦截...
    var hq_str_sz000063="中兴通讯,39.710,39.540,38.910,39.740,38.880,38.900,38.910,55869686,2183069899.570,516348,38.900,129234,38.890,575200,38.880,52700,38.870,66800,38.860,37900,38.910,127819,38.920,49100,38.930,24811,38.940,82444,38.950,2020-07-30,15:00:03,00";
    

    2. 项目启动时:Feign引入

    对Feign的处理主要通过两个注解:@EnableFeignClients 和 @FeignClient。配置类加上@EnableFeignClients注解,在项目启动时Spring IOC容器注册Feign相关Bean。最终目的就是生成@FeignClient注解接口的代理类对象。

    先看@EnableFeignClients,如下图

    这里通过@Import(FeignClientsRegistrar.class)对Feign进行相关初始化,实现了ImportBeanDefinitionRegistrar接口,在项目启动时Spring会调用registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry)方法。

    项目启动时

    FeignClientsRegistrar类相关代码如下

    
    // ImportBeanDefinitionRegistrar接口方法,项目启动时会执行该方法
    // 这里的AnnotationMetadata存放启动类(com.mingo.exp.DemoApplication)相关元数据信息
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
    		BeanDefinitionRegistry registry) {
    	// Feign Client缺省配置
    	registerDefaultConfiguration(metadata, registry);
    	// 处理@FeignClient注解接口
    	registerFeignClients(metadata, registry);
    }
    
    // 默认的Feign配置类
    private void registerDefaultConfiguration(AnnotationMetadata metadata,
    		BeanDefinitionRegistry registry) {
    	// 获取@EnableFeignClients注解的属性
    	Map<String, Object> defaultAttrs = metadata
    			.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
    
    	if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
    		// default.com.mingo.exp.DemoApplication.FeignClientSpecification
    		String name;
    		if (metadata.hasEnclosingClass()) {
    			name = "default." + metadata.getEnclosingClassName();
    		}
    		else {
    			name = "default." + metadata.getClassName();
    		}
    		// Class<?>[] defaultConfiguration() default {};
    		registerClientConfiguration(registry, name,
    				defaultAttrs.get("defaultConfiguration"));
    	}
    }
    
    // 处理@FeignClient注解接口
    public void registerFeignClients(AnnotationMetadata metadata,
    		BeanDefinitionRegistry registry) {
    	// 这里用于搜索被@FeignClient注解的接口
    	ClassPathScanningCandidateComponentProvider scanner = getScanner();
    	scanner.setResourceLoader(this.resourceLoader);
    
    	Set<String> basePackages;
    
    	Map<String, Object> attrs = metadata
    			.getAnnotationAttributes(EnableFeignClients.class.getName());
    	// @FeignClient注解的标记
    	AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
    			FeignClient.class);
    	// 判断@EnableFeignClients注解clients属性是否有值
    	final Class<?>[] clients = attrs == null ? null
    			: (Class<?>[]) attrs.get("clients");
    	if (clients == null || clients.length == 0) {
    		// clients属性没值时才在basePackages中扫描被@FeignClient注解的接口
    		scanner.addIncludeFilter(annotationTypeFilter);
    		basePackages = getBasePackages(metadata);
    	}
    	else {
    		final Set<String> clientClasses = new HashSet<>();
    		basePackages = new HashSet<>();
    		for (Class<?> clazz : clients) {
    			basePackages.add(ClassUtils.getPackageName(clazz));
    			clientClasses.add(clazz.getCanonicalName());
    		}
    		AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
    			@Override
    			protected boolean match(ClassMetadata metadata) {
    				String cleaned = metadata.getClassName().replaceAll("\$", ".");
    				return clientClasses.contains(cleaned);
    			}
    		};
    		scanner.addIncludeFilter(
    				new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    	}
    
        // 扫描
    	for (String basePackage : basePackages) {
    		// 找到被@FeignClient注解的类
    		Set<BeanDefinition> candidateComponents = scanner
    				.findCandidateComponents(basePackage);
    		for (BeanDefinition candidateComponent : candidateComponents) {
    			if (candidateComponent instanceof AnnotatedBeanDefinition) {
    				AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
    				// 本例是com.mingo.exp.feign.remote.FeignTest1Service接口元数据信息
    				AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
    				// 只能在接口上注解
    				Assert.isTrue(annotationMetadata.isInterface(),
    						"@FeignClient can only be specified on an interface");
    
    				Map<String, Object> attributes = annotationMetadata
    						.getAnnotationAttributes(
    								FeignClient.class.getCanonicalName());
    				// 这里面会判断name或value是否存在
    				String name = getClientName(attributes);
    				// 本例是注册com.mingo.exp.feign.TestFeignRequestInterceptor
    				registerClientConfiguration(registry, name,
    						attributes.get("configuration"));
    
    				registerFeignClient(registry, annotationMetadata, attributes);
    			}
    		}
    	}
    }
    
    // 这里为每一个合法@FeignClient注解的接口生成一个FeignClientFactoryBean对象,注册到IOC容器
    private void registerFeignClient(BeanDefinitionRegistry registry,
    		AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    	String className = annotationMetadata.getClassName();
    	// FeignClientFactoryBean实现了FactoryBean接口,用于装饰和实例化Feign的客服端
    	// 其getObject()可得到动态代理的客户端对象
    	BeanDefinitionBuilder definition = BeanDefinitionBuilder
    			.genericBeanDefinition(FeignClientFactoryBean.class);
    	validate(attributes);
    	definition.addPropertyValue("url", getUrl(attributes));
    	definition.addPropertyValue("path", getPath(attributes));
    	String name = getName(attributes);
    	definition.addPropertyValue("name", name);
    	String contextId = getContextId(attributes);
    	definition.addPropertyValue("contextId", contextId);
    	definition.addPropertyValue("type", className);
    	definition.addPropertyValue("decode404", attributes.get("decode404"));
    	definition.addPropertyValue("fallback", attributes.get("fallback"));
    	definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
    	definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    
        // 本例是test1FeignClient
    	String alias = contextId + "FeignClient";
    	AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    
    	boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be null
    
    	beanDefinition.setPrimary(primary);
    
    	String qualifier = getQualifier(attributes);
    	if (StringUtils.hasText(qualifier)) {
    		alias = qualifier;
    	}
    
    	BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
    			new String[] { alias });
    	// 注册
    	BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
    
    // 用于注册Feign相关的ClientConfiguration,如拦截器等
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
    		Object configuration) {
    	BeanDefinitionBuilder builder = BeanDefinitionBuilder
    			.genericBeanDefinition(FeignClientSpecification.class);
    	// FeignClientSpecification(String name, Class<?>[] configuration)的构造器
    	builder.addConstructorArgValue(name);
    	builder.addConstructorArgValue(configuration);
    	// 注册bean
    	registry.registerBeanDefinition(
    			name + "." + FeignClientSpecification.class.getSimpleName(),
    			builder.getBeanDefinition());
    }
    

    __ FeignClientFactoryBean类实现了FactoryBean接口,用于装饰和实例化Feign的客服端。 FactoryBean为IOC容器生成Bean更加灵活。FactoryBean#getObject()方法得到的bean即是用@Autowired等真正注入的Feign客服端代理对象。__

    3. Bean的注入:@Autowired等

    也就是执行处理这行代码时

    FeignClientFactoryBean怎样生成代理对象

    // 在使用@Autowired等注入bean时,容器会调用getObject()生成业务真实的bean
    
    // FactoryBean#getObject()实现
    @Override
    public Object getObject() throws Exception {
    	return getTarget();
    }
    
    /**
     * @param <T> the target type of the Feign client
     * @return a {@link Feign} client created with the specified data and the context
     * information
     */
    <T> T getTarget() {
    	// 进行默认或设置的FeignClientsConfiguration的encoder、decoder、retryer等进行处理
    	FeignContext context = this.applicationContext.getBean(FeignContext.class);
    	Feign.Builder builder = feign(context);
    
        // 本例url = http://hq.sinajs.cn 所以不走这一步
        // 即是单纯的http请求,不需要配合Spring Cloud
    	if (!StringUtils.hasText(this.url)) {
    		if (!this.name.startsWith("http")) {
    			this.url = "http://" + this.name;
    		}
    		else {
    			this.url = this.name;
    		}
    		this.url += cleanPath();
    		return (T) loadBalance(builder, context,
    				new HardCodedTarget<>(this.type, this.name, this.url));
    	}
    	if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
    		this.url = "http://" + this.url;
    	}
    
    	// 可是设置@FeignClient的path属性,会在这里接在url后面
    	String url = this.url + cleanPath();
    	// 本例是null
    	Client client = getOptional(context, Client.class);
    	// 单纯的http请求,不需要配合Spring Cloud
    	if (client != null) {
    		if (client instanceof LoadBalancerFeignClient) {
    			// not load balancing because we have a url,
    			// but ribbon is on the classpath, so unwrap
    			client = ((LoadBalancerFeignClient) client).getDelegate();
    		}
    		if (client instanceof FeignBlockingLoadBalancerClient) {
    			// not load balancing because we have a url,
    			// but Spring Cloud LoadBalancer is on the classpath, so unwrap
    			client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
    		}
    		builder.client(client);
    	}
    	// HystrixTargeter
    	Targeter targeter = get(context, Targeter.class);
    	// 生成代理对象bean。HardCodedTarget对象用于存type、url等信息
    	return (T) targeter.target(this, builder, context,
    			new HardCodedTarget<>(this.type, this.name, url));
    }
    

    上述代码的进入HystrixTargeter.target(...)

    进入Feign.Builder.target()

    进入ReflectiveFeign.newInstance()

    
    // ReflectiveFeign构造器
    ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
          QueryMapEncoder queryMapEncoder) {
    	this.targetToHandlersByName = targetToHandlersByName;
    	this.factory = factory;
    	this.queryMapEncoder = queryMapEncoder;
    }
    
    // 生成代理对象
    @SuppressWarnings("unchecked")
    @Override
    public <T> T newInstance(Target<T> target) {
    	// targetToHandlersByName.apply(target) 会对@FeignClient注解接口方法生成对应的SynchronousMethodHandler
    	Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    	// 这里面存的就是每一个远程调用方法的实际处理 SynchronousMethodHandler
    	Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    	List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    
    	for (Method method : target.type().getMethods()) {
    	  if (method.getDeclaringClass() == Object.class) {
    	    continue;
    	  } else if (Util.isDefault(method)) {
    	    DefaultMethodHandler handler = new DefaultMethodHandler(method);
    	    defaultMethodHandlers.add(handler);
    	    methodToHandler.put(method, handler);
    	  } else {
    	  	// Methed对象为key
    	    methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    	  }
    	}
    	// 生成对@FeignClient注解接口方法的代理处理
    	InvocationHandler handler = factory.create(target, methodToHandler);
    	// 生成代理对象
    	T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler);
    
    	for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
    	  defaultMethodHandler.bindTo(proxy);
    	}
    	// 生成的代理对象即是IOC容器注入的bean
    	return proxy;
    }
    
    // 类FeignInvocationHandler
    static class FeignInvocationHandler implements InvocationHandler {
    
    	private final Target target;
    	private final Map<Method, MethodHandler> dispatch;
    
        // InvocationHandler handler = factory.create(target, methodToHandler);这行代码触发
        // dispatch的key即是方法名,MethodHandler即是实际的代理处理(SynchronousMethodHandler)
    	FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
    	  this.target = checkNotNull(target, "target");
    	  this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    	}
        
        // 代理处理@FeignClient接口的方法
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	  // 继承Object的方法处理
    	  if ("equals".equals(method.getName())) {
    	    try {
    	      Object otherHandler =
    	          args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
    	      return equals(otherHandler);
    	    } catch (IllegalArgumentException e) {
    	      return false;
    	    }
    	  } else if ("hashCode".equals(method.getName())) {
    	    return hashCode();
    	  } else if ("toString".equals(method.getName())) {
    	    return toString();
    	  }
    
          // 找到对应的SynchronousMethodHandler 并调用
    	  return dispatch.get(method).invoke(args);
    	}
    
    	// 省略了其他代码
    }
    

    @FeignClient注解接口方法生成对应的SynchronousMethodHandler

    生成的代理对象

    4. 接口方法的调用

    下一篇文章在细谈。

    原创 Doflamingo https://www.cnblogs.com/doflamingo
  • 相关阅读:
    VA对于开发QT是神器,VA自动补全QT
    64位下好神奇啊(增加了PatchGuard技术保护自己,SSDT是相对地址,参数通过寄存器与rdi来传递)
    贵在坚持
    VC程序查错之内存访问异常
    QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数
    QTimer源码分析(以Windows下实现为例)
    Go语言的优点(oschina讨论)
    致诸位新程序员:来自Chuck Jazdzewski慈父般的忠告
    poj1483 It's not a Bug, It's a Feature!
    App应用与思考
  • 原文地址:https://www.cnblogs.com/doflamingo/p/13407449.html
Copyright © 2020-2023  润新知