开篇前,我给此文起个小标题(受老钱的《Redis深度历险》有感起的)----自研框架中那颗最核心的螺丝。
在注解驱动编程模型下,自定义注解永远避不开;在Java领域中,同样离不开Spring。相信大家都对Dubbo的@Service(高版本已变为@DubboService)、@Reference(高版本已变为@DubboReference)以及Mybatis的@Mapper注解等有所了解。但是有没有深入想过他们区区一个普通注解是怎么让spring接管这些注解所标记的Bean呢?
在注解模式下,纳入Spring管理方法
- 运用Spring提供的元注解@Component。这种方式是最暴力最傻瓜的,同时也是最简单的。但缺点也是很明显的,扩展性弱,因为它将遵循spring默认的处理方式。你能运用就只剩spring提供的那些后置处理器或者钩子方法来扩展。比如,你想有自己专门的BeanDefinition装配模式,不想按照spring默认的,这时你就要用BeanDefinitionRegistryPostProcessor这个扩展点去修改BeanDefinition元配置信息。
通俗点说,就是有一把五颜六色的豆子和一个麻袋,但在麻袋里,你要的只想要一种颜色。按照上面的来说,不管三七二十一,一把豆子放下去,再把不需要的豆子挑出来,达到麻袋里只剩你要的颜色的豆子。方法可行,但显得有点那个。为何不如一开始就只放你想要的那种豆子放到麻袋呢?这就是将要介绍的第二方式。
其实,Dubbo的PMC 小马哥也说过Dubbo最开始的做法也是这样,后面不好扩展才改换的。
- 扩展spring。自定义扫描器,自定义注册Bean逻辑等。这也是自研框架中最推荐的做法。
接下来笔者将以第二种方法以例子示范重点展开讲解。下文需要读者有一定的spring功底才能比较容易理解,比如spring的bean生命周期,spring的扩展点以及钩子方法都有一定基础的了解。
自定义注解
自定义注解@Honey,作用就是类似spring的@Controller、@Service等注解标注这是一个spring组件(Bean)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Honey {
}
第一个注解是打标记,那么就得有个在spring启动时触发扫描我们自定义注解标记的Bean的注解。我们起名@HoneyComponentScan。最关键的就是告诉从哪里扫起,此时扫描包的指定就很关键了。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HoneyComponentScanRegistrar.class)
public @interface HoneyComponentScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
此时,这个注解就可以打在项目启动类上了。细心的读者会发现这个注解里有@Import一个类,熟悉springboot的读者应该不难理解,这是springboot的自动装配中的一个机制特点。你可以@Import直接引入,也可以实现ImportSelector 接口。这里不再展开赘述。
自定义扫描注册器
怎么扫,怎么注册。这是接下来的核心流程。自定义HoneyComponentScanRegistrar。实现spring的
ImportBeanDefinitionRegistrar 扩展点。重写registerBeanDefinitions逻辑。
public class HoneyComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 从HoneyComponentScan注解解析得到包路径集合
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 构建并注册一个BeanFactoryPostProcessor后置处理器
registerHoneyAnnotationBeanPostProcessor(packagesToScan, registry);
}
private void registerHoneyAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = rootBeanDefinition(HoneyAnnotationBeanPostProcessor.class);
builder.addConstructorArgValue(packagesToScan);
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
}
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(HoneyComponentScan.class.getName()));
String[] basePackages = attributes.getStringArray("basePackages");
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
String[] value = attributes.getStringArray("value");
// Appends value array attributes
Set<String> packagesToScan = new LinkedHashSet<String>(Arrays.asList(value));
packagesToScan.addAll(Arrays.asList(basePackages));
for (Class<?> basePackageClass : basePackageClasses) {
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
}
if (packagesToScan.isEmpty()) {
return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
}
return packagesToScan;
}
}
自定义BeanFactoryPostProcessor后置处理器–HoneyAnnotationBeanPostProcessor。
实现BeanDefinitionRegistryPostProcessor,重写postProcessBeanDefinitionRegistry。它是BeanFactoryPostProcessor的子类。各种织面是各种资源的织入。在这里我们可以对即将注册的Bean的beanDefinition进行专门处理,可以加入我们各种特定的自定义业务逻辑。
像Dubbo就是在这里构建了ServiceBean,加入了一些需要暴露的属性信息。
笔者猜测Mybatis的动态代理将mapper接口与xml的sql建立映射关系也是在这里,后面研究mybatis源码再来论证一下。
public class HoneyAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
ResourceLoaderAware, BeanClassLoaderAware {
.......
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
registerServiceBeans(resolvedPackagesToScan, registry);
} else {
if (logger.isWarnEnabled()) {
logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
}
}
}
private Set<String> resolvePackagesToScan(Set<String> packagesToScan) {
Set<String> resolvedPackagesToScan = new LinkedHashSet<String>(packagesToScan.size());
for (String packageToScan : packagesToScan) {
if (StringUtils.hasText(packageToScan)) {
String resolvedPackageToScan = environment.resolvePlaceholders(packageToScan.trim());
resolvedPackagesToScan.add(resolvedPackageToScan);
}
}
return resolvedPackagesToScan;
}
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
HoneyClassPathBeanDefinitionScanner scanner =
new HoneyClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
scanner.addIncludeFilter(new AnnotationTypeFilter(Honey.class));
for (String packageToScan : packagesToScan) {
// Registers @Service Bean first
scanner.scan(packageToScan);
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
registerServiceBean(beanDefinitionHolder, registry, scanner);
}
.............
}
}
}
测试验证
建立两个测试类,含依赖注入。
@Honey
public class MyLove {
private String loveName;
private int loveAge;
}
@Honey
public class MyHoney {
@Autowired
private MyLove myLove;
private String honeyName;
private int honeyAge;
}
成功纳入spring容器管理。
- 本文就到此结束了,我们下文见,谢谢
- github: honey开源系列组件作者