Te habras dado cuenta que solo necesitas crear una interfaz y decorarla con la anotacion @FeingClient, con esto Feign hace la implementación de manera magica sin necesidad de nosotros escribir código o implementar dicha esa interfaz.
¿Como puedo hacer algo similar?
En este post explicare como crear una anotación personalizada con la que poder dar funcionalidad a interfaces sin necesidad de implementar dicha interfaz, esto lo shraremos con Proxies utilizando cglib (que ya viene incluida en springboot)
¿Que es un Proxy?
Proxy es un patrón de diseño estructural que proporciona un objeto que actúa como sustituto de un objeto de servicio real utilizado por un cliente. Un proxy recibe solicitudes del cliente, realiza parte del trabajo (control de acceso, almacenamiento en caché, etc.) y después pasa la solicitud a un objeto de servicio
Cglib es una libreria que permite generar y transformar bytecode de Java en tiempo de ejecución, gracias a esta libreria podemos crear la implementación de las interfaces en tiempo de ejecución y agregar nuestra funcionalidad requerida.
Guia
Bueno, luego de explicar a grandes razgos eso vamos a explicar como podemos realizar nuestra propia anotacion y implementación.
Creando un proyecto nuevo
Cree un proyecto nuevo solo agregando la dependencia base de springboot
Creando la anotación para decorar las interfaces
Ahora procedemos a crear nuestra anotación que marcara las interfaces que debemos capturar y agregar el proxy.
la anotación será @ResourceProvider se usará para marcar las interfaces que deben ser capturadas
@Target(ElementType.TYPE): esto define el tipo de target al que apunta la anotación, aquí definimos si queremos que se use a nivel de clase (type), metodo, campo, etc.
@Retention(RetentionPolicy.RUNTIME): esto define la politica de retencion de la anotación, hay 3 valores: RetentionPolicy.SOURCE, RetentionPolicy.CLASS, RetentionPolicy.RUNTIME, nosotros usaremos RUNTIME ya que será usada en tiempo de ejecución
Creando una interfaz con la anotación que creamos
vamos a crearemos una interfaz que este decorada con la anotación @ResourceProvider
Como se puede ver es una interfaz simple con un método que retorna un string y se decora con nuestra anotación.
Realizando prueba
Intentemos inyectar esta interfaz en el main con un @Autowired:
en este caso implemente la clase CommandLineRunner para poder ejecutar de manera directa el metodo getResource() de mi interfaz.
si lo ejecutamos obtendremos un error de spring ya que no puede encontrar un bean para la interfaz IMyResourceProvider:
Agregando el bean IMyResourceProvider al contenedor de spring
creamos la clase que se encarga de agregar el bean de la interfaz al contenedor de spring y inyectarle funcionalidad con cglib
Creamos la clase que implementa BeanDefinitionRegistryPostProcessor, aqui sobreescribir el método postProcessBeanDefinitionRegistry: es el metodo que permite modificar el registro de definición de bean interno del contexto de la aplicación después de su inicialización estándar. Se habrán cargado todas las definiciones de beans normales, pero todavía no se habrá creado una instancia de ningún bean. Esto permite agregar más definiciones de beans antes de que comience la siguiente fase de posprocesamiento.
también tenemos el método createProxy que es donde creamos el proxy para nuestra interfaz y le damos funcionalidad, en este caso retorna un String con el nombre de la clase y el nombre del metodo.
Realizando prueba
ahora si lo ejecutamos vemos que tenemos el valor que le declaramos en nuestro proxy
ya estamos generando un bean para nuestra interfaz y agregando funcionalidad mediante un proxy con Cglib.
Creando mas interfaces
¿Pero que pasa si creamos otra interfaz?
la agregamos a nuestra clase main y ejecutamos:
vemos que falla, porque ahora no encuentra nuesto bean para IMyResource2Provider, ya que de la forma que lo definimos no queda dinamico detectando las interfaces decoradas con @ResourceProvider y autogenerando los beans con su implementación.
Agregando beans de manera dinamica
para solucionar esto de forma dinamica y automatica, debemos modificar la clase ResourceProviderBeanDefinitionRegistryPostProcessor para que nos permita escanear el proyecto y buscar las interfaces decoradas con la anotacion @ResourceProvider:
Agregamos dos metodos
getBeanDefinition: es el encargado de escanear el classpath y buscar las interfaces decoradas con @ResourceProvider
registerBeanForInterface: se encarga de crear el registrar la definicion del bean creando el proxy para la interfaz que viene en la variable targetClass
ahora si ejecutamos obtendremos lo siguiente:
como se puede apreciar se shran inyectar ambas interfaces y cada una devuelve el String que definimos en el metodo createProxy()
ahora podemos crear mas interfaces y todas se les agregara el proxy, pero que pasa si queremos generar mas de un metodo en las interfaces?, y tambien queremos agregarle valores dinamicos a cada metodo?
Creando anotación @ResourceValue
Ahora creamos una anotación que marcara los metodos y poder agregar funcionalidad de manera dinamica a cada uno.
en este caso el target sera: @Target(ElementType.METHOD) ya que queremos aplicar este decorador solo a los metodos, no a las clases ni interfaces.
la anotación tiene dos campos obligatorios: key y source que son necesarios para la implementación
Modificando interfaces
Agregamos a las interfaces el decorador @ResourceValue
en este caso tendre 3 fuentes: redis, datagrid y environment
Modificando metodo createProxy
ahora debemos modificar el metodo createProxy de la clase postProcessBeanDefinitionRegistry para mostrar el los valores de la anotacion @ResourceValue:
mediante el api de Reflection de java podemos obtener la anotación del método ejecutado mediante el proxy y asi obtener sus valores
si ejecutamos obtendremos lo siguiente:
ahora el inconveniente es que no podemos agregar otras dependencias ya que en este punto aun no se inicializado los beans de springboot, para eso vamos a crear otras clases:
esta clase es la encargada de interceptar las ejecuciones de nuestro proxy esto lo hace implementando la clase MethodInterceptor y sobreescribiendo el metodo intercept, aqui ya podemos inyectar otras dependencias
ahora toca crear InterfaceProxyFactoryBean.java que es la encargara de la creación del proxy
ahora modificamos la clase que implementa BeanDefinitionRegistryPostProcessor.java para indicar cual clase es nuestra factory:
ahora si ejecutamos nuestro programa:
y listo esta funcionando, ahora solo queda aplicar nuestra shica en ResourceProviderHandler para nuestras interfaces.
Resumen
Creamos la anotación @ResourceProvider para marcar las interfaces que queremos que se cree un proxy y se inyecte el bean al contexto de spring, de manera automatica,
Creamos la anotación @ResourceValue para proveer de valores a los metodos dentro de la interfaz de manera de crear una implementaciones dinamicas.
Creamos una clase de tipo BeanFactory que es la fabrica de nuestras interfaces y la encargada de crear el proxy
Si quieres hacer esto mas dinamico aún, como crear mas de una anotación y distintos handlers para cada una, puedes ver un ejemplo en mi Github: fneiraj/JavaSpringExamples 🔗