前言 在认识BeanDefinitionRegistryPostProcessor
和 ClassPathBeanDefinitionScanner
之前,需要说明一下为什么会有这篇文章?动态注册Bean
?日常开发中我们有太多方式来定义注册Bean
实例到Spring
容器中:
通过xml的方式配置注册
通过@Component
以及其派生注解注册
通过@Bean
注册
…
那为什么还需要去动态注册Bean
呢?就算需要动态注册Bean
,完全可以不使用上诉方式——比如使用 @Conditional
系列条件注解(当符合条件的时候才会注册Bean
),@Profile
在指定的环境下才会被注册( @Conditional
的一种)等等,这里我们就不往下展开了。
虽然标题为Bean的动态注册
,但是日常开发过程中我们完全可以使用 @Conditional
家族的条件注解,足以满足绝大多数场景。其实写这篇文章主要是因为最近在看 Mybatis
整合 Spring
(mybatis-spring
模块),了解Mapper
是如何被作为Bean
被注册到Ioc
容器中后的一些感想,以博客的方式呈现出来,在此特别说明一下。
接下来的内容主要是简化 Mybatis
整合 Spring
的例子来介绍Bean
的动态注册
引出问题 我们先通过 Mybatis 官网 上的一段代码回顾其单独使用时步骤:
1 2 3 4 5 6 7 String resource = "org/mybatis/example/mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); try (SqlSession session = sqlSessionFactory.openSession()) { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(101 ); }
熟悉Mybatis
的同学应该都知道,这里的BlogMapper
是一个接口,并且我们没有定义其实现类,当我们执行BlogMapper mapper = session.getMapper(BlogMapper.class)
的时候,返回的对象其实是一个动态代理类,具体细节这里先不做讲解,有兴趣可以阅读 tiny-frameworks 中的tiny-mybatis
模块。
当我们整合Spring
后,上诉的对象管理可以移交给Spring
,我们只需注入所需的 Mapper
:
1 2 3 4 5 6 7 8 9 10 @Service public class BlogService { @Autowired private BlogMapper mapper; public Blog selectBlog (Integer blogId) { return mapper.selectBlog(101 ); } }
能通过@Autowired
的方法注入 Bean
,说明整合Spring
后,Mapper
被注册到 Ioc
容器中,为了展示这个过程,接下来将通过一个小小的 Demo
去模拟 Mapper
的动态注册的过程。
Mapper 动态注册 定义Mapper 首先我们需要提供一个测试接口 TestMapper
:
1 2 3 4 5 6 public interface TestMapper { String name () ; String echo () ; }
提供FactoryBean 因为 BeanDefinition
需要设置一个BeanClass
, 这里我们需要额外提供一个FactoryBean
,用于动态生成其代理类,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Slf 4jpublic class TinyMapperFactoryBean <T > implements FactoryBean <T > { private Class<?> interfaceClass; public TinyMapperFactoryBean () { } public TinyMapperFactoryBean (Class<?> interfaceClass) { this .interfaceClass = interfaceClass; } @SuppressWarnings ("unchecked" ) @Override public T getObject () throws Exception { return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, (proxy, method, args) -> { if (method.getName().equals("toString" )) { return interfaceClass.getSimpleName(); } return "proxy: " + method.getName(); }); } @Override public Class<?> getObjectType() { return interfaceClass; } }
自定义Scanner 我们可以通过 BeanDefinitionRegistry#registerBeanDefinition
来注册BeanDefinition
, 也可以配合 ClassPathBeanDefinitionScanner
来实现自动扫描注册(根据一定的规则去扫描指定路径下的Class
来作为候选BeanDefinition
),通过继承ClassPathBeanDefinitionScanner
来扩展自己的 Scanner
,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 @Slf 4jpublic class TinyMapperScanner extends ClassPathBeanDefinitionScanner { private Class<? extends Annotation> annotationClass; public TinyMapperScanner (BeanDefinitionRegistry registry) { super (registry); } public void setAnnotationClass (Class<? extends Annotation> annotationClass) { this .annotationClass = annotationClass; } @Override protected Set<BeanDefinitionHolder> doScan (String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super .doScan(basePackages); if (beanDefinitions.isEmpty()) { log.warn("No target mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration." ); } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; } private void processBeanDefinitions (Set<BeanDefinitionHolder> beanDefinitions) { beanDefinitions.forEach(holder -> { GenericBeanDefinition beanDefinition = (GenericBeanDefinition) holder.getBeanDefinition(); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName()); beanDefinition.setBeanClass(TinyMapperFactoryBean.class); beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); log.info("==> {} process finished" , beanDefinition.getBeanClassName()); }); } public void registerFilters () { boolean acceptAllInterfaces = true ; if (this .annotationClass != null ) { addIncludeFilter(new AnnotationTypeFilter(this .annotationClass)); acceptAllInterfaces = false ; } if (acceptAllInterfaces) { addIncludeFilter((metadataReader, metadataReaderFactory) -> true ); } addExcludeFilter((metadataReader, metadataReaderFactory) -> { String className = metadataReader.getClassMetadata().getClassName(); return className.endsWith("package-info" ); }); } @Override protected boolean isCandidateComponent (AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); } }
这里需要说明一下:
通过 TinyMapperScanner#registerFilters
, 我可以注册一些过滤器,上诉实现是复制于mybatis
的 ClassPathMapperScanner#registerFilters
。
当调用 ClassPathBeanDefinitionScanner#doScan
的时候,默认的 ClassPathScanningCandidateComponentProvider#isCandidateComponent(AnnotatedBeanDefinition)
会过滤掉接口,我们需要重写这个方法的实现,以确保返回Mapper
接口的 BeanDefinition
。
BeanDefinitionRegistry 我们选择自定义的 TinyMapperScanner
来实现 BeanDefinition
自动扫描注册,通过实现 BeanDefinitionRegistryPostProcessor
接口来获取 BeanDefinitionRegistry
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Component public class TinyMapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry (BeanDefinitionRegistry registry) throws BeansException { TinyMapperScanner scanner = new TinyMapperScanner(registry); scanner.setAnnotationClass(null ); scanner.registerFilters(); scanner.doScan("com.github.byference.extensions.registrar.mapper" ); } @Override public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
测试 最后我们可以写一个单元测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 @RunWith (SpringRunner.class)@SpringBootTest public class RegistrarApplicationTests { @Autowired private TestMapper testMapper; @Test public void testMapperTest () { assert "proxy: name" .equals(testMapper.name()); assert "proxy: echo" .equals(testMapper.echo()); } }
总结 通过了解Bean
的动态注册,能进一步的了解Spring
的一些运行机制,以及其他框架整合Spring
的一些实现方式,做到举一反三——例如各大 RPC
框架整合Spring
的过程~