7

E se sì quale configurazione è necessaria? Non è raccomandato?È possibile iniettare un bean definito con @Component come argomento su un oggetto BeanFactoryPostProcessor?

La classe annotata:

package com.springbug.beanfactorydependencyissue; 

import javax.annotation.Resource; 
import org.springframework.stereotype.Component; 

@Component 
public class DependantBean { 

    @Resource 
    DependencyBean dependencyBean; // Isn't initialized correctly 

    public DependencyBean getDependencyBean() { 
     return dependencyBean; 
    } 
} 

Il fagiolo dipendenza che non riesce:

package com.springbug.beanfactorydependencyissue; 

import org.springframework.stereotype.Component; 

@Component 
public class DependencyBean { 

} 

Testcase:

package com.springbug.beanfactorydependencyissue; 

import static org.fest.assertions.Assertions.assertThat; 

import javax.annotation.Resource; 

import org.springframework.test.context.ContextConfiguration; 
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; 
import org.testng.annotations.Test; 

import com.springbug.beanfactorydependencyissue.DependantBean; 

@ContextConfiguration(locations = "/applicationContext.xml") 
public class AppTest extends AbstractTestNGSpringContextTests { 

    @Resource 
    private DependantBean annotatedBean; 

    @Test 
    public void testThatDependencyIsInjected() { 
     // Fails as dependency injection of annotatedBean.dependencyBean does not work 
     assertThat(annotatedBean.getDependencyBean()).isNotNull(); 
    } 
} 

Una consuetudine BeanFactoryPostProcessor con la dipendenza "difettosa":

package com.springbug.beanfactorydependencyissue; 

import org.springframework.beans.BeansException; 
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 

@Configuration 
public class BeanFactoryPostProcessorConfiguration { 

    /** 
    * The {@link DependantBean} here causes the bug, can 
    * {@link BeanFactoryPostProcessor} have regular beans as dependencies? 
    */ 
    @Bean 
    public static BeanFactoryPostProcessor beanFactoryPostProcessor(
      DependantBean dependantBean) { 
     return new BeanFactoryPostProcessor() { 

      public void postProcessBeanFactory(
        ConfigurableListableBeanFactory beanFactory) 
        throws BeansException { 

      } 
     }; 
    } 
} 

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 
    xsi:schemaLocation=" 
     http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
     http://www.springframework.org/schema/context 
     http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 

    <context:component-scan base-package="com.springbug.beanfactorydependencyissue" /> 
</beans> 

Perché non BeanFactoryPostProcessorConfiguration riferimento DependantBean?

L'istanza risultante in AppTest non è nullo, cioè è stato creato da primavera, ma le sue dipendenze (DependencyBean) sono nulle. Il fatto che Spring non si lamenta del tutto mi porta a credere che questo sia un bug entro la primavera. Questo caso d'uso dovrebbe essere supportato o no?

Btw, sto usando spring - * - 3.1.1.RELEASE.jar Btw 2: il codice per riprodurre il bug può anche essere trovato here.

+0

C'è qualcosa di strano in CommonAnnotationBeanPostProcessor. Puoi passare alle annotazioni @Autowired invece di @Resource? Aiuta? –

+0

Buona teoria ma myDependency è ancora nullo. Sono sorpreso che la primavera lo mangi e continui senza nemmeno riportare un'iniezione fallita. – jontejj

+0

Quando funziona 'ApplicationContextAwareProcessor AbstractApplicationContext $ BeanPostProcessorChecker ConfigurationClassPostProcessor $ ImportAwareBeanPostProcessor CamelBeanPostProcessor CoreNamespacePostProcessor CommonAnnotationBeanPostProcessor AutowiredAnnotationBeanPostProcessor RequiredAnnotationBeanPostProcessor AbstractApplicationContext $ ApplicationListenerDetector' registrati come processori di fagioli, ma quando lo fa non solo' ApplicationContextAwareProcessor' è registrato. – jontejj

risposta

3

Grazie ad alcuni gravi debug della primavera abbiamo scoperto che il parametro di DependantBean a BeanFactoryPostProcessorConfiguration causato l'inizializzazione desiderosi di altri fagioli (seamingly non correlati). Ma poiché la primavera era nello stadio BeanFactoryPostProcessor, lo BeanPostProcessors non era pronto.

Leggendo il Javadoc per BeanFactoryPostProcessor (Grazie a @Pavel per la precisazione) spiega il problema esattamente:

BeanFactoryPostProcessor può interagire e modificare le definizioni di fagioli, ma le istanze mai di fagioli. Ciò potrebbe causare l'anticipazione dei bean prematuri, violando il contenitore e causando effetti collaterali indesiderati. Se è richiesta l'interazione dell'istanza bean, prendere in considerazione l'implementazione di {@link BeanPostProcessor}.

La soluzione:

leggermente modificati applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 
xsi:schemaLocation=" 
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 

<context:component-scan base-package="com.stackoverflow.springbug.beanfactorydependencyissue.other" /> 
</beans> 

Il nuovo bootstrapContext.xml: (Si noti che solo i pacchetti differiscono)

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 
    xsi:schemaLocation=" 
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 

    <context:component-scan base-package="com.stackoverflow.springbug.beanfactorydependencyissue.bootstrap" /> 
