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.

Dilaver DEMİREL