我们知道要使用feign,需要在springboot启动类放入@EnableFeignClients开关来打开feign的使用。
@EnableFeignClients
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
然后会使用例如
@FeignClient("user-center")
public interface UserClient {
@PostMapping("/users-anon/finduser")
LoginAppUser findUserByName(@RequestParam("username") String username);
}
现在我们可以考虑的方向是打上了@FeignClient("user-center")标签后,UserClient接口是如何被实例化成对象的,然后是在Controller中调用UserClient对象是如何进行网络请求的。
@Slf4j
@RestController
public class TokenController {
@Autowired
private Oauth2Client oauth2Client;
@Autowired
private UserClient userClient;
/**
* 系统登陆<br>
* 根据用户名登录<br>
* 采用oauth2密码模式获取access_token和refresh_token
*
* @param username
* @param password
* @return
*/
@SuppressWarnings("unchecked")
@PostMapping("/sys/login")
public Result<Map> login(@RequestParam String username,@RequestParam String password) {
Map<String, String> parameters = new HashMap<>();
parameters.put(OAuth2Utils.GRANT_TYPE, "password");
parameters.put(OAuth2Utils.CLIENT_ID, "system");
parameters.put("client_secret", "system");
parameters.put(OAuth2Utils.SCOPE, "app");
// parameters.put("username", username);
// 为了支持多类型登录,这里在username后拼装上登录类型
parameters.put("username", username + "|" + CredentialType.USERNAME.name());
parameters.put("password", password);
Map<String, Object> tokenInfo = oauth2Client.postAccessToken(parameters);
AppUser user = userClient.findUserByName(username);
tokenInfo.put("user",user);
saveLoginLog(username, "用户名密码登陆");
return Result.success(tokenInfo);
}
在feign的框架源码中,有3个类是比较重要的,FeignClientFactoryBean,FeignContext,SynchronousMethodHandler.
我们先来看一下@EnableFeignClients的作用。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class) //用于处理@FeignClient注解
public @interface EnableFeignClients {
//以下这三个都是用于指定需要扫描的包
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
//用于自定义feign client的自定义配置,可以配置Decoder(解码器),Encoder(编码器)和Contract等组件,
//FeignClientsConfiguration是默认的配置类
Class<?>[] defaultConfiguration() default {};
//指定被@FeignClient修饰的类,如果不为空,那么路径自动检测机制会被关闭
Class<?>[] clients() default {};
}
@Import注解有四个作用
FeignClientsRegistrar是用来处理@FeignClient修饰的FeignClient接口类,将这些接口类的BeanDefinition注册到Spring容器中,这样就可以使用@Autowired等方式来自动装载这些@FeignClient接口类的Bean实例。
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware
FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,该接口是Spring实现bean动态注入的。我们来看一下该接口的方法
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
在FeignClientsRegistrar中的实现
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//从EnableFeignClients的属性值来构建Feign的自定义Configuration进行注册
registerDefaultConfiguration(metadata, registry);
//扫描package,注册被@FeignClient修饰的接口类的Bean信息
registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//将@EnableFeignClients的所有属性值导入map中
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
//如果@EnableFeignClients配置了defaultConfiguration,则往下运行。如果没有,rantion
//会使用默认的FeignConfiguration,一般我们这里不会设置这些配置,都是默认,但是如果设置了,
//就会好比我们自己写了@Configuration的类一样的效果,这里是自动配置了
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
//如果原始类中有内部类,获取内部类名
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
//否则获取原始类名
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
//获取一个FeignClientSpecification的建造器
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
//传入构造参数
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
//将FeignClientSpecification当成内部类对象进行注册
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
以上这里可以方便我们学习怎么写一个标签达到自动配置效果。以下是扫描包,并把打了@FeignClient标签的接口给加载到Spring容器中。
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
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) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
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());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}