spring ioc的包的扫描(基于注解)
一、包扫描初始步骤源码分析
AnnotationConfigApplicationContext aac =new AnnotationConfigApplicationContext("com.mydemo");AnnotationConfigApplicationContext可以实现基于Java的配置类(包括各种注解)加载Spring的应用上下文。避免使用application.xml进行配置。相比XML配置,更加便捷。
已GenericApplicationContext——通用应用上下文,内部持有一个DefaultListableBeanFactory实例,这个类实现了BeanDefinitionRegistry接口,可以在它身上使用任意的bean definition读取器。典型的使用案例是:通过BeanFactoryRegistry接口注册bean definitions,然后调用refresh()方法来初始化那些带有应用上下文语义(ApplicationContextAware)的bean,自动探测(BeanFactoryPostProcessor)等。
BeanDefinitionRegistry——用于持有像RootBeanDefinition和 ChildBeanDefinition实例的bean definitions的注册表接口。DefaultListableBeanFactory实现了这个接口,因此可以通过相应的方法向beanFactory里面注册bean。GenericApplicationContext内置一个DefaultListableBeanFactory实例,它对这个接口的实现实际上是通过调用这个实例的相应方法实现的。
AbstractApplicationContext——ApplicationContext接口的抽象实现,没有强制规定配置的存储类型,仅仅实现了通用的上下文功能。这个实现用到了模板方法设计模式,需要具体的子类来实现其抽象方法。自动通过registerBeanPostProcessors()方法注册BeanFactoryPostProcessor, BeanPostProcessor和ApplicationListener的实例用来探测bean factory里的特殊bean
AnnotationConfigRegistry——注解配置注册表。用于注解配置应用上下文的通用接口,拥有一个注册配置类和扫描配置类的方法。
1.AnnotatedBeanDefinitionReader——BeanDefinition解析器用来解析带注解的bean
2.ClassPathBeanDefinitionScanner——bean的扫描器,用来扫描类
3.注册解析传入的配置类(使用类配置的方式进行解析)
4.调用容器的refresh方法初始化容器
这里我们用的是最后一种构造函数,即传入一个包路径。
这里面最主要的是registerDefaultFilters()方法,初始化spring扫描默认过滤规则,对应@ComponentScan注解,如果没有自定义规则,就初始化默认过滤规则。这里调用的是ClassPathScanningCandidateComponentProvider类中的registerDefaultFilters()方法如下:
//向容器注册过滤规则 @SuppressWarnings("unchecked") protected void registerDefaultFilters() {//向要包含的过滤规则中添加@Component注解类//@Service和@Controller都是Component,因为这些注解都添加了@Component注解this.includeFilters.add(new AnnotationTypeFilter(Component.class));//获取当前类的类加载器ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();try {//向要包含的过滤规则添加JavaEE6的@ManagedBean注解this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.}try {//向要包含的过滤规则添加@Named注解this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-330 API not available - simply skip.} }两个关键变量:
//includeFilters表示要包含的注解,即只有包含includeFilters中的注解,才会被扫描 private final List<TypeFilter> includeFilters = new LinkedList<>(); // excludeFilters表示要排除的注解,即包含excludeFilters中的注解不会被扫描 private final List<TypeFilter> excludeFilters = new LinkedList<>();在这个方法中,includeFilters集合中添加了@Component、JavaEE6的@ManagedBean和JSR-330的@Named注解 而excludeFilters集合没做任何变动,即没有要排除的注解
总结:
所以默认规则就是,只要包含了@Component、JavaEE6的@ManagedBean和JSR-330的@Named这3个注解中的任意一个,就会被扫描
二、包扫描的具体过程
先看以下源码
AnnotationConfigApplicationContext类:
上文我们分析了this()方法,会去初始化AnnotatedBeanDefinitionReader读取器(BeanDefinition解析器用来解析带注解的bean)和ClassPathBeanDefinitionScanner扫描器(bean的扫描器 用来扫描类),并初始化扫描过滤规则。此次我们跟踪scan(basePackages)方法:
一直跟踪下去,发现调用了ClassPathBeanDefinitionScanner类中的scan()方法
可以看到主要是doScan(basePackages)方法实现了扫描的逻辑,我们继续跟踪进去看下:
//类路径Bean定义扫描器扫描给定包及其子包protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");//创建一个集合,存放扫描到Bean定义的封装类Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();//遍历扫描所有给定的包for (String basePackage : basePackages) {//调用父类ClassPathScanningCandidateComponentProvider的方法//扫描给定类路径,获取符合条件的Bean定义Set<BeanDefinition> candidates = findCandidateComponents(basePackage);//遍历扫描到的Beanfor (BeanDefinition candidate : candidates) {//获取@Scope注解的值,即获取Bean的作用域ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);//为Bean设置作用域 candidate.setScope(scopeMetadata.getScopeName());//为Bean生成名称String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);//如果扫描到的Bean不是Spring的注解Bean,则为Bean设置默认值,//设置Bean的自动依赖注入装配属性等if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}//如果扫描到的Bean是Spring的注解Bean,则处理其通用的Spring注解if (candidate instanceof AnnotatedBeanDefinition) {//处理注解Bean中通用的注解,在分析注解Bean定义类读取器时已经分析过AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//根据Bean名称检查指定的Bean是否需要在容器中注册,或者在容器中冲突if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);//根据注解中配置的作用域,为Bean应用相应的代理模式definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);//向容器注册扫描到的BeanregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}这一大段代码基本上就是spring扫描识别注解,并注册Bean到IOC容器中的代码。在第10行有一个findCandidateComponents(basePackage)方法,这个方法里就是具体的扫描逻辑。
ClassPathScanningCandidateComponentProvider类:
有一个if判断,默认走else里scanCandidateComponents(basePackage)方法。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {//补全扫描路径,扫描所有.class文件 classpath*:com/mydemo/**/*.classString packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;//定位资源Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}if (resource.isReadable()) {try {//通过ASM获取class元数据,并封装在MetadataReader元数据读取器中MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);//判断该类是否符合@CompoentScan的过滤规则//过滤匹配排除excludeFilters排除过滤器(可以没有),包含includeFilter中的包含过滤器(至少包含一个)。if (isCandidateComponent(metadataReader)) {//把元数据转化为 BeanDefinitionScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);sbd.setSource(resource);//判断是否是合格的bean定义if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}//加入到集合中candidates.add(sbd);}else {//不合格 不是顶级类、具体类if (debugEnabled) {logger.debug("Ignored because not a concrete top-level class: " + resource);}}}else {//不符@CompoentScan过滤规则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);}}}}catch (IOException ex) {throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);}return candidates;}这里就是主要的扫描逻辑,代码中的注释已经说的很清楚了。
主要过程:
1.根据包路径,扫描所有.class文件
2.根据包路径,生成.class对应的Resource对象
3.通过ASM获取class元数据,并封装在MetadataReader元数据读取器中
4.判断该类是否符合过滤规则
5.判断该类是否为独立的类、具体的类
6.加入到集合中
详细看下过滤的方法 isCandidateComponent(metadataReader)
//判断元信息读取器读取的类是否符合容器定义的注解过滤规则//@CompoentScan的过滤规则支持5种 (注解、类、正则、aop、自定义)protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {//如果读取的类的注解在排除注解过滤规则中,返回falsefor (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return false;}}//如果读取的类的注解在包含的注解的过滤规则中,则返回turefor (TypeFilter tf : this.includeFilters) {//判断当前类的注解是否match规则if (tf.match(metadataReader, getMetadataReaderFactory())) {//是否有@Conditional注解,进行相关处理return isConditionMatch(metadataReader);}}//如果读取的类的注解既不在排除规则,也不在包含规则中,则返回falsereturn false;}接着跟踪 tf.match()方法AbstractTypeHierarchyTraversingFilter类
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException {// This method optimizes avoiding unnecessary creation of ClassReaders// as well as visiting over those readers.//检查当前类的注解是否符合规律规则if (matchSelf(metadataReader)) {return true;}//check 类名是否符合规则ClassMetadata metadata = metadataReader.getClassMetadata();if (matchClassName(metadata.getClassName())) {return true;}//如果有继承父类if (this.considerInherited) {String superClassName = metadata.getSuperClassName();if (superClassName != null) {// Optimization to avoid creating ClassReader for super class.Boolean superClassMatch = matchSuperClass(superClassName);if (superClassMatch != null) {if (superClassMatch.booleanValue()) {return true;}}else {// Need to read super class to determine a match...try {if (match(metadata.getSuperClassName(), metadataReaderFactory)) {return true;}}catch (IOException ex) {logger.debug("Could not read super class [" + metadata.getSuperClassName() +"] of type-filtered class [" + metadata.getClassName() + "]");}}}}//如果有实现接口if (this.considerInterfaces) {for (String ifc : metadata.getInterfaceNames()) {// Optimization to avoid creating ClassReader for super classBoolean interfaceMatch = matchInterface(ifc);if (interfaceMatch != null) {if (interfaceMatch.booleanValue()) {return true;}}else {// Need to read interface to determine a match...try {if (match(ifc, metadataReaderFactory)) {return true;}}catch (IOException ex) {logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" +metadata.getClassName() + "]");}}}}return false;}这里面最主要的是 matchSelf(metadataReader) 方法 AnnotationTypeFilter类
protected boolean matchSelf(MetadataReader metadataReader) {//获取注解元数据AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();//check 注解及其派生注解中是否包含@Component//获取当前类的注解 metadata.hasAnnotation @Controller//获取当前类的注解及其派生注解 metadata.hasAnnotation @Controller包含的@Component\@Documented等等return metadata.hasAnnotation(this.annotationType.getName()) ||(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));}在这段代码中,可以解决我们之前的疑惑“Spring是怎么发现@Configuration、@Controller、@Service这些注解修饰的类的?”
原来@Configuration、@Controller、@Service这些注解其实都是@Component的派生注解,我们看这些注解的代码会发现,都有@Component注解修饰。而spring通过metadata.hasMetaAnnotation()方法获取到这些注解包含@Component,所以都可以扫描到。
然后我们再看回 scanCandidateComponents(basePackage)方法,接下来有一个 isCandidateComponent(sbd)方法,如下:
至此,ClassPathBeanDefinitionScanner类中的doScan(basePackages)方法中的findCandidateComponents(basePackage)方法已经结束了,包扫描也结束了,已经把扫描到的类存入到了集合中,结下来就是解析注册Bean的过程了。
总结
通过这一节,我们可以回答之前的一些问题了:
- Spring是怎么发现@Bean、@Controller、@Service这些注解修饰的类的?
通过 matchSelf(metadataReader)方法,判断这些注解中是否包含@Component - @CompoentScan注解是怎么起作用的?
通过 isCandidateComponent(metadataReader)方法过滤
文章转自1:
文章转自2:
总结
以上是生活随笔为你收集整理的spring ioc的包的扫描(基于注解)的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: Docker安装zookeeper 单节
- 下一篇: 大厂产品是如何做行业调研和规划的?附汇报