Spring 自己实现一个自定义Bean注解注册器来惊艳面试官

背景

我们都知道在我们最开始使用spring定义Bean的时候有如下方式

<bean id="testBean" class="com.zou.TestBean"/>
复制代码

如果Bean多了我们不可能一个一个Bean标签去定义,就有了基于包去扫描

<context:component-scan base-package="com.zou"/>
复制代码

后来流行注解编程后就将xml改为@ComponentScan注解了,然后配置@Configuration注解一起使用

@ComponentScan(basePackages = {"com.zou"})
public class Application { ... }
复制代码

但是不管使用上面哪一种方式,他扫描的都只是Spring 内部定义的一些Bean注册注解,比如@Component@Service@Controller@Repository等。但有时候我们需要自定义一些注解来区分这些Bean的作用,比如我这边想定义一些事件处理的Bean,自定义一个注解(Handle)来区分他们和普通的Bean区分

Spring内置扫描器

目前Spring主要的Bean扫描器有两个

  • ClassPathBeanDefinitionScanner:component-scan标签底层底层实现
  • ComponentScanAnnotationParser:@ComponentScan注解配合@Configuration注解底层实现

我们这里简单看看ClassPathBeanDefinitionScanner的处理过程

ClassPathBeanDefinitionScanner 类结构

image-20220119103313832

整个处理过程如下:

  1. 遍历basePackages,根据每个basePackage找出这个包下的所有的class
  2. 遍历找到的Resource集合,通过includeFilters和excludeFilters判断是否解析。这里的includeFilters和excludeFilters是TypeFilter接口类型的集合,是ClassPathBeanDefinitionScanner内部的属性。TypeFilter接口是一个用于判断类型是否满足要求的类型过滤器。excludeFilters中只要有一个TypeFilter满足条件,这个Resource就会被过滤。includeFilters中只要有一个TypeFilter满足条件,这个Resource就不会被过滤
  3. 如果没有被过滤。把Resource封装成ScannedGenericBeanDefinition添加到BeanDefinition结果集中
  4. 返回最后的BeanDefinition结果集

这里就不过多深入研究原理了,我们以实战为主

实战

自定义Bean注解

首先我们肯定需要一个注解Handle

  • Handle
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Handle {
}
复制代码

接下来就需要知道如何去扫描到加了这些注解的Bean,并注册到Spring容器中

自定义 注解扫描器

  • EnableHandle
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({HandleRegistrar.class})
public @interface EnableHandle {

    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}
复制代码

自定义Bean注册处理器

  • HandleRegistrar
public class HandleRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    private ResourceLoader resourceLoader;

    private Environment environment;

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

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

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathBeanDefinitionScanner scanner = getScanner(registry);
        scanner.setResourceLoader(this.resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(Handle.class));
        Set<String> basePackages = this.getBasePackages(importingClassMetadata);
        scanner.scan(basePackages.toArray(new String[]{}));

    }

    protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
        Map<String, Object> attributes = importingClassMetadata
                .getAnnotationAttributes(EnableHandle.class.getCanonicalName());

        Set<String> basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (String pkg : (String[]) attributes.get("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }

        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
        }
        return basePackages;
    }

    protected ClassPathBeanDefinitionScanner getScanner(BeanDefinitionRegistry registry) {
        return new ClassPathBeanDefinitionScanner(registry, false, this.environment, this.resourceLoader) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) {
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }
}
复制代码

核心的Bean注册都交给了Spring,即代码 super.doScan(basePackages);

注意 getScanner()方法中获取了 ClassPathBeanDefinitionScanner类,并重写了isCandidateComponent 方法,isCandidateComponent是否成立的条件是beanDefinitions对应的类是top class或者nested class且不是注解

测试

单Bean注入

@Handle
public class TestBean {

    private  Integer i;

    public TestBean() {
        this.i = 2;
        System.out.println("单Bean 注册");
    }

    public Integer getI() {
        return i;
    }
}
复制代码

依赖注入

@Handle
public class TestABean {

    private TestBean testBean;

    public TestABean(TestBean testBean) {
        System.out.println("依赖Bean加载");
        this.testBean = testBean;
        System.out.println(this.testBean.getI());

    }


}
复制代码

接口注入

public interface TestInterface {
}
复制代码
@Handle
public class TestInterfaceImpl implements TestInterface{

    public TestInterfaceImpl() {
        System.out.println("测试接口实现");
    }
}
复制代码

运行结果

@SpringBootApplication
@EnableHandle
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
复制代码

image-20220119135743135

可以看到我们的Bean都是正常的完成了注册

参考

博客

spring-cloud-openfeign

觉得文章不错欢迎关注公众号:小奏技术

Guess you like

Origin juejin.im/post/7055105879355424776