Анализ исходного кода ComponentScan
Следующая версия анализа исходного кода Spring 5.2.5 выпуска
Понимание @Component
@Component
В качестве универсального компонента, размещенного в контейнере Spring, любой@Component
Аннотированные компоненты — это все объекты, сканируемые компонентом.
Подобные компоненты, такие как@Repository
,@Service
или использовать@Component
Аннотации как пользовательские аннотации.
//@see org.springframework.context.annotation.ClassPathBeanDefinitionScanner
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
String value() default "";
}
1. Воспользуйтесь преимуществами@Repository
пользовательская аннотация
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//@Component
@Repository
public @interface StringRepository {
String value() default "";
}
2. Напишите тестовый классNameRepository
, добавьте эту аннотацию
@StringRepository("chineseNameRepository")
public class NameRepository {
public List<String> findAll() {
return Arrays.asList("张三", "李四", "王二麻子");
}
}
3. ТестNameRepository
Может ли класс быть загружен контейнером Spring
Создайте тестовый классComponentBootStrap
, код показывает, как показано ниже
@Configuration
@ComponentScan
public class ComponentBootStrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentBootStrap.class);
// context.register(ComponentBootStrap.class);
// context.refresh();
NameRepository nameRepository = context.getBean(NameRepository.class);
System.out.println("nameRepository.findAll() = " + nameRepository.findAll());
}
}
Совет: КлассыNameRepository
с классом начальной загрузкиComponentBootStrap
Его нужно положить под сумку, чтобы быть@ComponentScan
Отсканируйте, чтобы загрузить.
Результат:nameRepository.findAll() = [张三, 李四, 王二麻子]
, что объясняет пользовательский класс аннотаций@StringRepository
Есть и похожие@Component
функция.
Вывод: пока ваши аннотации содержат@Component
, может управляться контейнером Spring. потому что@Component
Это мета-аннотация, и вы также можете комбинировать мета-аннотации для создания комбинированных аннотаций, таких как@RestController
к@Controller
а также@ResponseBody
состоит из.Официальный сайт beans-meta-annotations
Приведенный выше пример взят из «Идеи программирования Spring Boot» брата Ма,NameRepository
Аннотации не являются наследованием@Component
, но эффект является наследованием, заимствуя определение Сяо Ма этой аннотации режима как@Component
вывод. Затем исследуйте@Component
Принцип деривации.
Исследуйте источник
Пока аннотация содержит@Component
, он будет просканирован Spring и зарегистрирован. Почему это так? Это как-то связано с методом сканирования?
Spring может сканировать bean-компоненты двумя способами: один — настроить тег NameRepository
Имя пакета, одно из них — аннотация@ComponentScan
, если вы не заполните значение value, все классы в пакете класса аннотации сканируются по умолчанию.
Первый способ настройки тегов здесь пока обсуждать не буду, и позже я напишу специальную статью для его анализа.
Мы сосредоточимся на аннотациях здесь@ComponentScan
Способ.
Первый взгляд@ComponentScan
исходный код
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
// 这里默认为true,说明使用默认的过滤器,下文会详细说这里
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
В этой аннотации много атрибутов, я не буду их здесь приводить, и вам интересно проверить информацию самостоятельно. есть примечание@AliasFor("value")
Он появляется часто. Если читателям интересно, я напишу отдельную статью, чтобы представить это в следующий раз. Не по теме, давайте продолжим исследовать, где Spring сканирует? Увидев это, многие люди будут сбиты с толку.Мой метод, как правило, заключается в том, чтобы сначала использовать поиск использования идеи, сочетание клавиш CTRL + G, чтобы найти место для вызова аннотации, как показано ниже.
Затем поищите похожие в использовании в .class Здесь мы находимся вorg.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
Анализ был найден в этом методе@ComponentScan
Место. Но взгляните на этот класс, чувак, это не просто разбор того, о чем мы говорим.@ComponentScan
, также разбор@Import
,@Bean
,@PropertySources
,@ComponentScans
,@ImportResource
Подождите, в следующий раз я объясню этот класс сразу.
Давайте сосредоточимся на основном кодеConfigurationClassParser#doProcessConfigurationClass
:
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter){
···
// Process any @ComponentScan annotations
// 获取ComponentScans,ComponentScan注解里面所有的属性
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// 核心代码parse:下面重点解析
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
···
ComponentScanAnnotationParser#parse(AnnotationAttributes,String)
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// 这里new一个scanner对象,因为@ComponentScan注解里面的useDefaultFilters默认为true,所以这里传入的值为true
// 也就是在构造器中使用了默认的过滤器,下文会介绍
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
// 上面都是将注解里面的属性赋值给scanner,然后解析方法委托给ClassPathBeanDefinitionScanner#doScan
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 这里的basePackages就是之前的@ScanComponent所在的包名
for (String basePackage : basePackages) {
// 找到候选组件
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 将候选组件注册为BeanDefinition
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
Сканировать путь к классам на наличие компонентов-кандидатов ClassPathScanningCandidateComponentProvider#findCandidateComponents(String)
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
// 假如这里的basePackage是com.example.learnspring,resolveBasePackage(basePackage)就把包名变成com/example/learnspring
// resolveBasePackage(basePackage)的目的是将包名里面的"."换成"/",
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + "**/*.class";
// 根据传入的路径解析成Resource
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
···省去日志
if (resource.isReadable()) {
try {
// 产生一个访问元数据的门面类MetadataReader
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 判断是否为候选组件
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
}
}
catch (Throwable ex) {
···
}
return candidates;
}
Вот введениеMetadataReader
, это фасадный класс, который получает доступ к метаданным класса через ASM, класс реализации здесьSimpleMetadataReader
, метаданныеSimpleAnnotationMetadataReadingVisitor
Генерируется ASM.
final class SimpleMetadataReader implements MetadataReader {
private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG
| ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES;
private final Resource resource;
// 数据元信息,包括className,superClassName等等,
// 如果还是不清楚,看下面类StringRepository数据元信息图
private final AnnotationMetadata annotationMetadata;
SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
// 通过SimpleAnnotationMetadataReadingVisitor来创建AnnotationMetadata,原理是ASM,对这里有兴趣的同学可以在这里打个断点,尽情调试
SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader);
getClassReader(resource).accept(visitor, PARSING_OPTIONS);
this.resource = resource;
this.annotationMetadata = visitor.getMetadata();
}
Затем оцените, является ли он компонентом-кандидатом.
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
наш классStringRepository
Он был успешно загружен Spring ранее, и все здесь должны возвращать true, поэтому сделайте здесь точку останова для отладки.excludeFilters
: исключить метаинформацию с аннотациями.includeFilters
: сопоставляет метаинформацию с аннотациями
нашел здесьincludeFilters
содержит дваAnnotationTypeFilter
, соответственно включаяComponent
а такжеManagedBean
, это потому что вregisterDefaultFilters
Фильтр по умолчанию прописан в методе.
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("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 {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
поищи где это называетсяregisterDefaultFilters
,ОбнаружитьClassPathBeanDefinitionScanner#ClassPathBeanDefinitionScanner()
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
if (useDefaultFilters) {
// 设置默认的过滤器
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(resourceLoader);
}
Наконец нашел вClassPathBeanDefinitionScanner
Фильтр по умолчанию задается в конструкторе, т.е.ComponentScanAnnotationParser#parse
Первая строка метода создаетClassPathBeanDefinitionScanner
объект, повторяя вышеизложенное.
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
У вас может слегка закружиться голова. Давайте разберемся. Мы только что представили, что суждение является компонентом-кандидатом, то есть метод match(), приведенный ниже, возвращает true. Угадывание по имени метода, если класс содержит аннотацию@Component
или@ManageBean
будет успешно совпадать. Продолжим углубленный анализ исходного кода.
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
// 核心判断逻辑
if (tf.match(metadataReader, getMetadataReaderFactory())) {
// 判断元信息是否包含@Conditional,跟条件相关的,这里省略
return isConditionMatch(metadataReader);
}
}
return false;
}
AbstractTypeHierarchyTraversingFilter#match(MetadataReader,MetadataReaderFactory)
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
// 主要讲解这个方法,下面的逻辑有兴趣的同学可以自己研究
if (matchSelf(metadataReader)) {
return true;
}
······
return false;
}
protected boolean matchSelf(MetadataReader metadataReader) {
// 获取元信息,这里是自定义注解@StringRepository信息
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
// 下面的this.annotationType.getName()是传入的org.springframework.stereotype.Component
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
AnnotationMetadata#hasMetaAnnotation
// 判断底层注解是否包含metaAnnotationName
default boolean hasMetaAnnotation(String metaAnnotationName) {
return getAnnotations().get(metaAnnotationName,
MergedAnnotation::isMetaPresent).isPresent();
}
На данный момент это уже очень ясно, есть только два метода, один из них — metadata.hasAnnotation(this.annotationType.getName()), который определяет, содержит ли класс данную аннотацию, Другим методом является metadata.hasMetaAnnotation(this.annotationType.getName()), который определяет, содержит ли базовый класс данную аннотацию, а нижний слой @StringRepository действительно содержит @Component, поэтому этот метод возвращает значение true.
isConditionMatch(MetadataReader)
Метод заключается в том, чтобы судить о том, есть ли в метаданных условная аннотация, которая здесь не представлена.Если вам интересно, см. другую мою статью.Условная сборка @Conditional анализ исходного кода
Если у вас есть сомнения или вы пишете что-то не так, вы можете прокомментировать или связаться со мной по электронной почтеcreazycoder@sina.com