2010-06-14 8 views
14

Próbuję użyć Wiosna wstrzyknąć rejestratora slf4j do klasy tak:Wstrzyknięty konstruktor sprężyny rejestratora SLF4J - jak uzyskać klasę docelową wtrysku?

@Component 
public class Example { 

    private final Logger logger; 

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

Znalazłem klasę FactoryBean, które zostały wdrożone. Problem polega jednak na tym, że nie mogę uzyskać żadnych informacji na temat celu wstrzykiwania:

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

Czy FactoryBean jest jeszcze dobrym rozwiązaniem? Podczas korzystania z picocontainerów factory injection, dostajesz Type przekazanego celu. W guice jest to nieco trickier. Ale jak to osiągnąć na wiosnę?

+4

Czy to naprawdę jest tego warte, aby uniknąć mówienia '= LoggerFactory.getLogger()'? – skaffman

+0

Nie podoba mi się statyczne powiązanie z LoggerFactory, z tych samych powodów, które Martin Fowler opisuje w http://martinfowler.com/articles/injection.html. Wstrzykiwany LoggerFactory jest akceptowalnym rozwiązaniem (zgodnie z wzorcem lokalizatora usług), ale nieco bardziej gadatliwy. Przypuszczam, że można się spierać, że wtrysk dziennika musi korzystać z lokalizatora usług, ponieważ czysta zależność powinna być agnostyczna. Ale rozwiązanie lokalizatora jest pełne, inne wspierają go i spodziewam się, że Spring będzie w stanie podać jakieś informacje na temat celu. Zastanawiam się, czy to naprawdę nie jest możliwe. –

+0

Mam na myśli, że ta informacja jest przekazywana do BeanPostProcessors: http://www.tzavellas.com/techblog/2007/03/31/implementing-seam-style-logger-injection-with-spring/. Czy tego samego nie można zrobić dla wtrysku konstruktora? –

Odpowiedz

10

Rozwiązałem go za pomocą niestandardowego BeanFactory. Jeśli ktoś wymyśli lepsze rozwiązanie, byłbym szczęśliwy, słysząc to. Tak czy inaczej, oto fabryka fasola:

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

Przykład użycia z config XML:

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

EDIT:

Poniżej można znaleźć Arend v Reinersdorffs poprawiona wersja (patrz komentarze do An. wyjaśnienie).

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

Dzięki za udostępnienie. –

+0

Działa ładnie, dzięki. Dwa punkty: 1. Powinien to być 'Logger.class == ...' zamiast 'isAssignableFrom (...)'. 'isAssignableFrom()' jest prawdziwe dla każdej podklasy programu Logger, ale Logger nie może zostać wstrzyknięty do pola swojej podklasy. Na przykład '@Autowired MyLogger myLogger;' dla 'klasy MyLogger implementuje Logger' zawsze rzuci wyjątek. 2. Jakiegokolwiek powodu, dla którego program rejestrujący nie zostanie wstrzyknięty do opcjonalnych zależności? –

+0

1. Dobra uwaga. Myślę, że zamierzałem zrobić coś odwrotnego, tj. Descriptor.getMethodParameter(). GetParameterType(). IsAssignableFrom (Logger.class), ale to spowodowałoby wstawienie Loggers do pól Object, więc może to jest droga do zrobienia. 2. Nie, bez powodu. Powyższy kod zadziałał w moim przypadku, ale możesz go zmodyfikować. Jeśli masz aktualizację, wyślij ją do mnie, a ja zmienię wpis. Pozdrawiam –

0
  1. Dlaczego tworzysz nowy program rejestrujący dla każdej instancji? Typowy wzór to jeden rejestrator na klasę (jako prywatny statyczny element członkowski).

  2. Jeśli naprawdę chcesz to zrobić w ten sposób: Może możesz napisać klasę fabryki rejestratora i wstrzyknąć to? Coś jak:

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

1: Zobacz http://wiki.apache.org/commons/Logging/StaticLog 2: To jest to, co już zrobiłem. Działa dobrze, ale jest bardzo gadatliwy IMO. –

0

Tak, idziesz w złym kierunku. Gdybym był tobą, wstrzyknęłabym LoggerFactory. Jeśli chcesz ukryć, że jest to slf4j, to zdefiniowałbym interfejs LoggerFactory i wstrzyknę klasę, która deleguje do 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); 
    } 
} 

Jednak zanim się tam udasz, jest to mniej więcej to, co robi org.apache.commons.logging, prawda? http://commons.apache.org/logging/

użyć dziennika zamiast rejestratorów:

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

