2010-06-14 10 views
14

Ich versuche Frühling zu verwenden, um einen SLF4J Logger in eine Klasse zu injizieren etwa so:Spring Constructor Injektion von SLF4J Logger - Wie bekommt man Injektionszielklasse?

@Component 
public class Example { 

    private final Logger logger; 

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

ich die FactoryBean Klasse gefunden habe, die ich umgesetzt habe. Aber das Problem ist, dass ich keine Informationen über die Injektion Ziel zu erreichen:

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? */); 
    } 
} 

Sind FactoryBean auch der richtige Weg zu gehen? Bei Verwendung von Picocontainern factory injection erhalten Sie die Type des Ziels übergeben. In guice ist es ein bisschen trickier. Aber wie schaffen Sie das im Frühling?

+4

Ist es das wirklich wert, nur um '' LoggerFactory.getLogger() 'zu vermeiden? – skaffman

+0

Ich mag die statische Bindung an LoggerFactory nicht, aus den gleichen Gründen, die Martin Fowler in http://martinfowler.com/articles/injection.html umreißt. Eine injizierte LoggerFactory ist eine akzeptable Lösung (nach dem Service-Locator-Muster), aber ein bisschen ausführlich. Ich nehme an, man könnte argumentieren, dass die Log-Injection einen Service-Locator verwenden muss, da eine reine Abhängigkeit ziel-agnostisch sein sollte. Aber die Locator-Lösung ist ausführlich, andere Frameworks unterstützen sie, und ich würde erwarten, dass Spring einige Informationen über das Ziel liefern könnte. Ich frage mich nur, ob das wirklich nicht möglich ist. –

+0

Ich meine, diese Information wird an BeanPostProcessors übergeben: http://www.tzavellas.com/techblog/2007/03/31/implementing-seam-style-logger-injection-with-spring/. Kann das für die Konstruktorinjektion nicht erreicht werden? –

Antwort

10

Ich löste es mit einer benutzerdefinierten BeanFactory. Wenn jemand eine bessere Lösung findet, würde ich mich freuen, es zu hören. Wie auch immer, hier ist der BeanFactory:

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); 
     } 
    } 
} 

Beispiel für die Verwendung mit einem XML-config:

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

EDIT:

Unten finden Arend v Reinersdorffs Version verbessert (siehe die Kommentare für ein. Erläuterung).

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

Danke für das Teilen. –

+0

Funktioniert gut, danke. Zwei Punkte: 1. Es sollte 'Logger.class == ...' anstelle von 'isAssignableFrom (...)' sein. 'isAssignableFrom()' ist für jede Unterklasse von Logger wahr, aber Logger kann nicht in ein Feld seiner Unterklasse eingefügt werden. Zum Beispiel wird '@Autowired MyLogger myLogger;' für eine 'Klasse MyLogger implementiert Logger' immer eine Ausnahme auslösen. 2. Gibt es einen Grund, warum der Logger nicht in optionale Abhängigkeiten eingefügt wird? –

+0

1. Guter Punkt. Ich denke, ich beabsichtigte, das Gegenteil zu tun, z. B. descriptor.getMethodParameter(). GetParameterType(). IsAssignableFrom (Logger.class), aber dies würde Loggers in Objektfelder injizieren, also vielleicht gleich ist der Weg zu gehen. 2. Nein, kein Grund. Der Code oben arbeitete für meinen Fall, aber fühlen Sie sich frei, ihn zu ändern. Wenn Sie ein Update haben, senden Sie es mir und ich werde den Beitrag bearbeiten. Prost –

0
  1. Warum erstellen Sie einen neuen Logger für jede Instanz? Das typische Muster ist ein Logger pro Klasse (als privates statisches Mitglied).

  2. Wenn Sie es wirklich so machen wollen: Vielleicht können Sie eine Logger-Fabrik-Klasse schreiben und das injizieren? Etwas wie:

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

1: Siehe http://wiki.apache.org/commons/Logging/StaticLog 2: Das habe ich schon gemacht. Es funktioniert ok, ist aber sehr ausführlich IMO. –

0

Ja, Sie sind in der falschen Richtung. Wenn ich du wäre, würde ich die LoggerFactory injizieren. Wenn Sie ausblenden möchten, dass es slf4j ist, dann würde ich eine LoggerFactory-Schnittstelle definieren und eine Klasse injizieren, die an slfl4j Logger delegiert.

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); 
    } 
} 

Doch bevor Sie dorthin gehen, dann ist dies etwa, was org.apache.commons.logging richtig macht? http://commons.apache.org/logging/

Sie verwenden Logbuch statt Loggers:

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

Apache dann Classpath schaut durch, um zu sehen, ob Sie log4j oder andere und Delegierten der „beste“ eine, die es findet. Slf4j ersetzt log4j im Klassenpfad. Wenn Sie es also geladen haben (und Apache log4j ausgeschlossen), wird die Commons-Protokollierung stattdessen an diesen delegieren.

