highlight: arduino-light
Mybatis自定义扫描器
上一篇详细讲解了spring的扫描器ClassPathBeanDefinitionScanner,本篇我们我们将模拟mybatis如何通过spring完成Mapper扫描,讲解如何通过spring编写自定义扫描器。
既然ClassPathBeanDefinitionScanner完成了spring的扫描功能,我们完全可以继承这个类来达到创建自定义扫描器的目的。
自定义CustomScanner
java public class CustomScanner extends ClassPathBeanDefinitionScanner { public CustomScanner(BeanDefinitionRegistry registry) { super(registry); } @Override public void addIncludeFilter(TypeFilter includeFilter) { super.addIncludeFilter(includeFilter); } }
上一篇我们讲过ClassPathBeanDefinitionScanner可以通过includeFilters来过滤符合条件的业务类。spring启动时会默认生成一个ClassPathBeanDefinitionScanner对象,该对象在初始化时会注册默认的过滤器。
```java protected void registerDefaultFilters() { this.includeFilters.add(new AnnotationTypeFilter(Component.class)); ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader(); try { this.includeFilters.add (new AnnotationTypeFilter (ClassUtils.forName("javax.annotation.ManagedBean", cl), false)); this.logger.trace ("JSR-250 'javax.annotation.ManagedBean' found"); } catch (ClassNotFoundException var4) { }
try {
this.includeFilters.add(new AnnotationTypeFilter
(ClassUtils.forName("javax.inject.Named", cl),
false));
this.logger.trace
("JSR-330 'javax.inject.Named' annotation found");
} catch (ClassNotFoundException var3) {
}
} ```
ClassPathBeanDefinitionScanner注册的过滤器类型是AnnotationTypeFilter,也就是说,我们只要调用addIncludeFilter添加自定义的AnnotationTypeFilter就行了,其余的交给ClassPathBeanDefinitionScanner来完成。
自定义注解MyAnnotation
OK,我们先自定义一个注解:
java public @interface MyAnnotation { }
再自定义一些类
```java
@MyAnnotation public class MyClass { }
@MyAnnotation public class MyClass2 { } ```
自定义AnnotationTypeFilter
```java public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new
AnnotationConfigApplicationContext(); //注册配置类 context.register(Config.class); //实例化自定义扫描器 CustomScanner customScanner = new CustomScanner(context); //为自定义扫描器增加包含过滤器 customScanner.addIncludeFilter(new
AnnotationTypeFilter(MyAnnotation.class)); //将带有MyAnnotation注解的类扫描到springIOC容器中,并返回扫描的个数 int num = customScanner.scan("com.scan");
System.out.println("扫描的个数:"+num);
context.refresh();
System.out.println(context.getBean(MyClass.class));
}
}
//打印结果: //扫描的个数:2 //com.scan.MyClass@1199fe66 ```
PS:spring只能扫描实体类,抽象类和接口是不能扫描的。
自定义扫描器重写isCandidateComponent
ClassPathScanningCandidateComponentProvider#isCandidateComponent
```java if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class:"+resource); } //返回BeanDefinition 注册到 BeanFactory candidates.add(sbd); }
/** * metadata.isIndependent():不是内部类或者是静态内部类 * metadata.isConcrete():不能是接口或抽象类 * metadata.isAbstract():是抽象类、 * metadata.hasAnnotatedMethods(Lookup.class.getName()):有lookUp注解标注 * 总结就是需要满足以下两个条件: * 1. 不能是内部类或者是静态内部类 * 2. 不能是接口或者抽象类,如果是抽象类,则需要LookUp注解标注 */ protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { AnnotationMetadata metadata = beanDefinition.getMetadata(); //1.普通的实体类 //2.带@Lookup注解的方法的抽象类 //上面2种情况返回true return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); } ```
isCandidateComponent这个方法判断
如果是实体类 返回true
如果是抽象类,但是有抽象方法被@Lookup注解注释返回true。
也就是说如果你是接口,isCandidateComponent返回的是false,candidates.add(sbd);
就不会执行了。自然添加不到IOC容器中。
mybatis就是修改了这个方法,让Mapper接口能扫描到IOC容器中的,我们先模拟.
自定义注解@MyAnnotation加到接口上。
java @MyAnnotation public interface MyInterFace { @Select("select * from table") public void select(); }
自定义扫描器
```java public class CustomScanner extends ClassPathBeanDefinitionScanner { public CustomScanner(BeanDefinitionRegistry registry) { super(registry); }
@Override
public void addIncludeFilter(TypeFilter includeFilter) {
super.addIncludeFilter(includeFilter);
}
@Override
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
//如果是接口,则返回true,会添加到IOC容器中去
return beanDefinition.getMetadata().isInterface();
}
} ```
测试
```java public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); //注册配置类 context.register(Config.class); CustomScanner customScanner = new CustomScanner(context); customScanner.addIncludeFilter(new
AnnotationTypeFilter(MyAnnotation.class)); int num = customScanner.scan("com.scan"); System.out.println("扫描的个数:"+num); context.refresh(); } } //打印结果 //扫描的个数:1
```
然后,spring会根据MyInterFace生成一个动态代理类,并通过反射拿到你所有的方法,拿到方法后再通过反射拿到你的注解信息,实际上,mybatis就是这么做的,不信看mybatis源码:
java // mybatis的自定义扫描类ClassPathMapperScanner.java protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { //接口 && 不是内部类或者是静态内部类 return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); }