OpenFeing Nasıl Çalışıyor ve Spring Framework ile Nasıl Entegre Oluyor?
Bildiğimiz üzere Feign projesinin amacı java http clientlarını kolayca oluşturmayı sağlamaktır. Ve bu amaçla Interface’ler üzerinden tanımlanan clientları kolayca kullanmayı sağlıyor.
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 + ")");
}
}
}
Peki Feign bu kolay kullanım fikrini nasıl implemente ediyor. Yapılan işlemlere baktığımızda; temelde Http request’i yapmak ve dönen response’u vermektir. Bu aşamada marshalling/request/response/unmarshalling işlemleri yapılarak http üzerinden gönderilen ve alınan veri java objelerine dönüştürülüyor.
Baktığımızda yapılan işlemler belirli bir pattern’i uyguluyor.
Benim buradaki amacım Feign kütüphanesi temelde javadaki hangi özelliği kullanarak bu standart işlemleri yaptığını ve belirli bir patterne uyan işlemlerde bu yöntemin kullanılabilirliğini göstermek istiyorum. Ve sonunda bu yöntemi spring ile nasıl kullanabileceğimizi göstereceğim.
Aslında mantık olarak karmaşık değil. Mantık Java reflection kütüphanesindeki Proxy class’ı kullanılarak tanımlanmış Interface’lere runtime’da InvocationHandler’lar ile bind edilerek dinamik davranış vermeye dayanmaktadır.
Öncelikle bir Interface’e ihtiyacımız var;
Parametrik yapıyı gösterebilmek için basit bir annotation tanımladım;
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);
}
İkinci olarak bir InvocationHandler’a ihtiyacımız var;
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] + "!";
}
}
CustomInvocationHandler classına Method ve Method argument’leri parametre olarak iletilmektedir. Method referansı üzerinden annotation bilgilerine erişilerek invocation sırasından istenilen parametrik yapı oluşturulabiliyor.
Son olarak Proxy oluşturup InvocationHandler ile bind ederek DummyInterface içerisindeki hello metoduna davranış vermeye kalıyor.
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"));
}
}
Yukarıda yaptığımız sadece Proxy.newProxyInstance metodunu kullanarak Interface’lere hangi InvocationHandler ile çalışacaklarını belirtiyoruz ve dönen instance’ın hello metodunu çağırıyoruz.
Çıktı aşağıdaki gibi oluşuyor;
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());
}
}
Benzer mantıkla spring beanları tanımlayarak Proxy Interface’leri erişilebilir hale getirebiliriz.
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();
}
}
Çıktı;
args = Dilaver
Invoked method:hello
Merhaba Bay Dilaver!
args = Derya
Invoked method:hello
Merhaba Bayan Derya!
Sonuç olarak;
Belirli bir patterni uygulayan problemlere esnek bir çözüm oluşturmanın bir mantığını paylaşmaya çalıştım. Proxy kullanılarak dynamic interface’ler ile farklı çözümler uygulanabilir.
Aşağıdaki repodan detaylı olarak inceleyebilirsiniz.