一、Feign的基本使用
1.1、编写Feign客户端
假设现在我们有一个UserClient,如下:
@FeignClient(value = Services.SYSTEM_SERVER)
public interface UserClient {
/**
* 根据用户名,获取用户信息
* @param account
* @return
*/
@RequestMapping(value = "/user/getByAccount", method = RequestMethod.GET)
LoginInfo getUser(@RequestParam("account") String account);
}
1.2、 使用FeignClient远程调用
feign的调用也非常的简单,如下:
public class FeignClientTests {
@Autowired
private UserClient userClient;
public void getUser() {
LoginInfo userInfo = userClient.getUser("zhangsan");
}
}
1.3、UserClient测试调用引发的疑问
可以看出来 UserClient
使用的时候是使用@autowired
注解注入的,但是很明显的是,UserClient
是一个接口(interface
),它并没有实现类,比如(UserClientImpl implements UserClient
)。那么它是如何被注入并且可以直接使用的呢?
二、验证接口在Spring注入
2.1、 Spring的Class类型组件注入
编写SpringComponent
组件
@Component
public class SpringComponent {
public void helloWorld() {
System.out.println("Hello World");
}
}
编写测试类调用组件
@RunWith(SpringRunner.class)
@SpringBootTest
public class HttpApplicationTests {
@Autowired
SpringComponent springComponent;
@Test
public void contextLoads() {
springComponent.helloWorld();
}
}
执行一下测试类,输出:
Hello World
2.2、 Spring注入接口组件尝试
编写SpringInterfaceComponent
接口组件
@Component
public interface SpringInterfaceComponent {
default void helloWorld() {
System.out.println("Hello World");
}
}
编写测试类调用组件
@RunWith(SpringRunner.class)
@SpringBootTest
public class HttpApplicationTests2 {
@Autowired
SpringInterfaceComponent springInterfaceComponent;
@Test
public void contextLoads() {
springInterfaceComponent.helloWorld();
}
}
执行一下测试类,输出:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.jumper.lib.http.demo.SpringInterfaceComponent' available
很明显,注入不了,因为他没有实现类。
2.3、 对Spring注入接口类改写
SpringInterfaceComponent
移除@Component
注解
public interface SpringInterfaceComponent {
default void helloWorld() {
System.out.println("Hello World");
}
}
编写SpringInterfaceComponent
的实现类
@Component
public class SpringInterfaceComponentImpl implements SpringInterfaceComponent {
}
测试类调用组件
@RunWith(SpringRunner.class)
@SpringBootTest
public class HttpApplicationTests2 {
@Autowired
SpringInterfaceComponent springInterfaceComponent;
@Test
public void contextLoads() {
springInterfaceComponent.helloWorld();
}
}
执行一下测试类,输出:
Hello World
2.4、 验证猜想(Feign
配置源码解析)
可以猜测,Feign
一定是为接口生成了实现类注入到Spring
容器里了。
那我们一起来看看Feign的注入
原理
2.4.1 Feign
的启动入口(@EnableFeignClients
)
@EnableFeignClients(basePackages = ProjectConstants.baseScanPackage)
@SpringBootApplication(scanBasePackages = ProjectConstants.baseScanPackage)
public class AuthServer {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(AuthServer.class, args);
String port = run.getEnvironment().getProperty("server.port");
log.info("Auth Server is running --> http://localhost:{}", port);
}
}
编写启动类的时候我们会加一个@EnableFeignClients
注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
String[] basePackages() default {};
}
进入@EnableFeignClients
注解可以看到重点就在于存在一个@Import(FeignClientsRegistrar.class)
Feigin
通过@Import
来导入FeignClientsRegistrar.class
(FeignClient
解析客户端并注册到Spring容器
的实现类)
2.4.2 FeiginClient
注册器(FeignClientsRegistrar.class
)
2.4.2.1、FeignClientsRegistrar
声明
FeignClientsRegistrar
他分别实现了ImportBeanDefinitionRegistrar
、ResourceLoaderAware
、EnvironmentAware
三个接口
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {}
我们看看ImportBeanDefinitionRegistrar
源码:
可以看出它声明一个注册Bean的接口
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
我们再看看ResourceLoaderAware
源码:
就是一个注入ResourceLoader
的接口
public interface ResourceLoaderAware extends Aware {
/**
* Set the ResourceLoader that this object runs in.
* <p>This might be a ResourcePatternResolver, which can be checked
* through {@code instanceof ResourcePatternResolver}. See also the
* {@code ResourcePatternUtils.getResourcePatternResolver} method.
* <p>Invoked after population of normal bean properties but before an init callback
* like InitializingBean's {@code afterPropertiesSet} or a custom init-method.
* Invoked before ApplicationContextAware's {@code setApplicationContext}.
* @param resourceLoader the ResourceLoader object to be used by this object
* @see org.springframework.core.io.support.ResourcePatternResolver
* @see org.springframework.core.io.support.ResourcePatternUtils#getResourcePatternResolver
*/
void setResourceLoader(ResourceLoader resourceLoader);
}
再看看EnvironmentAware
源码:
是一个注入Environment
的接口
public interface EnvironmentAware extends Aware {
/**
* Set the {@code Environment} that this component runs in.
*/
void setEnvironment(Environment environment);
}
我们再继续看FeignClientsRegistrar
它的成员变量,ResourceLoaderAware、 EnvironmentAware
需要注入的资源
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
public FeignClientsRegistrar() {
}
2.4.2.2、registerBeanDefinitions
方法
继续看看它最重要的实现接口
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// 注册默认配置
registerDefaultConfiguration(metadata, registry);
// 注册FeignClient
registerFeignClients(metadata, registry);
}
2.4.2.3、registerFeignClients
方法
砸门也不关注注册配置的方法了,这次只探讨注册FeignClient
的实现
我们看看registerFeignClients(metadata, registry);
方法
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
// ClassPath的条件扫描组件提供者
ClassPathScanningCandidateComponentProvider scanner = getScanner();
// 设置资源加载器
scanner.setResourceLoader(this.resourceLoader);
// 要扫描的包(@EnableFeignClients注解上添的那个)
Set<String> basePackages;
// 获取注解上的配置
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
// 注解过滤器,设置只过滤出FeignClient注解标识的Bean
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
// 扫描器设置过滤器
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) {
// 从指定的包中扫描出和规范的BeanDefinition
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
// 扫描的Bean是否是AnnotatedBeanDefinition的子类
// 虽然看不懂AnnotatedBeanDefinition是什么意思,但是顾名思义我觉得是通过注解扫出来的BeanDefinition就是他的子类
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
// 获取beanDefinition的元数据,你想要的他基本都有
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
// 验证@FeignClient修饰的必须是接口
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
// 获取@FeignClient注解的属性
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
// 获取客户端名称
String name = getClientName(attributes);
// 为FeignClient指定配置类
registerClientConfiguration(registry, name,
attributes.get("configuration"));
// 注册客户端
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
2.4.2.4、注册FeiginClient
具体实现registerFeignClient
方法
由上可见,准备工作做好了,那么最关键的就是registerFeignClient(registry, annotationMetadata, attributes);
方法了,我们继续探索。
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
// 被@FeignClient修饰的类名,比如 com.xxx.TestFeignClient,是自己编辑的
String className = annotationMetadata.getClassName();
// BeanDefinitionBuilder通过FeignClientFactoryBean这个类来生成BeanDefinition
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// 验证fallback和fallbackFactory是不是接口
validate(attributes);
// 通过BeanDefinitionBuilder给beanDefinition增加属性
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
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);
String alias = name + "FeignClient";
// 用Builder获取实际的BeanDefinition
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;
}
// 创建一个Bean定义的持有者
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
// 这里就是将Bean注册到Spring容器中
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
由上面一段代码,FeignClient
客户端注册就此完成。
但是上面还有两个重点还没看完
FeignClientFactoryBean.class
是如何实现的?BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
是注册到哪里?
我们先看第二个问题,直接进BeanDefinitionReaderUtils
源码:
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
Debug跟踪发现具体注册实现是在DefaultListableBeanFactory
中进行的。
现在回过头来看第一个问题就是FeignClientFactoryBean.class
这个类的实现是什么,因为上面的一段代码并没有看出来注册的BeanDefinition
跟我们自己编写的FeignClient
(例如上面的:UserClient
)有什么关系?
根据上面的代码推断,BeanDefinitionBuilder
是根据这个类创建出来的,并且与被@FeignClient
修饰的客户端的关系也是在这里实现的,我们一起来看看他的源码:
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {}
声明部分可以看到他实现了哪几个接口,这里我们只关注FactoryBean
这个接口,进去看看源码:
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
既然是注册Bean那么应该关注的就是获取bean的方法,所以我们一起看看FeignClientFactoryBean
重载的getObject()
方法
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
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();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
这段代码关注最后两行就好了,该方法的返回值是Object类型的。是通过Targeter返回的,Targeter是一个接口:
interface Targeter {
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target);
}
源码里并没有Java Doc很是难受,但是看得出来他是创建一个Feign代理对象。实现类由两个分别是DefaultTargeter
和HystrixTargeter
DefaultTargeter
源码很简单,就是直接调用Fegin的target方法生成目标对象。
class DefaultTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
return feign.target(target);
}
}
HystrixTargeter
源码复杂点,用的是HystrixFeign.Builder
来包装的
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
return feign.target(target);
feign.target()
源码:
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
}
}
其实看到这里就已经可以看出来了feign
生成代理是用的反射(生成ReflectiveFeign
对象)。我们通用的代理有JDK动态代理
和CGLIB
两种代理。我们继续看看Feign
用的是哪种呢?
2.4.5、ReflectiveFeign.newInstance(target)
我们直接看ReflectiveFeign
的newInstance
方法的源码:
@Override
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
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 {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
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);
}
return proxy;
}
忽略他生成的细节,其中有两行代码:
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
可见,feign
用的是jdk动态代理
。
三、小结
3.1、Spring
可以直接注入接口吗?
不行,需要实现类或者代理注册到Spring容器
里才能注入成功。
3.2、我想写一个框架然后像Feign
这样根据自定义注解来注册自己的Bean,怎么做?
不要讲原理,直接就是一把梭子,对着Feign
注册的源码就是一顿Ctrl+a/c
。
梳理流程如下
- 编写
@EnableXXX
注解,并且注解提供扫描包路径,或者不提供扫描包路径,直接扫描整个项目。 @EnableXXX
注解上加上@Import(MyBeanRegister.class)
注解导入你自己的Bean注册器。- 编写自己的
MyBeanRegister
并实现ImportBeanDefinitionRegistrar
、ResourceLoaderAware
、EnvironmentAware
这三个接口,主要重写registerBeanDefinitions
方法 - 编写一个
MyFactoryBean
实现FactoryBean<Object>
、InitializingBean
、ApplicationContextAware
,主要重写FactoryBean
的几个接口,返回接口代理对象实现。
3.3 我不仅要实现自定义注册,我还想像Feign一样就通过接口来注册Bean并且可以注入直接调用自定义方法,怎么实现?
Feign
使用的是JDK动态代理
,那就按JDK动态代理
的方式来简单实现一下这个调用,代码如下:
/**
* 仿FeignClient注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RxClient {
/**
* url
*/
String baseUrl();
/**
* 前缀
*/
String prefix() default "";
}
/**
* 仿RequestLine注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RxGet {
/**
* 请求路径 /login
*/
String path();
}
/**
* 模拟feign代理,简化执行过程
*/
public class RxClientProxy implements InvocationHandler {
private String baseUrl;
private String prefix;
public RxClientProxy(String baseUrl, String prefix) {
this.baseUrl = baseUrl;
this.prefix = prefix;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RxGet annotation = method.getAnnotation(RxGet.class);
String url = getUrl(annotation);
// 模拟调用接口
Object rs = mockGet(url);
System.out.println(String.format("模拟调用接口:\nurl -> %s\nres -> %s\n", url, rs));
return rs;
}
private Object mockGet(String url) {
return url.contains("username")?"一上访人":"幼儿园丶抠脚上单";
}
private String getUrl(RxGet annotation) {
return this.baseUrl + this.prefix + annotation.path();
}
}
/**
* 生成RxClient代理对象工厂
*/
public class RxClientFactory {
@SneakyThrows
public Object getInstance(String className) {
Class<?> aClass = Class.forName(className);
RxClient annotation = aClass.getAnnotation(RxClient.class);
return Proxy.newProxyInstance(aClass.getClassLoader(),
new Class[]{aClass},
new RxClientProxy(annotation.baseUrl(), annotation.prefix()));
}
}
/**
* 用户接口调用测试类
*/
@RxClient(baseUrl = "http://localhost:8080", prefix = "/user")
public interface UserRxClient {
@RxGet(path = "/username")
String getUsername();
@RxGet(path = "/nickname")
String getNickname();
}
/**
* RxClient测试类
*/
public class RxClientCallTest {
public static void main(String[] args) {
// 创建RxClient工厂
RxClientFactory factory = new RxClientFactory();
// 全路径反射出客户端
UserRxClient userRxClient = (UserRxClient) factory
.getInstance("com.jumper.lib.http.proxy.impl.UserRxClient");
// 调用获取昵称接口
userRxClient.getNickname();
// 调用获取用户名接口
userRxClient.getUsername();
}
}
执行RxClientCallTest
的main
方法输出:
模拟调用接口:
url -> http://localhost:8080/user/nickname
res -> 幼儿园丶抠脚上单
模拟调用接口:
url -> http://localhost:8080/user/username
res -> 一上访人
四、留言
写到这里本篇文章内容基本完结了,结合以上内容相信各位小伙伴都可以写出来了自己的自定义请求代理了吧。有兴趣的朋友评论留下实现的代码的Github或Gitee项目地址吧,我会把评论区大家的地址都发布出来,砸门一起共同学习进步。
P:如果错误请大家帮忙指出,及时修改。
五、已实现Demo练习地址
博客代码演示地址:https://gitee.com/MyAngle/HttpRequestCore
原文:https://blog.csdn.net/weixin_46496706/article/details/105320596
●阿里巴巴为什么能抗住90秒100亿?--服务端高并发分布式架构演进之路
●SpringCloud电商秒杀微服务-Redisson分布式锁方案
查看更多好文,进入公众号--撩我--往期精彩
一只 有深度 有灵魂 的公众号0.0