2010-06-14 20 views
14

estoy tratando de usar un muelle para inyectar un registrador SLF4J en una clase de este modo:Inyección de constructor de primavera del registrador SLF4J: ¿cómo obtener la clase de destino de inyección?

@Component 
public class Example { 

    private final Logger logger; 

    @Autowired 
    public Example(final Logger logger) { 
    this.logger = logger; 
    } 
} 

que he encontrado la clase FactoryBean, que he implementado. Pero el problema es que no puedo obtener ninguna información sobre el objetivo de la inyección:

public class LoggingFactoryBean implements FactoryBean<Logger> { 

    @Override 
    public Class<?> getObjectType() { 
     return Logger.class; 
    } 

    @Override 
    public boolean isSingleton() { 
     return false; 
    } 

    @Override 
    public Logger getObject() throws Exception { 
     return LoggerFactory.getLogger(/* how do I get a hold of the target class (Example.class) here? */); 
    } 
} 

Es FactoryBean incluso el camino correcto a seguir? Al utilizar picocontainers factory injection, se obtiene la Type del objetivo en el pasado. En guice que es un poco trickier. ¿Pero cómo logras esto en primavera?

+4

¿Realmente vale la pena, solo para evitar decir '= LoggerFactory.getLogger()'? – skaffman

+0

No me gusta el enlace estático a LoggerFactory, por las mismas razones que describe Martin Fowler en http://martinfowler.com/articles/injection.html. Una LoggerFactory inyectada es una solución aceptable (siguiendo el patrón de localizador de servicio), pero un poco detallada. Supongo que se podría argumentar que la inyección de Log necesita usar un localizador de servicio, ya que una dependencia pura debe ser un objetivo independiente. Pero la solución del localizador es prolija, otros marcos lo soportan y espero que Spring pueda proporcionar algún tipo de información sobre el objetivo. Me pregunto si esto realmente no es posible. –

+0

Es decir, esta información se pasa a BeanPostProcessors: http://www.tzavellas.com/techblog/2007/03/31/implementing-seam-style-logger-injection-with-spring/. ¿No se puede lograr lo mismo con la inyección del constructor? –

Respuesta

10

lo resolví con un BeanFactory personalizado. Si a alguien se le ocurre una solución mejor, me gustaría escucharla. De todos modos, aquí está la fábrica de beans:

import java.util.Set; 

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.BeansException; 
import org.springframework.beans.TypeConverter; 
import org.springframework.beans.factory.config.DependencyDescriptor; 
import org.springframework.beans.factory.support.DefaultListableBeanFactory; 

public class CustomBeanFactory extends DefaultListableBeanFactory { 

    public CustomBeanFactory() { 
    } 

    public CustomBeanFactory(DefaultListableBeanFactory delegate) { 
     super(delegate); 
    } 

    @Override 
    public Object resolveDependency(DependencyDescriptor descriptor, 
      String beanName, Set<String> autowiredBeanNames, 
      TypeConverter typeConverter) throws BeansException { 
     //Assign Logger parameters if required  
     if (descriptor.isRequired() 
       && Logger.class.isAssignableFrom(descriptor 
         .getMethodParameter().getParameterType())) {    
      return LoggerFactory.getLogger(descriptor.getMethodParameter() 
        .getDeclaringClass()); 
     } else { 
      return super.resolveDependency(descriptor, beanName, 
        autowiredBeanNames, typeConverter); 
     } 
    } 
} 

Ejemplo de uso con una configuración XML:

 CustomBeanFactory customBeanFactory = new CustomBeanFactory();  
     GenericApplicationContext ctx = new GenericApplicationContext(customBeanFactory); 
     XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx); 
     xmlReader.loadBeanDefinitions(new ClassPathResource("beans.xml")); 
     ctx.refresh(); 

EDIT:

A continuación puede encontrar Arend v Reinersdorffs versión mejorada (ver los comentarios para una. explicación).

import java.lang.reflect.Field; 
import java.util.Set; 

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
import org.springframework.beans.BeansException; 
import org.springframework.beans.TypeConverter; 
import org.springframework.beans.factory.config.DependencyDescriptor; 
import org.springframework.beans.factory.support.DefaultListableBeanFactory; 
import org.springframework.core.MethodParameter; 

public class CustomBeanFactory extends DefaultListableBeanFactory { 

    public CustomBeanFactory() { 
    } 

    public CustomBeanFactory(DefaultListableBeanFactory delegate) { 
     super(delegate); 
    } 

