Implementación de un contenedor IoC personalizado con anotaciones

Introducción

En el capítulo anterior se explicó cómo gestionar objetos bean mediante configuración XML. Ahora abordaremos la gestión del contenedor Spring y los objetos bean utilizando anotaciones personalizadas.

Definición de anotaciones personalizadas

Anotación @Inyeccion

package org.miproyecto.anotacion;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inyeccion {
    boolean esRequerido() default true;
}

Anotación @ComponenteDAO

package org.miproyecto.anotacion;

import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ComponenteDAO {
    @AliasFor(annotation = Component.class)
    String valor() default "";
}

Anotación @ComponenteServicio

package org.miproyecto.anotacion;

import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ComponenteServicio {
    @AliasFor(annotation = Component.class)
    String valor() default "";
}

Implementación de la capa de negocio

Interfza de servicio

package org.miproyecto.servicio;

public interface ServicioUsuario {
    void crearUsuario();
}

Implementación del servicio

package org.miproyecto.servicio.impl;

import org.miproyecto.anotacion.Inyeccion;
import org.miproyecto.anotacion.ComponenteServicio;
import org.miproyecto.dao.DaoUsuario;
import org.miproyecto.servicio.ServicioUsuario;

@ComponenteServicio
public class ImplementacionServicioUsuario implements ServicioUsuario {
    @Inyeccion
    private DaoUsuario daoUsuario;
    
    @Override
    public void crearUsuario() {
        System.out.println("ImplementacionServicioUsuario - ejecutando");
        daoUsuario.crearUsuario();
    }
}

Interfaz DAO

package org.miproyecto.dao;

public interface DaoUsuario {
    void crearUsuario();
}

Implementación DAO

package org.miproyecto.dao.impl;

import org.miproyecto.anotacion.ComponenteDAO;
import org.miproyecto.dao.DaoUsuario;

@ComponenteDAO
public class ImplementacionDaoUsuario implements DaoUsuario {
    @Override
    public void crearUsuario() {
        System.out.println("ImplementacionDaoUsuario - ejecutando");
    }
}

Clase principal del contenedor

Esta clase es responsable de escanear el paquete base, detectar clases anotadas y registraras como beans, además de realizar la inyección de dependencias.

package org.miproyecto.contexto;

import org.miproyecto.anotacion.Inyeccion;
import org.miproyecto.anotacion.ComponenteDAO;
import org.miproyecto.anotacion.ComponenteServicio;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContextoAnotaciones {
    private String paqueteBase;
    private Map<Class, Object> fabricaBeans = new ConcurrentHashMap<>();
    private String rutaRaiz = null;

    public ContextoAnotaciones(String paqueteBase) throws Exception {
        this.paqueteBase = paqueteBase;
        escanearArchivos();
        inyectarDependencias();
    }

    private void escanearArchivos() throws Exception {
        String rutaPaquete = this.paqueteBase.replaceAll("\\.", "\\\\");
        Enumeration<URL> recursos = Thread.currentThread()
            .getContextClassLoader()
            .getResources(rutaPaquete);

        while (recursos.hasMoreElements()) {
            URL url = recursos.nextElement();
            File directorio = new File(URLDecoder.decode(url.getFile(), "UTF-8"));
            if (rutaRaiz == null) {
                rutaRaiz = directorio.getAbsolutePath()
                    .substring(0, directorio.getAbsolutePath().indexOf(rutaPaquete));
            }
            cargarBeans(directorio);
        }
    }

    private void cargarBeans(File directorio) throws Exception {
        if (directorio.isDirectory()) {
            File[] archivos = directorio.listFiles();
            if (archivos == null || archivos.length == 0) {
                return;
            }
            for (File archivo : archivos) {
                if (archivo.isDirectory()) {
                    cargarBeans(archivo);
                } else if (archivo.isFile()) {
                    procesarArchivo(archivo);
                }
            }
        }
    }

    private void procesarArchivo(File archivo) throws IOException, 
            ClassNotFoundException, NoSuchMethodException, 
            InvocationTargetException, InstantiationException, 
            IllegalAccessException {
        
        String nombreCompleto = archivo.getPath().substring(rutaRaiz.length());
        
        if (nombreCompleto.contains(".class")) {
            nombreCompleto = nombreCompleto.replaceAll("\\\\", "\\.")
                .substring(0, nombreCompleto.length() - 6);

            Class<?> clase = Class.forName(nombreCompleto);
            
            if (!clazz.isInterface()) {
                ComponenteServicio anotacionServicio = clase.getAnnotation(ComponenteServicio.class);
                ComponenteDAO anotacionDAO = clase.getAnnotation(ComponenteDAO.class);

                if (anotacionServicio != null || anotacionDAO != null) {
                    Object instancia = clase.getDeclaredConstructor().newInstance();
                    
                    if (clase.getInterfaces().length > 0) {
                        Class interfaz = clase.getInterfaces()[0];
                        fabricaBeans.put(interfaz, instancia);
                    } else {
                        fabricaBeans.put(clase, instancia);
                    }
                }
            }
        }
    }

    private void inyectarDependencias() throws IllegalAccessException {
        Iterator<Map.Entry<Class, Object>> iterator = fabricaBeans.entrySet().iterator();
        
        while (iterator.hasNext()) {
            Map.Entry<Class, Object> entrada = iterator.next();
            Object instancia = entrada.getValue();
            Class clase = instancia.getClass();
            
            Field[] campos = clase.getDeclaredFields();
            for (Field campo : campos) {
                Inyeccion inyeccion = campo.getAnnotation(Inyeccion.class);
                if (inyeccion != null) {
                    campo.setAccessible(true);
                    Object dependencia = fabricaBeans.get(campo.getType());
                    campo.set(instancia, dependencia);
                }
            }
        }
    }

    public Object obtenerBean(Class clase) {
        return fabricaBeans.get(clase);
    }
}

Prrueba del sistema

package org.miproyecto;

import org.miproyecto.contexto.ContextoAnotaciones;
import org.miproyecto.servicio.ServicioUsuario;

public class AplicacionPrincipal {
    public static void main(String[] args) {
        System.out.println("Iniciando aplicación...");
        ejecutarPruebaAnotaciones();
    }

    public static void ejecutarPruebaAnotaciones() {
        try {
            ContextoAnotaciones contexto = new ContextoAnotaciones("org.miproyecto");
            ServicioUsuario servicio = (ServicioUsuario) contexto.obtenerBean(ServicioUsuario.class);
            servicio.crearUsuario();
        } catch (Exception excepcion) {
            throw new RuntimeException(excepcion);
        }
    }
}

Resultado de la ejecución

Iniciando aplicación...
ImplementacionServicioUsuario - ejecutando
ImplementacionDaoUsuario - ejecutando

Etiquetas: Spring IOC java anotaciones contenedor

Publicado el 6-16 22:37