java实现一个简单的feign

69 阅读2分钟

背景

在日常开发中,经常用到openfeign,于是,就是想试试参考openfeign,实现一个简单版的feign.

思路

创建注解:熟悉使用OpenFeign的都知道,其有两个重要的注解,@EnableFeignClients和@FeignClient,其中@FeignClient的作用,就是声明一个Feign客户端(可以把他看成一个Controller),而@EnableFeignClients的作用就是开启Feign功能(将被@FeignClient标记的bean注入到Spring容器当中),所以,第一步,要创建两个类似的注解。
实现@EnableFeignClients的功能:我们都知道@EnableFeignClients通过basePackages或者basePackageClasses属性可以扫码某些包下的类进行扫描并注册为bean。而在SpringBoot中,提供ImportBeanDefinitionRegistrar接口和ClassPathBeanDefinitionScanner来实现类似的功能。
实现@FeignClient的功能:@FeignClient主要功能是实现接口请求,我们可以通过SpringFactoryBean创建一个bean,在getObject()返回一个可以发起请求的代理对象。

创建EnableMyFeignClient和MyFeignClient注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyFeignClientsRegistrar.class)
public @interface EnableMyFeignClient {

    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MyFeignClient {

    String name() default "";
    
    String path() default "";

    String url() default "";

    String contextId() default "";

}

实现EnableMyFeignClient的功能

/**
 * 获取EnableMyFeignClient的属性值,并扫描相应的包路径,注册FeignClient的BeanDefinition
 * @Date 2024/11/29
 */
public class MyFeignClientsRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableMyFeignClient.class.getName());
        AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(attributes);
        List<String> basePackages = new ArrayList<>();
        String[] values = annotationAttributes.getStringArray("value");
        if (values != null && values.length > 0) {
            basePackages.addAll(Arrays.asList(values));
        }
        if (basePackages == null || basePackages.size() == 0) {
            String[] strings = (String[]) attributes.get("basePackages");
            basePackages.addAll(Arrays.asList(strings));
        }
        if (basePackages == null || basePackages.size() == 0) {
            Class<?>[] basePackageClasses = (Class<?>[]) attributes.get("basePackageClasses");
            for (int i = 0; i < basePackageClasses.length; i++) {
                String name = basePackageClasses[i].getPackage().getName();
                basePackages.add(name);
            }
        }
        if (Objects.isNull(basePackages) || basePackages.size() == 0) {
            throw new IllegalArgumentException("EnableMyFeign basePackages must not be empty");
        }
        FeignClientClassPathBeanDefinitionScanner scanner = new FeignClientClassPathBeanDefinitionScanner(registry);
        scanner.doScan(basePackages.toArray(new String[0]));

    }



}
/**
* 扫描FeignClient接口,并注册为FactoryBean
* @Date 2024/11/29
*/
public class FeignClientClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {

   public FeignClientClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
       super(registry);
   }

   @Override
   protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
       return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
   }


   @Override
   protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
       // 只扫描添加MyFeignClient的类
       addIncludeFilter(new AnnotationTypeFilter(MyFeignClient.class));
       Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
       if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
           for (BeanDefinitionHolder holder : beanDefinitionHolders) {
               // 注册bean
               BeanDefinition beanDefinition = holder.getBeanDefinition();
               GenericBeanDefinition definition = (GenericBeanDefinition) beanDefinition;
               // 获取接口的全路径名称
               String beanClassName = definition.getBeanClassName();
               // 设置构造函数参数
               definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
               // 设置bean的类型为MyFeignClientFactoryBean
               definition.setBeanClass(MyFeignClientFactoryBean.class);
               definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
           }
       }
       return beanDefinitionHolders;
   }
}

实现MyFeignClient功能

/**
 * 通过FactoryBean创建bean
 * @Date 2024/11/29
 */
public class MyFeignClientFactoryBean<T> implements FactoryBean<T> {

    private Class<T> type;

    public MyFeignClientFactoryBean(Class<T> type) {
        this.type = type;
    }

    @Override
    public T getObject() throws Exception {
        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type},
                new DefaultFeignClient<>(type));
    }

    @Override
    public Class<?> getObjectType() {
        return type;
    }

}
/**
 * 实现InvocationHandler 接口,重写invoke方法,从而实现远程请求
 * @Date 2024/11/29
 */
public class DefaultFeignClient<T> implements InvocationHandler {

    private Class<T> type;

    public DefaultFeignClient(Class<T> type) {
        this.type = type;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Object 方法,走原生方法, 比如 hashCode()
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        // 其它走动态代理
        Class<?> declaringClass = method.getDeclaringClass();
        MyFeignClient client = declaringClass.getAnnotation(MyFeignClient.class);
        //获取域名/服务ip地址
        String domain = client.path();

        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof GetMapping) {
                GetMapping getAnno = (GetMapping) annotation;
                //OkHttp 发起请求, getAnno.value()为GetMapping的路径
                Response response = OkHttpUtils.doGet(domain + getAnno.value());
                String resp = response.body().string();
                return resp;
            }
        }

        return null;
    }
}

测试

@MyFeignClient(name = "test", path = "http://localhost:8080")
public interface TestClient {

    @GetMapping("/user/get")
    String test();

}
@RestController
@RequiredArgsConstructor
@RequestMapping("/order")
public class OrderController {

    private final TestClient testClient;

    @GetMapping(value = "/test")
    public String test() {
        return testClient.test();
    }
@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/user")
public class UserController {

    @GetMapping("/get")
    public R getUserByPhone() {

        return R.success("请求成功");

    }

}

image.png