    @Override 
    public Object resolveDependency(DependencyDescriptor descriptor, 
      String beanName, Set<String> autowiredBeanNames, 
      TypeConverter typeConverter) throws BeansException { 
     //Assign Logger parameters if required  
     if (Logger.class == descriptor.getDependencyType()) {    
      return LoggerFactory.getLogger(getDeclaringClass(descriptor)); 
     } else { 
      return super.resolveDependency(descriptor, beanName, 
        autowiredBeanNames, typeConverter); 
     } 
    } 

    private Class<?> getDeclaringClass(DependencyDescriptor descriptor) { 
     MethodParameter methodParameter = descriptor.getMethodParameter(); 
     if (methodParameter != null) { 
      return methodParameter.getDeclaringClass(); 
     } 
     Field field = descriptor.getField(); 
     if (field != null) { 
      return field.getDeclaringClass(); 
     } 
     throw new AssertionError("Injection must be into a method parameter or field."); 
    } 
} 
+0

Gracias por compartir. –

+0

Funciona muy bien, gracias. Dos puntos: 1. Debería ser 'Logger.class == ...' en lugar de 'isAssignableFrom (...)'. 'isAssignableFrom()' es verdadero para cualquier subclase de Logger, pero Logger no se puede inyectar en un campo de su subclase. Por ejemplo '@Autowired MyLogger myLogger;' para una 'clase MyLogger implementa Logger' siempre lanzará una excepción. 2. ¿Alguna razón por la cual el registrador no se inyecta en dependencias opcionales? –

+0

1. Buen punto. Creo que tenía la intención de hacer lo contrario, es decir descriptor.getMethodParameter(). GetParameterType(). IsAssignableFrom (Logger.class), pero esto inyectaría Loggers en los campos de Object, por lo que quizás igual sea el camino a seguir. 2. No, no hay razón. El código anterior funcionó para mi caso, pero no dude en modificarlo. Si tiene una actualización, envíela y editaré la publicación. Cheers –

0
  1. ¿Por qué está creando un nuevo registrador para cada instancia? El patrón típico es tener un registrador por clase (como miembro estático privado).

  2. Si realmente quiere hacerlo de esa manera: Tal vez usted puede escribir una clase de fábrica registrador, e inyectar eso? Algo así como:

    @Singleton 
    public class LogFactory { 
        public Logger getLogger(Object o) { 
         return LoggerFactory.getLogger(o.getClass()); 
        } 
    } 
    
+0

1: Ver http://wiki.apache.org/commons/Logging/StaticLog 2: Esto es lo que ya he hecho. Funciona bien, pero es muy detallado IMO. –

0

Sí, va en la dirección equivocada. Si yo fuera tú, inyectaría LoggerFactory. Si desea ocultar que es slf4j, entonces definiría una interfaz LoggerFactory e inyectaría una clase que delegue en slf4j Logger.

public interface LoggerFactory { 
    public Logger getLogger(Class<?> clazz); 
} 
... 
import org.slf4j.LoggerFactory; 
public class Slf4jLoggerFactory implements LoggerFactory { 
    public Logger getLogger(Class<?> clazz) { 
     return org.slf4j.LoggerFactory.getLogger(clazz); 
    } 
} 

Sin embargo, antes de ir allí, esto es aproximadamente lo org.apache.commons.logging está haciendo bien? http://commons.apache.org/logging/

Usted uso Entrar del lugar de registradores:

import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 
public class CLASS { 
    private Log log = LogFactory.getLog(CLASS.class); 
    ... 

Apache luego mira a través de la ruta de clases para ver si tiene log4j u otros y los delegados a la "mejor" que se encuentra. Slf4j reemplaza log4j en la ruta de acceso de clases, por lo que si la tiene cargada (y apache log4j excluido), el registro de los recursos comunes se delegará en ella.

+0

Esta es casi la misma respuesta que Mike dio. Me gustaría pasar el registro, ya que me gusta un estilo de oo puro. El enfoque del "localizador de servicios" es factible, pero un poco intrusivo. Uno podría argumentar que la clase de tiempo de ejecución no debe ser parte de la interfaz en getLog, pero esperaría que Spring transmita algún tipo de estructura que describa el objetivo. ¿No es esto posible? –

+1

Y, por encima de todo, el truco de registro de commons del autodescubrimiento fue la razón por la que nació slf4j. –

+0

El resorte no tiene esta capacidad con FactoryBean, no. Crearía algún tipo de dependencia circular si X depende de Y pero Y sabe acerca de X cuando se construye. – Gray

20

Aquí hay una alternativa a su solución. Se podría lograr su objetivo con BeanFactoryPostProcessor aplicación.

Supongamos que desea tener una clase con el registro.Aquí está:

package log; 
    import org.apache.log4j.Logger; 

    @Loggable 
    public class MyBean { 

