使用ImportBeanDefinitionRegistrar处理自定义注解将类注册到容器中

发布时间 2023-06-08 12:34:25作者: maketime

START

两个自定义注解:

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(LogRegistry.class)
public @interface EnableLog {
    String basePackage() default "";
}

该注解的作用是扫描指定的basePackage目录中使用了@Log注解的类,并将这些类注册到容器中;

如果basePackage为空,那么扫描使用了@EnableLog的目录;

@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}

使用了@Log注解的类都会被注册到容器中。

下面是最关键的LogRegistry类,这个类会将所有使用@Log直接的类实际地注册到容器中:

public class LogRegistry implements ImportBeanDefinitionRegistrar {
    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableLog.class.getName()));

        final String basePackage = Optional.ofNullable(StringUtils.isEmpty(attributes.getString("basePackage")) ? null : attributes.getString("basePackage")).orElseGet(() -> {
            try {
                final String className = importingClassMetadata.getClassName();
                final Class<?> aClass = Class.forName(className);
                return aClass.getPackage().getName();
            } catch (Exception e) {
                return "";
            }
        });

        registry(registry, Log.class.getName(), basePackage);
    }

    public void registry(BeanDefinitionRegistry registry, String className, String basePackage) {
        ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage, className);
        for (BeanDefinition beanDefinition : candidates) {
            ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(beanDefinition);
            beanDefinition.setScope(scopeMetadata.getScopeName());
            String beanName = buildDefaultBeanName(beanDefinition);
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
            BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
        }
    }

    private String buildDefaultBeanName(BeanDefinition definition) {
        String beanClassName = definition.getBeanClassName();
        String shortClassName = ClassUtils.getShortName(beanClassName);
        return Introspector.decapitalize(shortClassName);
    }

    private Set<BeanDefinition> findCandidateComponents(String basePackage, String className) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();

        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                basePackage.replace(".", "/") + '/' + DEFAULT_RESOURCE_PATTERN;
        Resource[] resources = new Resource[0];
        try {
            resources = resolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                if (metadataReader.getAnnotationMetadata().isAnnotated(className)) {
                    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                    sbd.setSource(resource);
                    candidates.add(sbd);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return candidates;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry);
    }
}

很重要的一点是这个类实现了ImportBeanDefinitionRegistrar接口。

手动注册bean的大致流程:

  • 获取basePackage
  • 通过basePackage,扫描指定的注解(@Log),获取最终的BeanDefinition集合
  • 对所有的BeanDefinition进行注册

关于为什么没有使用BeanDefinitionRegistryPostProcessor?以下是BeanDefinitionRegistryPostProcessor 和 ImportBeanDefinitionRegistrar 的区别(来自ChatGPT):

  • BeanDefinitionRegistryPostProcessor 和 ImportBeanDefinitionRegistrar 都是用来动态注册 BeanDefinition 的接口,但是它们的注册方式和作用有所区别。
  • BeanDefinitionRegistryPostProcessor 接口允许 Spring 用户在应用程序上下文装配之前修改 BeanDefinition 或添加额外的 BeanDefinition。这个接口的 postProcessBeanDefinitionRegistry() 方法会在 BeanDefinition 装载之前被调用,允许用户向应用程序上下文中添加、修改或删除 BeanDefinition。
  • ImportBeanDefinitionRegistrar 接口的主要作用是在当前应用程序上下文中注册额外的 BeanDefinition。它的作用不同于 BeanDefinitionRegistryPostProcessor,它是在应用程序上下文已经装载配置之后运行的,它的registerBeanDefinitions() 方法允许用户向应用程序上下文中注册额外的 BeanDefinition。
  • 所以说,BeanDefinitionRegistryPostProcessor 是主要用来调整和修改已有的 BeanDefinition,而 ImportBeanDefinitionRegistrar 是用来注册新的 BeanDefinition 的。这两个接口都可以用来动态地注册 Bean,但是它们发挥作用的时间和目的不同。

看一下BeanDefinitionRegistryPostProcessor接口的定义:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

这个接口有一个问题,虽然能注册bean,和从容器中获取bean。

但是如果自定义注解时,它没有办法拿到自定义注解的信息,而ImportBeanDefinitionRegistrar接口registerBeanDefinitions方法的AnnotationMetadata参数可以获取到自定义注解的信息。这样才能针对使用了自定义注解的类进行注册。

END