Is it possible to register all classes within a pa

2019-03-27 17:09发布

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?

4条回答
三岁会撩人
2楼-- · 2019-03-27 17:32

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

  }
}
查看更多
Evening l夕情丶
3楼-- · 2019-03-27 17:35

At the risk of sounding primitive, why not just do a simple find and replace in your IDE (e.g. search for "public class" in a package and replace with "@Component public class") ? That should be much quicker than trying to do anything programatically.

查看更多
干净又极端
4楼-- · 2019-03-27 17:41

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

查看更多
地球回转人心会变
5楼-- · 2019-03-27 17:48

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.

查看更多
登录 后发表回答