Spring类扫描器基础实现,自定义类扫描器和对象注入
Spring的类扫描器
Spring Component Scan看起来很方便,但内部实现还是相当暴力的,可能也是java实在没提供什么特别方便的api,基本原理就是使用ClassLoader的getResources方法直接获取对应包下的所有class文件,再使用MetadataReaderFactory
读取类信息,主要是获取className,之后一些反射处理就可以继续进行了。
基本示例:
public class ClasspathScanner implements EnvironmentAware{
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
protected final Logger logger = LoggerFactory.getLogger(getClass());
private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
private Environment environment;
private MetadataReaderFactory metadataReaderFactory;
//lazy init
private final MetadataReaderFactory getMetadataReaderFactory() {
if (this.metadataReaderFactory == null) {
this.metadataReaderFactory = new CachingMetadataReaderFactory();
}
return this.metadataReaderFactory;
}
//将package转换为classpath resource path,基本来说就是.转换为/
private String resolveBasePackage(String basePackage) {
return ClassUtils.convertClassNameToResourcePath(environment.resolveRequiredPlaceholders(basePackage));
}
private Class<?> checkClassName(String className){
try{
return Class.forName(className);
}catch(ClassNotFoundException e){
return null;
}
}
//扫描包下面的类,注意这个package是支持通配符的,通常而言*可替代一层包名,**可代替0层或多层任意的包名
private List<Class<?>> scanClasses(String basePackage) {
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
List<Class<?>> classes = new ArrayList<>();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning {}", resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
String className = metadataReader.getClassMetadata().getClassName();
Class<?> clazz;
if ((clazz = checkClassName(className)) != null) {
classes.add(clazz);
} else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: {}", resource);
}
}
} catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
} else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
return classes;
} catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
以上给了扫描类的基本案例,部分代码是直接手写的,可能存在错漏,自行调整一下吧。注意这个类使用了EnvironemntAware接口,因此需要先注册到Spring内才可以生效。通常而言是设定为Configuration类,不过还有一种常用实现,定义注解扫描器
ImportBeanDefinitionRegistrar
通过定义一个注解,然后在注解上加上@Import关联到对应的类,即可实现自动扫描并注册的功能
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Configuration
@Import(ClassPathScanner.class)
public @interface Scan{
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {}
}
这个注解关联了@Configuration,因此标注的类会被Spring识别为configuration class,同时Spring会根据@Import启动对应的注册器。当然这还要求ClassPathScanner实现ImportBeanDefinitionRegistrar
然后可以实现这个接口的registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
方法,即可获得registry对象。接下来就可以利用这个registry去注册我们想要的bean了。
BeanDefinition
扫描得到了对应的类,接下来就是对类进行筛选,无论是通过父类/接口关系还是通过注解对于反射而言都不是问题。
接下来就是如何注入Spring了。
通过上一步我们得到了BeanDefinitionRegistry
对象,接下来只需要构建一个BeanDefinition即可实现注入。Spring为我们提供了BeanDefinitionBuilder来简化BeanDefinition的构建,一个简单实现Demo:
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(SpiderRequestFactory.class)
.addConstructorArgValue("foo")
.addConstructorArgReference("spiderConfig")
.addConstructorArgValue(obj1)
.getBeanDefinition();
以上demo是通过定义构造器参数的方式来构建Bean,也可以定义autoWireProperty的方式进行依赖注入。可以看到这里支持直接的对象注入,beanName引用注入等方式。然后再注册到beanRegistry就可以了。
场景
这里的实现比较复杂了,实际上Spring能自动识别所有标注了@Component的类,包括其衍生注解。而Spring建立的注解传递机制也允许我们自己定义新的衍生注解完成扫描,并由Spring自行完成构建,远远不需要如此费事。
这种方式主要用于一些特殊场景,例如接口代理等的实现。最典型的例子要数mybatis,通过扫描Mapper接口可以自动构建接口的代理类并注册到Spring,而非直接把接口注册到Spring(interface也无法实例化),或者一些其他类似的场景。
当然要实现mybatis的方案还有一个关键技术是接口代理,这里就不做赘述了,也就是BeanDefinition构建出来的类需要代理对应的接口,并由spring识别为对应接口的实体对象而非代理类本身,这里就不做赘述了