</beans> 

Il nuovo Contexts.java: (Nota che bootstrap è il contesto genitore rispetto al normale applicationContext)

package com.stackoverflow.springbug.beanfactorydependencyissue; 

import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext; 

import com.google.common.base.Supplier; 
import com.google.common.base.Suppliers; 

public final class Contexts 
{ 
    private static Supplier<ApplicationContext> bootstrap = Suppliers.memoize(new Supplier<ApplicationContext>(){ 
     public ApplicationContext get() 
     { 
      return new ClassPathXmlApplicationContext("/bootstrapContext.xml"); 
     } 
    }); 

    /** 
    * Context for beans that are needed before initializing of other beans. 
    */ 
    public static ApplicationContext bootstrap() 
    { 
     return bootstrap.get(); 
    } 

    private static Supplier<ApplicationContext> applicationContext = Suppliers.memoize(new Supplier<ApplicationContext>(){ 
     public ApplicationContext get() 
     { 
      return new ClassPathXmlApplicationContext(new String[]{"/applicationContext.xml"}, bootstrap()); 
     } 
    }); 

    public static ApplicationContext applicationContext() 
    { 
     return applicationContext.get(); 
    } 
} 

Il BeanFactoryPostProcessorConfiguration senza DependantBean come parametro:

package com.stackoverflow.springbug.beanfactorydependencyissue.other; 

import org.springframework.beans.BeansException; 
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 

import com.stackoverflow.springbug.beanfactorydependencyissue.Contexts; 
import com.stackoverflow.springbug.beanfactorydependencyissue.bootstrap.DependantBean; 

@Configuration 
public class BeanFactoryPostProcessorConfiguration 
{ 

    /** 
    * The {@link DependantBean} here caused the bug, {@link Contexts#bootstrap()} is used as a 
    * workaround. 
    */ 
    @Bean 
    public static BeanFactoryPostProcessor beanFactoryPostProcessor() 
    { 
     final DependantBean dependantBean = Contexts.bootstrap().getBean(DependantBean.class); 
     System.out.println(dependantBean.getDependencyBean()); 
     return new BeanFactoryPostProcessor(){ 
      public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
      { 

      } 
     }; 
    } 
} 

L'ultima cosa da fare è il lavoro è stato quello di spostare DependantBean e DependencyBean nel pacchetto bootstrap. L'obiettivo è stato raggiunto per leggere le proprietà @Value dal database. Mentre riusare le vecchie definizioni dei fagioli e senza duplicare i fagioli.

0

è necessario dare un id al componente come questo

@Component("myClass") 
public class MyClass implements MyInterface 
    { 
    @Resource 
    private MyDependency myDependency; //Isn't initialized correctly when listOfMyClassBeans references myClass 

    //Implementation skipped for brevity's sake... 
    } 

e quindi utilizzare il riferimento

<ref bean="myClass"> 
+0

Perché l'ID predefinito non funziona? – jontejj

+0

Bel tocco che @NullPointerException risponde a una domanda su una NullPointerException :) – jontejj

+0

Purtroppo non ha aiutato con un id. – jontejj

0

Provare a utilizzare Primavera Util spazio nome e specificare il valore-tipo. Fare riferimento a questo question

+0

Sicuramente una lista digitata migliore è migliore, ma perché risolverebbe questo problema? Si fa riferimento al bean con "myClass" che non è ambiguo. – jontejj

5

Forse più semplice e la risposta descrittiva:

Sì, è possibile utilizzare @Component fagioli come BeanFactoryPostProcessor dipendenza.

Tuttavia, ogni dipendenza di BeanFactoryPostProcessor verrà creata prima che qualsiasi BeanPostProcessor sia attivo. E questi includono:

  • CommonAnnotationBeanPostProcessor - responsabile @PostConstruct, @Resource e alcune altre annotazioni
  • AutowiredAnnotationBeanPostProcessor - responsabili @Autowired e @Value annotazioni
  • ... e molti altri ...

Quindi riassumerlo:

Sì, è possibile utilizzare @Component fagioli come BeanFactoryPostProcessor dipendenza, ma non possono utilizzare l'iniezione di annotazione base (@Autowired, @Resource, @WebServiceRef, ...) e altre caratteristiche fornite dal BeanPostProcessor s.


Soluzione per il tuo esempio potrebbe essere quello di creare ApplicationContext gerarchia come avete suggerito:

  • Ogni contesto inizializza e si applica la propria Post Processor infrastrutture, dove è ancora possibile fare riferimento le dipendenze da genitore contesti.

Altri approcci potrebbero essere (che io preferirei):

interfaccia
  • Uso BeanFactoryAware sul @Component fagioli e tirare il tuo dipendenza da soli (come la primavera non sarà iniettare).
  • Definire i bean connessi con BeanFactoryPostProcessor s nella configurazione del contesto XML o @Configuration (ad esempio non utilizzare @Component per questi bean).
+0

Ottimi suggerimenti. La soluzione di contesto dell'applicazione genitore ha funzionato come un incantesimo in quanto riusciamo a riutilizzare le definizioni di bean esistenti. Se il progetto non fosse così grande, avrei comunque potuto seguire i tuoi suggerimenti. – jontejj

Problemi correlati