Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to register all classes within a package as Spring beans

I'm familiar with Springs Java based configuration options, including the usage of @Component and @Configuration in conjunction with @Bean annotations to register Spring beans.

However, when converting a decent size project to Spring, it can be very labor intensive to systematically touch all classes in the project and update with @Configuration @Beans or annotating each class with @Component. We have a large Groovy project to be converted and I would like to simplify the process.

My question: Is there a facility provided in Spring that allows you to tell Spring to auto-configure all valid bean candidate classes within a specific package?

If not, what other options are available?

like image 511
pczeus Avatar asked May 31 '16 14:05

pczeus


Video Answer


3 Answers

ClassPathBeanDefinitionScanner is all you need.

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

You can pass custom logic in include filter if you want. In current version every class in the provided package will be included as a bean.

But it is impossible to build a right dependency structure on your classes automagically, it really depends on the scope you want. You need to do it by your hands.

like image 185
SerCe Avatar answered Oct 21 '22 03:10

SerCe


I'd do pretty much the same thing that Roman did, only I'd do it at build time, not at runtime, using code generation. The rationale here is that I strongly prefer magic to happen at build time to magic that happens at deploy time.

In the simplest version, write a main method that scans the package (instead of reflections api, I'm using Guava's ClassPath scanner) and creates a @Bean method for every class it finds.

For the Code generation, I'd use 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"));

  }
}
like image 30
Sean Patrick Floyd Avatar answered Oct 21 '22 02:10

Sean Patrick Floyd


You can try to use your own 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
  }

}

See sample project at https://github.com/sandarkin/so-q37548350

like image 29
Roman Sandarkin Avatar answered Oct 21 '22 02:10

Roman Sandarkin