天天看点

自己动手实现一个简单的OpenFeign

自己动手实现一个简单的OpenFeign
写这个东西只是为了搞明白OpenFeign的实现原理,功能不全,仅供学习参考
           

定义注解

/**
 * 模拟 SpringBoot 扫描包
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface EnableFeignClient {

    String[] value();
}

/**
 * 模拟 Feign 客户端配置,其它复杂的参数都省略了
 **/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

    String value() default "";

    String url() default "";

}
/**
 * 模拟 SpringMvc 注解,供后续程序提取参数使用
 **/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GetMapping {

    String value();

}
/**
 * 模拟 SpringMvc 注解,供后续程序提取参数使用
 **/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {

    String value();

}
           

实现逻辑

核心思想是利用java的动态代理,生成代理对象,就是下面的代码

// java.lang.reflect.Proxy
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h);
           

围绕上面的核心逻辑,依次扩展出下面的逻辑:

// feign 上下文对象
public class Feign {

    InvocationFactory factory;

    public Feign(InvocationFactory factory) {
        this.factory = factory;
    }

    public <T> T newInstance(Class<T> type) {
        FeignClient client = type.getAnnotation(FeignClient.class);
        if (client == null) {
            return newInstance(type, "");
        } else {
            return newInstance(type, client.url());
        }
    }

    public <T> T newInstance(Class<T> type, String url) {
        HardCodedTarget<T> target = new HardCodedTarget<>(type, url);
        InvocationHandler invocationHandler = factory.create(target);

        return (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type() }, invocationHandler);
    }
    
}

// 代理执行器工厂类
public interface InvocationFactory {

    InvocationHandler create(Target target);

    static class DefaultInvocationFactory implements InvocationFactory {

        @Override
        public InvocationHandler create(Target target) {
            return new MyInvocationHandler(target, null);
        }
    }

}

// 代理实现方法
public class MyInvocationHandler implements InvocationHandler {

    private Target target;

    private MyHandler handler;

    public MyInvocationHandler(Target target, MyHandler handler) {
        this.target = target;
        this.handler = handler;
    }

	// 核心逻辑在这里
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        FeignClient client = (FeignClient) target.type().getAnnotation(FeignClient.class);
        if (client == null) {
            throw new Exception("这不是一个标准类");
        }

        // 用 get 请求进行测试
        GetMapping get = method.getAnnotation(GetMapping.class);
        if (get == null) {
            return method.invoke(proxy, args);
        }

        StringBuilder sb = new StringBuilder();
        sb.append(client.url()).append(get.value()).append("?");

        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter p = parameters[i];
            RequestParam requestParam = p.getAnnotation(RequestParam.class);
            sb.append(requestParam.value()).append('=').append(args[i]).append('&');
        }
		
		// 用 hutool 包中的工具类去执行远程调用
        String response = HttpUtil.get(sb.substring(0, sb.length() - 1));

		// 处理返回值
        Class<?> returnType = method.getReturnType();
        if (returnType == String.class) {
            return response;
        } else {
            return JSON.parseObject(response, returnType);
        }
    }
}

// 抄过来的客户端申明抽象
public interface Target<T> {

    Class<T> type();

    String name();

    String url();

}
public class HardCodedTarget<T> implements Target<T> {

    private Class<T> type;
    private String name, url;

    public HardCodedTarget(Class<T> type, String url) {
        this.type = type;
        this.url = url;
    }

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

    @Override
    public String name() {
        return name;
    }

    @Override
    public String url() {
        return url;
    }

}

           

开始测试

申明测试接口:

// 本地的一个服务
@FeignClient(value = "testClient", url = "http://localhost:9995")
public interface TestClient {

    @GetMapping("/order/detail")
    String queryOrder(@RequestParam("orderId") String orderId);

}
           

测试类的主要工作就是模拟 OpenFeign 在 SpringBoot 中的工作流程

@EnableFeignClient({
        "com.fang.test"
})
public class Test {