Apache następnie przegląda ścieżki klasy, aby sprawdzić, czy masz log4j lub innym i delegatów na „najlepszego” ten, który znajdzie. Slf4j zastępuje log4j w ścieżce klas, więc jeśli masz to załadowane (i wyłączone jest logowanie apache), to zamiast tego zostanie przeniesione do niego wspólne logowanie.

+0

To prawie ta sama odpowiedź co Mike. Chciałbym przekazać log, ponieważ lubię styl "czysty". Podejście "lokalizator usług" jest wykonalne, ale nieco inwazyjne. Można argumentować, że klasa runtime nie powinna być częścią interfejsu w getLog, ale spodziewam się, że Spring przekaże jakąś strukturę opisującą cel. Czy to w ogóle nie jest możliwe? –

+1

Co więcej, wspólne logowanie się do hack of auto-discovery było powodem, dla którego narodził się slf4j. –

+0

Wiosna nie ma tej pojemności w FactoryBean, nie. Stworzyłoby to pewną okrągłą zależność, jeśli X zależy od Y, ale Y wie o X, kiedy jest skonstruowany. – Gray

20

Oto alternatywa dla rozwiązania. Możesz osiągnąć swój cel dzięki implementacji BeanFactoryPostProcessor.

Załóżmy, że chcesz mieć zajęcia z rejestracją.Oto ona:

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

    @Loggable 
    public class MyBean { 

    private Logger logger; 
    } 

Jak można zobaczyć klasa ta nie robi nic i stworzył po prostu być pojemnik rejestrator dla prostoty. Jedyną niezwykłą rzeczą jest tutaj adnotacja: @Loggable. tu swój kod źródłowy:

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

tej uwagi jest jedynie markerem dla dalszego przetwarzania. I tu jest najciekawsza część:

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

To przeszukuje wszystkie fasola, fasola i jeżeli jest oznaczony jako @Loggable, to zainicjować swój prywatny pole z nazwą rejestratora. Możesz przejść jeszcze dalej i przekazać niektóre parametry w adnotacji @Loggable. Na przykład może to być nazwa pola odpowiadającego loggerowi.

Użyłem Log4j w tym przykładzie, ale myślę, że powinien działać dokładnie tak samo z slf4j.

+1

+1 Najwygodniejszy BeanFactoryPostProcessor przykład Widziałem wszędzie – Anonymoose

+1

Dziękuję za twój post. Twój przykład jest podobny do linku, do którego się odwołałem w jednym z powyższych komentarzy: http://www.tzavellas.com/techblog/2007/03/31/implementing-seam-style-logger-injection- with-spring/. Działa dobrze, ale nie może działać na konstruktorach. Chciałbym użyć iniekcji konstruktora, ponieważ oznacza to, że rejestrator może być użyty w konstruktorze, i że mogę zadeklarować ostateczne pole, dobry nawyk w aplikacjach wielowątkowych. Idealnie iniektor konstruktora działałby w taki sam sposób, jak iniekcja polowa, ale wymagałoby to pewnych konstruktorów argumentów konstruktora. –

+0

@disown Myślę, że próbujesz rozwiązać nieistniejący problem.Jest bardzo mało prawdopodobne, że będziesz musiał zapobiegać problemom wielowątkowości na starcie. Gwarantuje się, że potomkowie ApplicationContext będą bezpieczni w wątkach (http://forum.springsource.org/showthread.php?t=11791). – wax

-2

Próbuję włączyć tę funkcję do oficjalnego API SLF4J. Prosimy o wsparcie/głos/przyczynić: https://issues.jboss.org/browse/JBLOGGING-62

(ta funkcja jest już realizowany przez JBoss Seam Rejestrowanie + lutowane, patrz http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-logging.html)

11,4. Native rejestrator API

Można również wstrzyknąć "zwykły stary" Logger (z Logging API JBoss):

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

public class LogService { 

    @Inject 
    private Logger log; 

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

} 

wiadomości dziennika utworzone z tego rejestratora będzie miała kategorię (nazwa rejestratora) równa się w pełni -kwalifikowalna nazwa klasy klasy implementacji bean. Kategorię można jawnie określić za pomocą adnotacji.

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

Można również określić kategorię używając odniesienie do typu:

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

Przepraszamy za nie dostarczenie odpowiedniej odpowiedzi.

2

Spróbuj czegoś takiego:

@Component 
public class Example { 

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

} 

oraz:

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

od wiosny 4.3.0 można użyć InjectionPoint lub DependencyDescriptor jako parametry do fasoli produkujących metodami:

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

Aby spraw, aby twój kod był bardziej sprężysty, używaj InjectionPoint do definiowania rejestratorów, tj.:

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

@Scope("prototype") konieczna jest tutaj, aby utworzyć „rejestratora” wystąpienie fasoli każda metoda jest wywoływana raz.

Powiązane problemy