How Does OpenFeing Work and How Does It Integrate with the Spring Framework?

  • Home
  • Blog
  • How Does OpenFeing Work and How Does It Integrate with the Spring Framework?
How Does OpenFeing Work and How Does It Integrate with the Spring Framework?

How Does OpenFeing Work and How Does It Integrate with the Spring Framework?

As we know, the goal of the Feign project is to make it easy to create Java HTTP clients. To that end, it allows you to easily use clients defined through interfaces.

https://github.com/OpenFeign/feign

interface GitHub {

  @RequestLine("GET /repos/{owner}/{repo}/contributors")

  List contributors(@Param("owner") String owner, @Param("repo") String repo);

 

  @RequestLine("POST /repos/{owner}/{repo}/issues")

  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

public class MyApp {

  public static void main(String... args) {

    GitHub github = Feign.builder()

                         .decoder(new GsonDecoder())

                         .target(GitHub.class, "https://api.github.com");

 

    // Fetch and print a list of the contributors to this library.

    List contributors = github.contributors("OpenFeign", "feign");

    for (Contributor contributor : contributors) {

      System.out.println(contributor.login + " (" + contributor.contributions + ")");

    }

  }

}

So how does Feign implement this concept of ease of use? If we look at the operations involved, they essentially consist of making an HTTP request and returning the response. During this process, data sent and received over HTTP is converted into Java objects through marshalling, request, and unmarshalling operations.

As we can see, these operations follow a specific pattern.

My goal here is to demonstrate which Java feature the Feign library fundamentally uses to perform these standard operations and to show the applicability of this method in processes that follow a specific pattern. Finally, I will demonstrate how to use this method with Spring.

Logically, it’s not complicated. The logic relies on using the Proxy class from the Java reflection library to dynamically bind InvocationHandlers to interfaces at runtime, thereby enabling dynamic behavior.

First, we need an interface;
To demonstrate the parametric structure, I’ve defined a simple annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

 

/**

 * @author dilaverd

 * @since 03.03.2021

 */

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

public @interface Gender {

    String name();

}

 

Interface’ler;

/**

 * @author dilaverdemirel

 * @since 28/07/2022

 */

public interface MaleGenderInterface {

    @Gender(name = "Bay")

    String hello(String name);

}

/**

 * @author dilaverdemirel

 * @since 28/07/2022

 */

public interface FemaleGenderInterface {

    @Gender(name = "Bayan")

    String hello(String name);

}

 

Second, we need an InvocationHandler;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

 

/**

 * @author dilaverdemirel

 * @since 28/07/2022

 */

public class CustomInvocationHandler implements InvocationHandler {

 

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) {

        if (args != null) {

            for (Object arg : args)

                System.out.println("args = " + arg);

        }

 

        final var methodAnnotation = method.getAnnotation(Gender.class);

 

        System.out.println("Invoked method:" + method.getName());

        return "Merhaba " + methodAnnotation.name() + " " + args[0] + "!";

    }

}

The method and its arguments are passed as parameters to the `CustomInvocationHandler` class. By accessing the annotation information via the method reference, the desired parameter structure can be created during invocation.

Finally, we create a proxy, bind it to the `InvocationHandler`, and implement the behavior for the `hello` method within the `DummyInterface`.

import java.lang.reflect.Proxy;

 

/**

 * @author dilaverdemirel

 * @since 28/07/2022

 */

public class Tester {

    public static void main(String[] args) {

        MaleGenderInterface maleGenderInterface = (MaleGenderInterface) Proxy.newProxyInstance(

                MaleGenderInterface.class.getClassLoader(),

                new Class[]{MaleGenderInterface.class},

                new CustomInvocationHandler());

        System.out.println("result = " + maleGenderInterface.hello("Dilaver"));

 

        FemaleGenderInterface femaleGenderInterface = (FemaleGenderInterface) Proxy.newProxyInstance(

                FemaleGenderInterface.class.getClassLoader(),

                new Class[]{FemaleGenderInterface.class},

                new CustomInvocationHandler());

        System.out.println("result = " + femaleGenderInterface.hello("Derya"));

    }

}

In the code above, we simply use the `Proxy.newProxyInstance` method to specify which `InvocationHandler` the interfaces should use, and then call the `hello` method of the returned instance.

The output looks like this:

args = Dilaver

Invoked method:hello

result = Merhaba Bay Dilaver!

args = Derya

Invoked method:hello

result = Merhaba Bayan Derya!

 

Spring ile entegrasyon;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.ApplicationContext;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

 

import java.lang.reflect.Proxy;

 

/**

 * @author dilaverdemirel

 * @since 28/07/2022

 */

@Configuration

public class Config {

 

    @Autowired

    private ApplicationContext applicationContext;

 

    @Bean

    public MaleGenderInterface maleGenderInterface() {

        return (MaleGenderInterface) Proxy.newProxyInstance(

                MaleGenderInterface.class.getClassLoader(),

                new Class[]{MaleGenderInterface.class},

                new CustomInvocationHandler());

    }

 

    @Bean

    public FemaleGenderInterface femaleGenderInterface() {

        return (FemaleGenderInterface) Proxy.newProxyInstance(

                FemaleGenderInterface.class.getClassLoader(),

                new Class[]{FemaleGenderInterface.class},

                new CustomInvocationHandler());

    }

}

Using the same approach, we can define Spring beans to make proxy interfaces accessible.

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.CommandLineRunner;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import tr.com.dilaverdemirel.handler.DemoBean;

 

/**

 * @author dilaverdemirel

 * @since 28/07/2022

 */

 

@SpringBootApplication

public class DemoApplication implements CommandLineRunner {

 

    @Autowired

    private DemoBean demoBean;

 

    public static void main(String[] args) {

        SpringApplication.run(DemoApplication.class, args);

    }

 

    @Override

    public void run(String... args) {

        demoBean.test();

    }

 

}

Respons;

args = Dilaver

Invoked method:hello

Merhaba Bay Dilaver!

args = Derya

Invoked method:hello

Merhaba Bayan Derya!

In conclusion;

I’ve tried to share a rationale for creating a flexible solution to problems that follow a specific pattern. By using proxies and dynamic interfaces, different solutions can be implemented.

You can explore this in detail in the repository below.

https://github.com/dilaverdemirel/dynamic-method-handler