This is the sixth day of my participation in the First Challenge 2022. For details: First Challenge 2022.

The introduction

Hello, this is Anyin.

In the last article on OpenFeign, we talked about the use of OpenFeign and the solutions for some specific scenarios.

Today we are going to talk about how OpenFeign is implemented at the bottom, and why an interface with an annotation can implement remote interface calls.

Basics: Java dynamic proxy

In our daily development, the most common code we see is an interface, an implementation class, which performs specific logic. As follows:

    public interface FeignApi {
        ApiResponse infoByMobile(String mobile);
    }
    public class FeignApiImpl implements  FeignApi{   
        public ApiResponse infoByMobile(String mobile){
            log.info("this is execute impl method");
            return ApiResponse.success("impl method"); }}Copy the code

However, OpenFeign uses only one interface and one annotation, and does not implement a class to implement remote calls. Magic?

In fact, in our Java foundation, Java dynamic proxy can achieve this function. So here, let’s do it manually ourselves.

First, we create an interface:

    public interface FeignApi {
        ApiResponse infoByMobile(String mobile);
    }
Copy the code

Next, we create an implementation class, FeignApiProxy, that implements the InvocationHandler interface. This implementation class is the one that replaces the FeignApi interface.

    @Slf4j
    public static class FeignApiProxy implements InvocationHandler{
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // If it is an implemented concrete class, call it directly
            if(Object.class.equals(method.getDeclaringClass())){
                return method.invoke(this, args);
            }else{ // If it is an interface
                log.info("this is execute proxy method");
                return ApiResponse.success("proxy method"); }}}Copy the code

We then create a class ProxyFactory that creates dynamic proxy objects based on the type passed in.

    public class ProxyFactory {
        public <T> T getInstance(Class<T> clazz){
            // A custom implementation of the proxy class
            FeignApiProxy proxy = new FeignApiProxy();
            // Return the proxy object
            return (T) Proxy.newProxyInstance(clazz.getClassLoader(), newClass[] { clazz }, proxy); }}Copy the code

Finally, let’s test:

    @Test
    public void test(a){
        // Create a proxy factory
        ProxyFactory factory = new ProxyFactory();
        // Create a proxy object
        FeignApi api = factory.getInstance(FeignApi.class);
        // Execute method
        ApiResponse response = api.infoByMobile("anyin");
        log.info("response: {}", JSONUtil.toJsonStr(response));
    }
Copy the code

The results show:

With the above approach, we have implemented a simple proxy function, only interface, no implementation class, through the proxy object to perform concrete logic.

One might wonder if, although we don’t have an implementation class for the FeignApi interface, we do have an additional implementation class for the InvocationHandler interface. The number of classes is not reduced, nor does it seem to save much effort. The FeignApi implementation class is a concrete type class, whereas the InvocationHandler implementation class is abstract, and we can handle all kinds of logic in the implementation method.

For example, the specific application scenario is the Dao layer interface of Mybatis and The FeignClien interface of OpenFeign.

Spring Cloud NamedContextFactory

After learning about Java dynamic proxies, let’s take a look at Spring Cloud’s parent-child container mechanism.

In Spring Cloud, it has different configurations to implement different microservices. For example, different FeignClients will use different ApplicationContext to obtain different configurations from their own context for instantiation. In what circumstances do we need this mechanic? For example, if the authentication service is frequently accessed, the client timeout period of the authentication service should be set to a small value. However, the timeout time of report service should be set relatively large because it involves a large amount of data query and statistics.

The NamedContextFactory in Spring Cloud is designed to implement this mechanism. We can implement this mechanism manually ourselves.

First, create the AnyinContext class and the AnyinSpecification class. AnyinContext is our context class, or container class, which is a child container. The AnyinSpecification class is the corresponding configuration class save class, which is obtained based on the context name (name field) that is not passed.

public static class AnyinSpecification implements NamedContextFactory.Specification {
        private String name;
        privateClass<? >[] configurations;public AnyinSpecification(String name, Class
       [] configurations) {
            this.name = name;
            this.configurations = configurations;
        }
        @Override
        public String getName(a) {
            return name;
        }
        @Override
        publicClass<? >[] getConfiguration() {returnconfigurations; }}public static class AnyinContext extends NamedContextFactory<AnyinSpecification>{
        private static final String PROPERTY_SOURCE_NAME = "anyin";
        private static final String PROPERTY_NAME = PROPERTY_SOURCE_NAME + ".context.name";
        public AnyinContext(Class
        defaultConfigType) {
            super(defaultConfigType, PROPERTY_SOURCE_NAME, PROPERTY_NAME); }}Copy the code

Next, we create three bean classes that will be placed in the parent container configuration, child container common configuration, and child container configuration classes. As follows:

    public static class Parent {}
    public static class AnyinCommon{}
    @Getter
    public static class Anyin {
        private String context;
        public Anyin(String context) {
            this.context = context; }}Copy the code

Then, create three more configuration classes as follows:

    // Parent container configuration class
    @Configuration(proxyBeanMethods = false)
    public static class ParentConfig {
        @Bean
        public Parent parent(a){
            return new Parent();
        }
        @Bean
        public Anyin anyin(a){
            return new Anyin("anyin parent============="); }}// The child container common configuration class
    @Configuration(proxyBeanMethods = false)
    public static class AnyinCommonCnofig{
        @Bean
        public AnyinCommon anyinCommon(a){
            return newAnyinCommon(); }}// Subcontainer 1 config class
    @Configuration(proxyBeanMethods = false)
    public static class Anyin1Config{
        @Bean
        public Anyin anyin(a){
            return new Anyin("anyin1============="); }}// Subcontainer 2 configuration class
    @Configuration(proxyBeanMethods = false)
    public static class Anyin2Config{
        @Bean
        public Anyin anyin(a){
            return new Anyin("anyin2============="); }}Copy the code

