天天看點

自己動手實作一個簡單的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;
    }


}
           

繼續閱讀