2016-05-31 2 views
7

Я знаком с параметрами конфигурации на основе Java Springs, включая использование @Component и @Configuration в сочетании с @Bean аннотациями для регистрации весенних бобах.Возможно ли зарегистрировать все классы в пакете как Весенние бобы

Однако при преобразовании проекта приличного размера, чтобы весной, это может быть очень трудоемким систематически перебрать все классы в проекте и обновление с @Configuration@Bean с или аннотирования каждого класса с @Component. У нас есть большой проект Groovy, который нужно преобразовать, и я хотел бы упростить этот процесс.

Мой вопрос: Есть ли средство, предоставленное весной, которое позволяет рассказать Spring о том, чтобы автоматически настроить все допустимые классы кандидатов в конкретный пакет?

Если нет, то какие другие варианты доступны?

ответ

4

Вы можете попробовать использовать свой собственный 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 
    } 

} 

См пример проекта на https://github.com/sandarkin/so-q37548350

+0

Спасибо, Роман. Я немного знаком с возможностью регистрации компонента в runtime manuall с использованием 'BeanFactoryPostProcessor' и метода postProcessBeanFactory. Ваше решение очень похоже на эти примеры. Я не был знаком с API 'Reflections'. Это происходит от: https://github.com/ronmamo/reflections? Знаете ли вы, что Reflections учитывают классы, которые еще не загружены в загрузчик классов? Смысл сканирует также .class файлы? – pczeus

+0

@pczeus, да, я использовал отражение от ronmamo. Также я создал образец github проекта https://github.com/sandarkin/so-q37548350, который иллюстрирует описанный подход. –

+0

О 'Размышления'. Да, он сканирует файлы .class напрямую, что подтверждается [org.reflections.Reflections # scan()] (https://github.com/ronmamo/reflections/blob/master/src/main/java/org /reflections/Reflections.java#L179) и [org.reflections.Reflections # scan (java.net.URL)] (https://github.com/ronmamo/reflections/blob/master/src/main/java/org/reflections/Reflections.java#L241). –

6

Я бы сделал почти то же самое, что Роман сделал, только я бы сделал это во время сборки , а не во время выполнения, с использованием генерации кода. Обоснованием здесь является то, что я сильно предпочитаю, чтобы магия происходила во время создания магии, которая происходит во время развертывания.

В простейшей версии напишите основной метод, который сканирует пакет (вместо отражений api, я использую сканер ClassPath Guava) и создает метод @Bean для каждого найденного класса.

Для генерации кода, я использовал бы 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

Оба подхода (время выполнения и время сборки) являются хорошими ответами. Я надеялся, что Spring предоставит мне возможность справиться со всем этим, но становится ясно, что это невозможно. Учитывая это, я согласен с тем, что решение по времени сборки или утилита является лучшим вариантом. Я не думал об этом и после вашего сообщения, я больше склоняюсь к созданию утилиты, которая будет делать то же, что вы предлагали, но вместо этого манипулируете самими классами, идентифицируете потенциальные компоненты и сами изменяете исходные файлы, быть поставлены в исходное управление. В конце концов, это действительно упражнение преобразования. – pczeus

+0

@pczeus Я был бы очень осторожен с этим подходом. По крайней мере, я бы сохранил измененные источники в другом каталоге –

7

ClassPathBeanDefinitionScanner все, что вам нужно.

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

Вы можете передать пользовательскую логику в фильтр включения, если хотите. В текущей версии каждый класс в предоставленном пакете будет включен в качестве компонента.

Но невозможно построить правильную структуру зависимостей на ваших классах автоматически, это действительно зависит от области действия, которую вы хотите. Вам нужно сделать это своими руками.

+0

Может ли это использоваться с существующим ApplicationContext, который вводится? Можно ли получить дескриптор Сканера из существующего ApplicationContext и добавить пакет во время выполнения для включения в сканирование и/или допустимо ли иметь более одного сканера для одного и того же ApplicationContext? – pczeus

1

С риском примириться, почему бы просто не просто найти и заменить в своей среде IDE (например, искать «открытый класс» в пакете и заменить на «публичный класс @Component»)? Это должно быть намного быстрее, чем пытаться сделать что-либо программно.

Смежные вопросы