+0

Das ist fast die gleiche Antwort wie Mike gab. Ich würde gerne den Login weitergeben, da ich einen reinen OO-Stil mag. Der Service-Locator-Ansatz ist machbar, aber ein wenig aufdringlich. Man könnte argumentieren, dass die Laufzeitklasse nicht Teil der Schnittstelle in getLog sein sollte, aber ich würde erwarten, dass Spring eine Struktur weitergibt, die das Ziel beschreibt. Ist das überhaupt nicht möglich? –

+1

Darüber hinaus war der Commons-Logging-Hack der Auto-Discovery der Grund dafür, dass slf4j geboren wurde. –

+0

Der Frühling hat diese Kapazität nicht mit FactoryBean, nein. Es würde eine Art zirkuläre Abhängigkeit erzeugen, wenn X von Y abhängt, aber Y von X weiß, wenn es konstruiert wird. – Gray

20

Hier ist eine Alternative zu Ihrer Lösung. Sie können Ihr Ziel mit BeanFactoryPostProcessor Implementierung erreichen.

Angenommen, Sie möchten eine Klasse mit Protokollierung haben.Hier ist sie:

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

    @Loggable 
    public class MyBean { 

    private Logger logger; 
    } 

Wie Sie diese Klasse nichts tut, sehen können, und erstellt nur ein Logger Behälter für Einfachheit zu sein. Die einzige bemerkenswerte Sache ist hier @Loggable Annotation. hier sein Quellcode:

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 { 
} 

Diese Anmerkung ist nur ein Marker für die weitere Verarbeitung. Und hier ist ein interessanteste Teil:

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(); 
       } 
      } 
     } 
    } 
} 

Es durchsucht alle Bohnen, und wenn eine Bohne als @Loggable markiert ist, es seinen privaten Bereich initialisieren mit dem Namen Logger. Sie könnten sogar noch weiter gehen und einige Parameter in @Loggable Annotation übergeben. Zum Beispiel könnte es ein Name des Feldes sein, das dem Logger entspricht.

Ich habe Log4j in diesem Beispiel verwendet, aber ich denke, es sollte genau so funktionieren mit slf4j.

+1

+1 Klarstes BeanFactoryPostProcessor-Beispiel, das ich überall gesehen habe – Anonymoose

+1

Vielen Dank für Ihren Beitrag. Ihr Beispiel ist dem Link sehr ähnlich, auf den ich in einem der obigen Kommentare verwiesen habe: http://www.tzavellas.com/techblog/2007/03/31/implementing-seam-style-logger-injection-with-spring/. Es funktioniert gut, kann aber nicht mit Konstruktoren arbeiten. Ich würde gerne Konstruktorinjektion verwenden, da dies bedeutet, dass der Logger im Konstruktor verwendet werden kann und dass ich das Feld final deklarieren kann, eine gute Gewohnheit in Multithread-Anwendungen. Im Idealfall würde die Konstruktorinjektion genauso funktionieren wie die Feldinjektion, dies würde jedoch eine Art Konstruktorargument-Builder erfordern. –

+0

@disown Ich denke, du versuchst ein nicht existierendes Problem zu lösen.Es ist sehr unwahrscheinlich, dass Sie Multithreading-Probleme beim Start verhindern müssen. ApplicationContext-Nachkommen sind garantiert Thread-sicher (http://forum.springsource.org/showthread.php?t=11791). – wax

-2

Ich versuche, diese Funktion in offizielle SLF4J API zu bekommen. Bitte unterstützen/Stimme/contribute: https://issues.jboss.org/browse/JBLOGGING-62

(diese Funktion bereits von JBoss Logging + Seam Solder implementiert ist, siehe http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-logging.html)

11,4. Logger native API

Sie können auch eine "plain old" Logger injizieren (von der JBoss Logging API):

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

public class LogService { 

    @Inject 
    private Logger log; 

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

} 

Log-Meldungen von diesem Logger erstellt eine Kategorie (Logger Name) haben gleich der vollständig -qualifizierter Klassenname der Bean-Implementierungsklasse Sie können eine Kategorie explizit mithilfe einer Anmerkung angeben.

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

Sie können auch eine Kategorie mit einem Verweis auf einen Typ festlegen:

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

Es tut uns keine relevante Antwort zu geben.

2

Versuchen Sie so etwas wie:

@Component 
public class Example { 

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

} 

Und:

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

Seit dem Frühjahr 4.3.0 Sie InjectionPoint oder DependencyDescriptor als Parameter für Bohnenherstellungsverfahren verwendet werden können: Um

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

Machen Sie Ihren Code mehr Frühling bewusst verwenden Sie die InjectionPoint, um die Logger zu definieren, d.h.:

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

@Scope("prototype") wird hier gebraucht 'Logger' Bean-Instanz jedes Mal Methode aufgerufen wird, zu erstellen.

Verwandte Themen