Spring类路径组件扫描

时间:2017-05-01 14:30:29

标签: java spring

我需要为类构建映射(字面意思为Map<Class<?>, String>),它在运行时不会发生变化,并且保持解耦的优先级。由于我在Spring应用程序中,我认为我会使用注释,而ClassPathScanningCandidateComponentProvider或多或少都是这样:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Mapping {
     String value();
}

public class MappingLookUp {
    private static final Map<Class<?>, String> MAPPING_LOOK_UP;
    static {
        Map<Class<?>, String> lookUp = new HashMap<>();
        ClassPathScanningCandidateComponentProvider scanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false);
        scanningCandidateComponentProvider.addIncludeFilter(new AnnotationTypeFilter(Mapping.class));
        for (BeanDefinition beanDefinition : scanningCandidateComponentProvider.findCandidateComponents("blah")) {
            Class<?> clazz;
            try {
                clazz = Class.forName(beanDefinition.getBeanClassName());
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            Mapping mapping = AnnotationUtils.getAnnotation(clazz, Mapping.class);
            if (mapping == null) {
                throw new IllegalStateException("This should never be null");
            }
            lookUp.put(clazz, mapping.value());
        }
        MAPPING_LOOK_UP = Collections.unmodifiableMap(lookUp);
    }

    public static String getMapping(Class<?> clazz) {
        ...
    }
}

虽然我相信这会奏效,但感觉就像:

  1. 要进行静态初始化
  2. 使用扫描组件提供商,即使它通常被推荐用于此目的; BeanDefinition使它听起来像是用于查找Spring bean而不是一般的类定义。
  3. 要明确的是,带注释的值是数据类 - 而不是Spring管理的bean - 因此BeanPostProcessor模式不合适,事实上,这就是为什么它感到尴尬的原因使用扫描组件提供程序,对我来说,似乎是为了发现Spring托管bean。

    这是实施此模式的正确方法吗?它是提供者的正确应用吗?是否有可行的替代方案而不引入其他类路径扫描实现?

2 个答案:

答案 0 :(得分:4)

我会建议这看起来不像是以春天的方式完成。

如果我要这样做,我会使用Spring的BeanPostProcessorBeanFactoryPostProcessor。这两个都允许对Spring的BeanFactory中的所有Bean进行内省,并允许你摆脱当前设置的静态,因为PostProcessors本身就是Spring Bean。

class MappingLookup implements BeanPostProcessor {
  private final Map<Class<?>, String> lookup = new HashMap<>();

  @Override
  public Object postProcessAfterInitialization(Object bean, String beanName) {
    // check bean's class for annotation...
    // add to lookup map as necessary...
    // make sure to return bean (javadoc explains why)
    return bean;
  }

  public String getMapping(Class<?> clazz) {
    // ...
  }

  // omitted other methods...
}

答案 1 :(得分:0)

我最近提出了一个非常类似的问题How to get list of Interfaces from @ComponentScan packages,最后实施了第一个建议的方法。

您可以看到代码https://github.com/StanislavLapitsky/SpringSOAProxy看到https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/service/ProxyableScanRegistrar.java,当然还有初始化注释https://github.com/StanislavLapitsky/SpringSOAProxy/blob/master/core/src/main/java/org/proxysoa/spring/annotation/ProxyableScan.java关键是添加@Import({ProxyableScanRegistrar.class})

关键代码是

public class ProxyableScanRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // Get the ProxyableScan annotation attributes
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ProxyableScan.class.getCanonicalName());

        if (annotationAttributes != null) {
            String[] basePackages = (String[]) annotationAttributes.get("value");

            if (basePackages.length == 0) {
                // If value attribute is not set, fallback to the package of the annotated class
                basePackages = new String[]{((StandardAnnotationMetadata) metadata).getIntrospectedClass().getPackage().getName()};
            }