2016-05-31 15 views
7

Ho familiarità con le opzioni di configurazione di Spring Java, compreso l'utilizzo di @Component e @Configuration insieme alle annotazioni @Bean per la registrazione di bean Spring.È possibile registrare tutte le classi all'interno di un pacchetto come bean Spring

Tuttavia, quando la conversione di un progetto di dimensioni adeguate alla primavera, può essere molto alta intensità di lavoro a toccare in modo sistematico tutte le classi del progetto e l'aggiornamento con @Configuration@Bean s o annotare ogni classe con @Component. Abbiamo un grande progetto Groovy da convertire e vorrei semplificare il processo.

La mia domanda: C'è una funzione fornita in primavera che consente di dire a Spring di auto-configurare tutte le classi candidate candidate valide all'interno di un pacchetto specifico?

In caso contrario, quali altre opzioni sono disponibili?

risposta

4

Si può provare a utilizzare il proprio BeanDefinitionRegistryPostProcessor

@Component 
public class CustomBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { 

    @Override 
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { 
    Reflections reflections = new Reflections("my.package.prefix", new SubTypesScanner(false)); 
    Set<Class<? extends Object>> allClasses = reflections.getSubTypesOf(Object.class); 
    for (Class clazz : allClasses) { 
     GenericBeanDefinition gbd = new GenericBeanDefinition(); 
     gbd.setBeanClass(clazz); 
     gbd.setAttribute("attributeName", "attributeValue"); 
     registry.registerBeanDefinition(clazz.getSimpleName() + "_Bean", gbd); 
    } 
    } 

    @Override 
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 
    // Custom post process the existing bean definitions 
    } 

} 

progetto di esempio Sede presso https://github.com/sandarkin/so-q37548350

+0

Grazie Roman. Ho una certa familiarità con la possibilità di registrare un bean in runtime manuall usando 'BeanFactoryPostProcessor' e il metodo' postProcessBeanFactory'. La tua soluzione è molto simile a questi esempi. Non avevo familiarità con l'API 'Reflections'. Proviene da: https://github.com/ronmamo/reflections? Sei a conoscenza se Reflections tiene conto di classi che non sono ancora state caricate in un classloader? Significa che scannerizza anche i file .class? – pczeus

+0

@pczeus, sì, ho usato il riflesso di ronmamo. Inoltre ho creato il progetto github di esempio https://github.com/sandarkin/so-q37548350 che illustra l'approccio descritto. –

+0

Informazioni su 'Riflessioni'. Sì, analizza direttamente i file .class, cosa è dimostrato da [org.reflections.Reflections # scan()] (https://github.com/ronmamo/reflections/blob/master/src/main/java/org /reflections/Reflections.java#L179) e [org.metodi reflection.Reflections # scan (java.net.URL)] (https://github.com/ronmamo/reflections/blob/master/src/main/java/org/reflections/Reflections.java#L241). –

6

avrei fatto più o meno la stessa cosa che ha fatto romana, solo lo farei al momento della compilazione , non in fase di runtime, utilizzando la generazione del codice. La logica qui è che preferisco fortemente che la magia avvenga in fase di costruzione alla magia che accade al momento della distribuzione.

Nella versione più semplice, scrivere un metodo principale che analizza il pacchetto (invece di riflessioni api, sto usando lo scanner ClassPath Guava) e crea un metodo @Bean per ogni classe trovata.

Per la generazione di codice, userei JCodeModel:

public class PackageBeanGenerator { 
    public static void main(String[] args) throws Exception { 
    String packageName = args[0]; 
    JCodeModel codeModel = new JCodeModel(); 
    // create class definition 
    JDefinedClass springConfig = codeModel._package(packageName)._class("SpringConfig"); 
    springConfig.annotate(Configuration.class); 

    for (ClassPath.ClassInfo classInfo : ClassPath.from(
        PackageBeanGenerator.class.getClassLoader() 
     ).getTopLevelClasses(packageName)) { 

     Class<?> type = classInfo.load(); 
     String beanName = CaseFormat.UPPER_CAMEL.to(
          CaseFormat.LOWER_CAMEL, 
          type.getSimpleName()); 
     JMethod beanMethod = springConfig.method(JMod.PUBLIC, type, beanName); 
     beanMethod.annotate(Bean.class); 
     beanMethod.body()._return(JExpr._new(codeModel._ref(type))); 
    } 
    // write class to file 
    codeModel.build(new File("/path/to/output/folder")); 

    } 
} 
+0

Entrambi gli approcci (tempo di esecuzione e tempo di costruzione) sono buone risposte. Speravo che Spring avrebbe fornito un modo per gestire tutto questo per me, ma sta diventando chiaro che non è disponibile. Detto questo, sono d'accordo sul fatto che una soluzione per il tempo di costruzione o un'utilità sia un'opzione migliore. Non ci avevo pensato e, dopo il tuo post, mi sto appoggiando più verso la creazione di un'utilità che farebbe simile a ciò che hai proposto, ma invece di manipolare le classi stesse, identificare i bean candidati e alterare i file sorgente stessi, che potrebbero quindi essere messo nel controllo del codice sorgente. Dopotutto, questo è davvero un esercizio di conversione. – pczeus

+0

@pczeus Sarei molto attento con questo approccio. Come minimo, salverei le fonti modificate in una diversa directory –

7

ClassPathBeanDefinitionScanner è tutto ciò che serve.

public class Main { 
    public static void main(String[] args) { 
     GenericApplicationContext context = new GenericApplicationContext(); 
     ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false); 
     scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true); 
     scanner.scan("net.company.name"); 
     context.refresh(); 

     A a = context.getBean(A.class); 
     System.out.println(a.toString()); 
    } 
} 

È possibile passare la logica personalizzata nel filtro include se si desidera. Nella versione attuale ogni classe nel pacchetto fornito verrà inclusa come bean.

Ma è impossibile creare una struttura di dipendenza corretta sulle classi in modo automatico, in realtà dipende dall'ambito desiderato. Devi farlo con le tue mani.

+0

Può essere utilizzato con un ApplicationContext esistente che viene iniettato? È possibile ottenere un handle per lo Scanner da un ApplicationContext esistente e aggiungere il pacchetto in fase di runtime da includere nella scansione e/o è accettabile avere più di uno Scanner per lo stesso ApplicationContext? – pczeus

1

A rischio di sembrare primitivi, perché non basta fare una semplice ricerca e sostituzione nel proprio IDE (ad esempio cercare "public class" in un pacchetto e sostituirlo con "@Component public class")? Questo dovrebbe essere molto più veloce di cercare di fare qualcosa in modo programmatico.

Problemi correlati