    public static void main(String[] args) {
        // 读取当前 main 注解信息  -> 
        // 对应 FeignClientsRegistrar.registerBeanDefinitions()
        EnableFeignClient client = Test.class.getAnnotation(EnableFeignClient.class);

        // 构建 环境上下文 ->
        // 对应 FeignAutoConfiguration,OpenFeign 的自动装配配置
        InvocationFactory.DefaultInvocationFactory factory = new InvocationFactory.DefaultInvocationFactory();
        Feign feign = new Feign(factory);

        // 根据注解加载出 需要的 class  -> 
        // 对应 ClassPathScanningCandidateComponentProvider.scanCandidateComponents()
        List<Class<?>> validClass = ClassUtil.getPackageAllClassName(client.value(), FeignClient.class);

        // 生成代理对象
        for (Class<?> c : validClass) {
        	// 在 SpringBoot 中,生成实例对象后,会放入单例池中,供后续注入使用
            Object o = feign.newInstance(c);
            if (o instanceof TestClient) {
                TestClient test = (TestClient) o;
                System.out.println(test.queryOrder("2651086162633039725"));
            }
        }

    }

}

           

执行结果如下:

开始扫描包: com.fang.test
{"code":0,"data":{"actualPay":10...}}
           

顺道补一下 SpringBoot 加载 OpenFeign 的流程

1、启动类使用

EnableFeignClient

注解,告诉

SpringBoot

程序开启了这个模块

2、在

EnableFeignClient

注解中,

Import

FeignClientsRegistrar

类,该类由于实现了 SpringBoot 规范接口

ImportBeanDefinitionRegistrar

,从而在 SpringBoot 有名的

refresh()

方法中(

invokeBeanFactoryPostProcessors

)注册相关的Bean定义

3、在

refresh()

中的

finishBeanFactoryInitialization

阶段生成动态代理对象,最终调用到

ReflectiveFeign.newInstance()

生成代理对象,并放入

DefaultSingletonBeanRegistry.singletonObjects

中(IOC的单例池)

4、SpringBoot 构建对象的依赖关系时,会将代理对象进行注入;代理对象拦截该接口行为,翻译成远程调用,然后生成该接口的返回值类型即可。

补全工具类

往上抄来的,来源: https://blog.csdn.net/jdzms23/article/details/17550119

public class ClassUtil {

    public static List<Class<?>> getPackageAllClassName(String[] packages, Class annotation) {
        List<Class<?>> result = new ArrayList<>();
        for (String p : packages) {
            System.err.println("开始扫描包: " + p);
            List<Class<?>> aClass = getClass(p);
            if (aClass.isEmpty()) {
                continue;
            }
            for (Class<?> cc : aClass) {
                // 过滤我需要的东西
                Annotation client = cc.getAnnotation(annotation);
                if (client != null) {
                    result.add(cc);
                }
            }
        }
        return result;
    }

    public static List<Class<?>> getClass(String packageName) {
        List<Class<?>> result = new ArrayList<>();
        String dir = packageName.replace(".", "/");

        try {
            Enumeration<URL> files = Thread.currentThread().getContextClassLoader().getResources(dir);
            while (files.hasMoreElements()) {
                URL url = files.nextElement();
                String protocol = url.getProtocol();

                if ("file".equals(protocol)) {
                    String filePath = URLDecoder.decode(url.getFile(), "utf-8");
                    result.addAll(findClassInPackageByFile(packageName, filePath, true));
                } else if ("jar".equals(protocol)) {
                    JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
                    Enumeration<JarEntry> entries = jar.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        String name = entry.getName();

                        if (name.charAt(0) == '/') {
                            name = name.substring(1);
                        }

                        if (name.startsWith(dir)) {
                            int idx = name.lastIndexOf('/');
                            if (idx != -1) {
                                packageName = name.substring(0, idx).replace('/', '.');
                            }
                            if (idx != -1) {
                                if (name.endsWith(".class") && !entry.isDirectory()) {
                                    String className = name.substring(packageName.length() + 1, name.length() - 6);
                                    result.add(Class.forName(packageName + '.' + className));
                                }
                            }
                        }
                    }

                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return result;
    }

    private static List<Class<?>> findClassInPackageByFile(String packageName, String packagePath, final boolean recursive) {
        List<Class<?>> result = new ArrayList<>();
        File dir = new File(packagePath);
        if (!dir.exists() || !dir.isDirectory()) {
            return result;
        }

        File[] files = dir.listFiles(file -> (recursive && file.isDirectory()) || file.getName().endsWith(".class") || file.getName().endsWith(".java"));
        if (files == null || files.length <= 0) {
            return result;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                List<Class<?>> childResult = findClassInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive);
                result.addAll(childResult);
            } else {
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    result.add(Class.forName(packageName + "." + className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }


}
           

继续阅读