我们都知道fegin 是一个很好用的远程调用工具, 底层就是restTemplate 实现的。但是具体他是怎么工作的,这里我们我们今天来自己实现一下, 仿写一个feign。
我们先设计一下目录结构,
1, 自定义注解扫描feignScan 扫描 ,带有自定义注解feignClient 标记的注册进去。
2, 自定义factoryBean 用代理 resttempalte 实现真正的调用。
里面主要用到的是 ClassPathBeanDefinitionScanner,ImportBeanDefinitionRegistrar , cglib 代理
项目结构:
3, 先定义注解
代码语言:javascript复制/**
* 开启feign的使用, 在启动类上面定义扫描的包路径。
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(FeignRegister.class)// 使用了自定义bean的注册器
public @interface EnableFeign {
//扫描包的路径
String[] basePackages() default {};
}
/**
* 用来声明加入spring的ioc 容器里面 , 在获取baseUrl 的值。
* baseurl 可以用类似 @value 注解的方式从配置文件读取。
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
//根部的url,可以从配置文件读取 , 必传。
String baseUrl();
}
/**
* 作用在方法上面,表明请求的url 和方法。方法是一个枚举类型
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignRequest {
//请求url
String url();
//默认请求方式get
RequestMethonEnum methon() default RequestMethonEnum.GET;
}
/**
* 请求方式枚举类型
*/
public enum RequestMethonEnum {
GET,POST,DELETE
}
4, 配置类
代码语言:javascript复制// 注册类
public class FeignRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
Logger logger = LoggerFactory.getLogger(FeignRegister.class);
private Environment environment;
/**
* 要将环境变量的配置加进去
* @param environment
*/
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set<String> packages = getPackages(importingClassMetadata);
CustomScanner customScanner = new CustomScanner(registry, environment);
customScanner.scan(packages.toArray(new String[0]));
}
/**
* 获取包名称,并且去重
* @param metadata
* @return
*/
private Set<String> getPackages(AnnotationMetadata metadata) {
AnnotationAttributes attributes =
AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(EnableFeign.class.getName()));
// 获取basePackages 的属性
String[] basePackages = attributes.getStringArray("basePackages");
return new LinkedHashSet<>(Arrays.asList(basePackages));
}
}
// 扫描类
public class CustomScanner extends ClassPathBeanDefinitionScanner {
Logger logger = LoggerFactory.getLogger(CustomScanner.class);
private Environment environment;
/**
* 构造器给register 注册用的
* @param registry
* @param environment
*/
public CustomScanner(BeanDefinitionRegistry registry,Environment environment) {
super(registry, false);
this.environment = environment;
registerFilter();
}
public void registerFilter () {
// 表示要过滤出带有 feignClient 注解的类
addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
}
//扫描包下待有`@feignClient` 注解的接口,调用 processBeanDefinitions() 实现接口代理类生成注册
@Override
protected Set<BeanDefinitionHolder> doScan (String...basePackages){
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No @feignClient interface was found in '" Arrays.toString(basePackages) "' package. Please check your configuration.");
} else {
// 执行以下bean的注册
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
/**
* 重写候选判断逻辑,捞出带有注解的接口
*
* @param beanDefinition
* @return
*/
@Override
protected boolean isCandidateComponent (AnnotatedBeanDefinition beanDefinition){
//interface 接口会自动跳过, 这里要把接口加进去beanDefinition 里面
boolean isCandidate = false;
AnnotationMetadata metadata = beanDefinition.getMetadata();
if (metadata.isIndependent()) {
if ( !metadata.isAnnotation() && metadata.hasAnnotation(FeignClient.class.getName())) {
isCandidate = true;
}
}
return isCandidate;
}
/**
* 真正进行bean 的注册
* @param beanDefinitions
*/
private void processBeanDefinitions (Set < BeanDefinitionHolder > beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// definition.getConstructorArgumentValues(),BeanClass需要提供包含该属性的构造方法,否则会注入失败
definition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(definition.getBeanClassName()));
// 获取注解的元注解对象
AnnotationMetadata beanMetadata = ((AnnotatedBeanDefinition) definition).getMetadata();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(beanMetadata.getAnnotationAttributes(FeignClient.class.getName()));
//解析interface上@FeignClientInfo注解信息,转换成FeignClientInfo对象
FeignClientInfo feignClientInfo = new FeignClientInfo(attributes, environment);
// 使用自己的factoryBean
definition.setBeanClass(FeignFactoryBean.class);
//FactoryBean属性
definition.getPropertyValues().addPropertyValue("feignClientInfo", feignClientInfo);
}
}
}
public class FeignFactoryBean<T> implements FactoryBean<T> , ApplicationContextAware {
Logger logger = LoggerFactory.getLogger(FeignFactoryBean.class);
private Class<T> targetClass;
private ApplicationContext applicationContext;
private FeignClientInfo feignClientInfo;
public FeignFactoryBean(Class<T> targetClass) {
this.targetClass = targetClass;
}
@Override
public boolean isSingleton() {
return false;
}
@Override
@SuppressWarnings("unchecked")
public T getObject() throws Exception {
return ProxyUtil.newProxyInstance(targetClass, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取resttemplate
RestTemplate restTemplate = applicationContext.getBean(RestTemplate.class);
MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
// 参数绑定
for (int index = 0; index < args.length; index ) {
Parameter p = method.getParameters()[index];
params.add(p.getName(), args[index]);
}
String url = feignClientInfo.getBaseUrl();
FeignRequest annotation = method.getAnnotation(FeignRequest.class);
logger.info("FeignRequest注解上面的url是: " url);
RequestMethonEnum methon = annotation.methon();
String response = null;
// 这里只写了post
switch (methon){
case POST :
response = restTemplate.postForObject(url annotation.url(), params, String.class);
break;
}
// 判断返回类型是不是string
if (method.getReturnType() == String.class) {
return response;
}
return JSONObject.parseObject(response, method.getReturnType());
}
});
}
@Override
public Class<?> getObjectType() {
return targetClass;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
public FeignClientInfo getFeignClientInfo() {
return feignClientInfo;
}
public void setFeignClientInfo(FeignClientInfo feignClientInfo) {
this.feignClientInfo = feignClientInfo;
}
}
@Configuration
public class RestTemplateConfig {
/**
* 生命restTemplate是一个bean
* @return
*/
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplateBuilder().build();
}
}
5, 具体使用
启动项目看一下日志
postman 调一下看一下结果
参数是我写死的,这里只是测试,最终能返回json 格式的response 就行了。