Spring Extensions (一) Bean的动态注册

Spring Extensions (一) Bean的动态注册

前言

在认识BeanDefinitionRegistryPostProcessorClassPathBeanDefinitionScanner 之前,需要说明一下为什么会有这篇文章?动态注册Bean ?日常开发中我们有太多方式来定义注册Bean实例到Spring容器中:

  1. 通过xml的方式配置注册
  2. 通过@Component 以及其派生注解注册
  3. 通过@Bean注册

那为什么还需要去动态注册Bean 呢?就算需要动态注册Bean,完全可以不使用上诉方式——比如使用 @Conditional 系列条件注解(当符合条件的时候才会注册Bean),@Profile 在指定的环境下才会被注册( @Conditional 的一种)等等,这里我们就不往下展开了。

虽然标题为Bean的动态注册 ,但是日常开发过程中我们完全可以使用 @Conditional 家族的条件注解,足以满足绝大多数场景。其实写这篇文章主要是因为最近在看 Mybatis 整合 Springmybatis-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
@Slf4j
public 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();
}
// 如果返回值为 String 则返回方法名称
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
@Slf4j
public 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());
});
}


/**
* copy form {@code org.mybatis.spring.mapper.ClassPathMapperScanner#registerFilters}
*/
public void registerFilters() {
boolean acceptAllInterfaces = true;

// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}

if (acceptAllInterfaces) {
// default include filter that accepts all classes
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}

// exclude package-info.java
addExcludeFilter((metadataReader, metadataReaderFactory) -> {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
});
}


/**
* {@inheritDoc}
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}

这里需要说明一下:

  1. 通过 TinyMapperScanner#registerFilters, 我可以注册一些过滤器,上诉实现是复制于mybatisClassPathMapperScanner#registerFilters
  2. 当调用 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的过程~

# Spring
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×