    private Logger logger; 
    } 

Como se podía ver esta clase no hace nada y creado sólo para ser un contenedor registrador por simplicidad. Lo único notable aquí es @Loggable anotación. Aquí su código fuente:

package log; 

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

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.TYPE) 
public @interface Loggable { 
} 

Esta anotación es solamente un marcador para su posterior procesamiento. Y aquí es una parte más interesante:

package log; 

import org.apache.log4j.Logger; 
import org.springframework.beans.BeansException; 
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 

import java.lang.reflect.Field; 

public class LoggerBeanFactoryPostProcessor implements BeanFactoryPostProcessor{ 

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 
     String[] names = beanFactory.getBeanDefinitionNames(); 
     for(String name : names){ 
      Object bean = beanFactory.getBean(name); 
      if(bean.getClass().isAnnotationPresent(Loggable.class)){ 
       try { 
        Field field = bean.getClass().getDeclaredField("logger"); 
        field.setAccessible(true); 
        field.set(bean, Logger.getLogger(bean.getClass())); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      } 
     } 
    } 
} 

Se busca a través de todos los granos, y si un grano está marcado como @Loggable, que inicializar su ámbito privado con el nombre registrador. Puede ir más allá y pasar algunos parámetros en @Loggable anotación. Por ejemplo, podría ser un nombre de campo correspondiente al registrador.

Usé Log4j en este ejemplo, pero supongo que debería funcionar exactamente de la misma manera con slf4j.

+1

+1 Ejemplo más cercano de BeanFactoryPostProcessor que he visto en cualquier parte – Anonymoose

+1

Gracias por su publicación. Su ejemplo es bastante similar al enlace al que hice referencia en uno de los comentarios anteriores, http://www.tzavellas.com/techblog/2007/03/31/implementing-seam-style-logger-injection-with-spring/. Funciona bien, pero no puede funcionar en constructores. Me gustaría utilizar la inyección de constructor, ya que esto significa que el registrador se puede usar en el constructor y que puedo declarar el campo como definitivo, un buen hábito en las aplicaciones de subprocesos múltiples. Idealmente, la inyección de constructor funcionaría de la misma manera que la inyección de campo, pero esto requeriría algún tipo de constructores de argumentos de constructor. –

+0

@disown Creo que estás tratando de resolver un problema inexistente.Es muy poco probable que tengas que evitar problemas de subprocesamiento múltiple al inicio. Se garantiza que los descendientes de ApplicationContext sean seguros para subprocesos (http://forum.springsource.org/showthread.php?t=11791). – wax

-2

Estoy tratando de obtener esta característica en la API SLF4J oficial. Por favor apoye/voto/contribute: https://issues.jboss.org/browse/JBLOGGING-62

(esta función ya está implementado por JBoss Seam + Registro de soldadura, ver http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-logging.html)

11,4. API registrador nativa

También puede inyectar un "viejo y simple" Logger (de la API de registro de JBoss):

import javax.inject.Inject; 
import org.jboss.logging.Logger; 

public class LogService { 

    @Inject 
    private Logger log; 

    public void logMessage() { 
     log.info("Hey sysadmins!"); 
    } 

} 

mensajes de registro creados a partir de este registrador tendrán una categoría (nombre de registro) igual a la plena -nombre de clase calificado de la clase de implementación de beans. Puede especificar una categoría explícitamente usando una anotación.

@Inject @Category("billing") 
private Logger log; 

También puede especificar una categoría mediante una referencia a un tipo:

@Inject @TypedCategory(BillingService.class) 
private Logger log; 

Lo siento por no dar una respuesta pertinente.

2

Pruebe algo como:

@Component 
public class Example { 

    @Autowired 
    @Qualifier("exampleLogger") 
    private final Logger logger; 

} 

Y:

<bean id="exampleLogger" class="org.slf4j.LoggerFactory" factory-method="getLogger"> 
    <constructor-arg type="java.lang.Class" value="package.Example"/>   
</bean> 
0

desde la primavera 4.3.0 se puede utilizar InjectionPoint o DependencyDescriptor como parámetros para la producción de frijol métodos:

@Component 
public class LoggingFactoryBean { 
    @Bean 
    public Logger logger(InjectionPoint injectionPoint) { 
     Class<?> targetClass = injectionPoint.getMember().getDeclaringClass(); 
     return LoggerFactory.getLogger(targetClass); 
    } 
} 
3
Para

haga que su código sea más consciente de Spring use el InjectionPoint para definir los registradores, es decir:

@Bean 
@Scope("prototype") 
public Logger logger(InjectionPoint ip) { 
    return Logger.getLogger(ip.getMember().getDeclaringClass()); 
} 

@Scope("prototype") se necesita aquí para crear instancia del bean 'logger' todos los métodos de tiempo se llama.

Cuestiones relacionadas