借助ImportBeanDefinitionRegistrar接口实现bean的动态注入

ImportBeanDefinitionRegistrar

spring官方就是用这种方式,实现了@Component@Service等注解的动态注入机制。定义一个ImportBeanDefinitionRegistrar的实现类,然后在有@Configuration注解的配置类上使用@Import导入

这里我们用一个简单的例子,我们需要实现类似@Componet的功能,添加了@Mapper注解的类会被自动加入到spring容器中。

首先创建一个@Mapper注解,再新建一个CountryMapper类,使用该Mapper注解。

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
public @interface Mapper {
}

@Mapper
public class CountryMapper {
}

创建MapperAutoConfiguredMyBatisRegistrar实现ImportBeanDefinitionRegistrar,同时可以继承一些Aware接口,获得spring的一些数据

  • BeanFactoryAware
  • ResourceLoaderAware
  • EnvironmentAware
public class MapperAutoConfiguredMyBatisRegistrar
        implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanFactoryAware
{

    private ResourceLoader resourceLoader;

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        
    }

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

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

实现registerBeanDefinitions方法。但是有一个问题,我们并不知道需要register哪些bean。这里我们还需要借助一个类ClassPathBeanDefinitionScanner,也就是扫描器,通过扫描器获取我们需要注册的bean。先简单看一下spring源码中的的定义

/**
 * A bean definition scanner that detects bean candidates on the classpath,
 * registering corresponding bean definitions with a given registry ({@code BeanFactory}
 * or {@code ApplicationContext}).
 *

需要继承ClassPathBeanDefinitionScanner,扫描使用@Mapper的注解的类,ClassPathBeanDefinitionScanner又继承ClassPathScanningCandidateComponentProvider类,ClassPathScanningCandidateComponentProvider中有两个TypeFilter集合,includeFilters、excludeFilters。满足任意includeFilters会被加载,同样的满足任意excludeFilters不会被加载。看下具体实现:

@Slf4j
public class MyClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner{

    public MyClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
    }

    protected void registerFilters() {
        addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
    }

    @Override
    protected Set doScan(String... basePackages) {
        return super.doScan(basePackages);
    }
}

核心代码就是registerFilters()方法,然后在我们的ImportBeanDefinitionRegistrar实现类中调用

 @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        MyClassPathBeanDefinitionScanner scanner = new MyClassPathBeanDefinitionScanner(registry, false);
        scanner.setResourceLoader(resourceLoader);
        scanner.registerFilters();
        scanner.doScan("com.faderw.school.domain");
    }

测试

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = {MapperAutoConfig.class})
public class DemoApplicationTests {

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    CountryMapper countryMapper;

    @Test
    public void contextLoads() {
        System.out.println(countryMapper.getClass());
    }

结果

class com.faderw.school.domain.CountryMapper

最后

这里我们的CountryMapper是class类型并不是interface,要想实现Mybatis的接口注入,需要使用动态代理创建bean,这里只是一个简单的抛砖引玉。

你可能感兴趣的:(借助ImportBeanDefinitionRegistrar接口实现bean的动态注入)