Finally, let’s do a code test.

    @Test
    public void test(a){
        // Create parent container
        AnnotationConfigApplicationContext parent = new AnnotationConfigApplicationContext();
        // Register the parent container configuration class
        parent.register(ParentConfig.class);
        parent.refresh();
        // Create a child container and inject the default function configuration classes
        AnyinContext context = new AnyinContext(AnyinCommonCnofig.class);
        // Subcontainer 1 config class
        AnyinSpecification spec1 = new AnyinSpecification("anyin1".new Class[]{Anyin1Config.class});
        // Subcontainer 2 configuration class
        AnyinSpecification spec2 = new AnyinSpecification("anyin2".new Class[]{Anyin2Config.class});
        // The child container is bound to the parent container
        context.setApplicationContext(parent);
        // The subcontainer injects the subcontainer 1/2 configuration class
        context.setConfigurations(Lists.newArrayList(spec1, spec2));
        // Get the Parent instance of child container 1
        Parent parentBean1 = context.getInstance("anyin1", Parent.class);
        // Get the Parent instance of child container 2
        Parent parentBean2 = context.getInstance("anyin2", Parent.class);
        // Get the Parent instance of the Parent container
        Parent parentBean3 = parent.getBean(Parent.class);
        // true
        log.info("parentBean1 == parentBean2: {}", parentBean1.equals(parentBean2));
        // true
        log.info("parentBean1 == parentBean3: {}", parentBean1.equals(parentBean3));
        // true
        log.info("parentBean2 == parentBean3: {}", parentBean2.equals(parentBean3));
        // Get the AnyinCommon instance of child container 1
        AnyinCommon anyinCommon1 = context.getInstance("anyin1", AnyinCommon.class);
        // Get the AnyinCommon instance of child container 2
        AnyinCommon anyinCommon2 = context.getInstance("anyin1", AnyinCommon.class);
        // true
        log.info("anyinCommon1 == anyinCommon2: {}", anyinCommon1.equals(anyinCommon2));
        // Failed to find the corresponding bean
        // AnyinCommon anyinCommon3 = parent.getBean(AnyinCommon.class);
        // Get the Anyin object of child container 1
        Anyin anyin1 = context.getInstance("anyin1", Anyin.class);
        // anyin1 context: anyin1=============
        log.info("anyin1 context: {}", anyin1.getContext());
        // anyin2 context: anyin2=============
        Anyin anyin2 = context.getInstance("anyin2", Anyin.class);
        log.info("anyin2 context: {}", anyin2.getContext());
        // false
        log.info("anyin1 == anyin2: {}", anyin1.equals(anyin2));
        // anyinParent: anyin parent=============
        Anyin anyinParent = parent.getBean(Anyin.class);
        log.info("anyinParent: {}", anyinParent.getContext());
    }
Copy the code

The above code may be quite long, please refer to it in detail. Through the above tests, we can summarize the following points:

  • Child containers can get instances of parent containers
  • The parent container cannot get instances of the children
  • Instances are first fetched from common configuration classes (this is required in theAnyinCommonCnofigAdding configuration classesAnyinInstance configuration)

The source code parsing

Knowing the above two basic mechanisms, this time we look at the source, it may be easier to understand.

The entry to the OpenFeign component starts with the @enableFeignClients annotation. It imports the FeignClientsRegistrar class where the FeignClient is initialized and registered.

Inside FeignClientsRegistrar, it registers the default configuration class and then the specific FeignClient. This default configuration class needs to be configured in the @enableFeignClients annotation, which we don’t normally configure, so we’ll leave it out for now.

Next, let’s look at how registering a specific FeignClient is implemented.

Let’s look at the registerFeignClient method, which creates instances through FeignClientFactoryBean. The main code is as follows:

Next, we look at the getObject() method of the FeignClientFactoryBean, which returns the proxy object.

Through feign method to obtain the feign. Builder instance, it is configured in the FeignClientsConfiguration class.

In loadBalance method, obtain the Client instance, the instance is used to do load balancing, Client is DefaultTargeter Targeter instance, it is also in FeignClientsConfiguration assembly.

The code then returns to the Target method of the Feign class.

Let’s look directly at the newInstance method of the ReflectiveFeign class.

At this point, if you see a familiar scenario, this is where Java dynamic proxies are handled. Target.type () returns our defined FeignClient interface. The implementation class of InvocationHandler is ReflectiveFeign#FeignInvocationHandler, whose invoke method is the implementation of the method proxy that we used to call the FeignClient interface.

Based on the method instance, get the corresponding MethodHandler and call it. Dispatch is a map data structure, as follows:

The implementation class of the returned MethodHandler interface is the SynchronousMethodHandler, whose Invoke method starts executing the concrete HTTP call logic.

The last

The above is the OpenFeign source code process according to my personal understanding. If you have any questions, please point out.

Personally, as long as you understand the underlying Java dynamic proxy and Spring Cloud NamedContextFactory mechanism, and then go back to comb the source code, the whole process will be much smoother.

Test case source code address: Anyin Cloud