Spring源码阅读(一):Spring是如何扫描某个包下面所有的类
Spring扫描注解的功能 我们知道在Spring中可以使用注解声明Bean,可以让我们不用再去配置繁琐的xml文件,确实让我们的开发简便不少。只要在Spring xml里配置如下,就开启了这项功能。 <context:component-scan base-package="com.zhaoyanblog" /> Spring就会自动扫描该包下面所有打了Spring注解的类,帮你初始化病注册到Spring容器中。 @Service public class UserController { @Autowired private UserDao userDao; //TODO } 上述行为,就和在xml里进行下面的配置是等价的。 <bean class="com.zhaoyanblog.UserController"> <property name="userDao" ref="userDao" /> </bean> 那么问题来了,Spring是怎么扫描到com.zhaoyanblog包下面的所有带注解的类的呢? context:component-scan标签的处理者 要弄清楚为什么在xml里配置了context:component-scan就可以实现这样的功能,就要现找到这个标签的处理者。我们很容易联想到Spring解析xml的基本原理,就是遇到这个标签以后,交给一个类处理,这个类扫描包下带注解的类,初始化成对象。我们就是要找到这个关键的类。 Spring对Xml的解析功能后面阅读,这里先简要描述。Spring的jar包里有两个重要的配置文件:spring.schemas和spring.handlers,在META-INF目录下。 spring.schemas记录了xml的每个命名空间,对应的Schema校验XSD文件在哪个目录。 http\://www.springframework.org/schema/context/spring-context-4.2.xsd=org/springframework/context/config/spring-context.xsd http\://www.springframework.org/schema/context/spring-context-4.3.xsd=org/springframework/context/config/spring-context.xsd http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context.xsd spring.handlers里配置了每个命名空间的标签都由哪个类处理 http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler 我们可以看到context这个命名空间由org.springframework.context.config.ContextNamespaceHandler这个类处理。打开这个类 public class ContextNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); } } 这个类为每个标签都给出了一个处理类,component-scan的处理类是ComponentScanBeanDefinitionParser 继续打开ComponentScanBeanDefinitionParser private static final String BASE_PACKAGE_ATTRIBUTE = "base-package"; public BeanDefinition parse(Element element, ParserContext parserContext) { String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE); basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage); String[] basePackages = StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); // Actually scan for bean definitions and register them. ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages); registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null; } 看到这里整明白了,扫描某个包下面的所有类的工作,就是ClassPathBeanDefinitionScanner干的。入口是doScan方法。 查找所有的包路径 ClassPathBeanDefinitionScanner有很多细节,比如可以设置class的filter, 设置classloader等等,我们先关注最主要的功能,就是怎么找到一个包下面所有的类的。 通过调用关系,一路找下去 doScan->findCandidateComponents(super)->scanCandidateComponents(super) private Set<BeanDefinition> scanCandidateComponents(String basePackage) { Set<BeanDefinition> candidates = new LinkedHashSet<>(); try { String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 我们发现,原来Spring是在classpath下面,通过查找所有classpath*:com/zhaoyanblog/**/*.class的文件来实现的啊。 ...