
假设现在我们有一个UserClient,如下:
@FeignClient(value = Services.SYSTEM_SERVER)
public interface UserClient {
@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)。那么它是如何被注入并且可以直接使用的呢?
3 二、验证接口在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 World2.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 World2.4、 验证猜想(Feign配置源码解析)
可以猜测,Feign一定是为接口生成了实现类注入到Spring容器里了。
那我们一起来看看Feign的注入原理
@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容器的实现类)
FeignClientsRegistrar 他分别实现了importBeanDefinitionRegistrar、ResourceLoaderAware、EnvironmentAware三个接口
class FeignClientsRegistrar implements importBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {}
我们看看importBeanDefinitionRegistrar源码:
可以看出它声明一个注册Bean的接口
public interface importBeanDefinitionRegistrar {
public void registerBeanDefinitions(
Annotationmetadata importingClassmetadata, BeanDefinitionRegistry registry);
}
我们再看看ResourceLoaderAware源码:
就是一个注入ResourceLoader的接口
public interface ResourceLoaderAware extends Aware {
void setResourceLoader(ResourceLoader resourceLoader);
}
再看看EnvironmentAware 源码:
是一个注入Environment 的接口
public interface EnvironmentAware extends Aware {
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 basePackages;
// 获取注解上的配置
Map 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 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 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 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, Mapattributes) { // 被@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)有什么关系?
2.4.3、FeignClientFactoryBean根据上面的代码推断,BeanDefinitionBuilder是根据这个类创建出来的,并且与被@FeignClient修饰的客户端的关系也是在这里实现的,我们一起来看看他的源码:
class FeignClientFactoryBean implements FactoryBean
声明部分可以看到他实现了哪几个接口,这里我们只关注FactoryBean这个接口,进去看看源码:
public interface FactoryBean{ @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 target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget target);
}
源码里并没有Java Doc很是难受,但是看得出来他是创建一个Feign代理对象。实现类由两个分别是DefaultTargeter和HystrixTargeter
DefaultTargeter源码很简单,就是直接调用Fegin的target方法生成目标对象。
class DefaultTargeter implements Targeter {
@Override
public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget target) {
return feign.target(target);
}
}
HystrixTargeter源码复杂点,用的是HystrixFeign.Builder来包装的
@Override public2.4.4、feign.target()实现T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget 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()源码:
publicT target(Target 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 publicT newInstance(Target target) { Map nameToHandler = targetToHandlersByName.apply(target); Map methodToHandler = new linkedHashMap (); List defaultMethodHandlers = new linkedList (); 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/v。
梳理流程如下
编写@EnableXXX注解,并且注解提供扫描包路径,或者不提供扫描包路径,直接扫描整个项目。
@EnableXXX注解上加上@import(MyBeanRegister.class)注解导入你自己的Bean注册器。
编写自己的MyBeanRegister并实现importBeanDefinitionRegistrar、ResourceLoaderAware、EnvironmentAware这三个接口,主要重写registerBeanDefinitions方法
编写一个MyFactoryBean实现FactoryBean、InitializingBean、ApplicationContextAware ,主要重写FactoryBean的几个接口,返回接口代理对象实现。
Feign使用的是JDK动态代理,那就按JDK动态代理的方式来简单实现一下这个调用,代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface RxClient {
String baseUrl();
String prefix() default "";
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface RxGet {
String path();
}
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 -> %snres -> %sn", 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();
}
}
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();
}
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 -> 一上访人
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)