Spring系列——@ComponentScan

本文介绍了Spring注解@ConfigurationScan的用法,包括它如何替代XML中的<Context:component-scan>标签,以及@ComponentScan中的userDefaultFilters、lazyInit、excludeFilters、includeFilters属性。通过源码解析,阐述了@Filter的FilterType枚举和自定义类型过滤实战,展示了如何使用excludeFilters排除Controller,使用includeFilters添加@Service,并提供了自定义TypeFilter接口的实现示例,帮助读者深入理解@ComponentScan的工作原理。
摘要由CSDN通过智能技术生成

前言

大家都了解过传统的Xml配置方式的Spring,基本上都知道Xml配置中有<Context:compent-scan>标签,它的作用是开启包扫描,把标注了@Controller、@Service、@Repository、@Component注册进Spring容器中,而Spring注解之@ComponentScan的作用就是代替此功能。

@ComponentScan源码

/*
 * Copyright 2002-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.type.filter.TypeFilter;

/**
 * Configures component scanning directives for use with @{@link Configuration} classes.
 * Provides support parallel with Spring XML's {@code <context:component-scan>} element.
 *
 * <p>Either {@link #basePackageClasses} or {@link #basePackages} (or its alias
 * {@link #value}) may be specified to define specific packages to scan. If specific
 * packages are not defined, scanning will occur from the package of the
 * class that declares this annotation.
 *
 * <p>Note that the {@code <context:component-scan>} element has an
 * {@code annotation-config} attribute; however, this annotation does not. This is because
 * in almost all cases when using {@code @ComponentScan}, default annotation config
 * processing (e.g. processing {@code @Autowired} and friends) is assumed. Furthermore,
 * when using {@link AnnotationConfigApplicationContext}, annotation config processors are
 * always registered, meaning that any attempt to disable them at the
 * {@code @ComponentScan} level would be ignored.
 *
 * <p>See {@link Configuration @Configuration}'s Javadoc for usage examples.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 3.1
 * @see Configuration
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

	/**
	 * Alias for {@link #basePackages}.
	 * <p>Allows for more concise annotation declarations if no other attributes
	 * are needed &mdash; for example, {@code @ComponentScan("org.my.pkg")}
	 * instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
	 */
	@AliasFor("basePackages")
	String[] value() default {};

	/**
	 * Base packages to scan for annotated components.
	 * <p>{@link #value} is an alias for (and mutually exclusive with) this
	 * attribute.
	 * <p>Use {@link #basePackageClasses} for a type-safe alternative to
	 * String-based package names.
	 */
	@AliasFor("value")
	String[] basePackages() default {};

	/**
	 * Type-safe alternative to {@link #basePackages} for specifying the packages
	 * to scan for annotated components. The package of each class specified will be scanned.
	 * <p>Consider creating a special no-op marker class or interface in each package
	 * that serves no purpose other than being referenced by this attribute.
	 */
	Class<?>[] basePackageClasses() default {};

	/**
	 * The {@link BeanNameGenerator} class to be used for naming detected components
	 * within the Spring container.
	 * <p>The default value of the {@link BeanNameGenerator} interface itself indicates
	 * that the scanner used to process this {@code @ComponentScan} annotation should
	 * use its inherited bean name generator, e.g. the default
	 * {@link AnnotationBeanNameGenerator} or any custom instance supplied to the
	 * application context at bootstrap time.
	 * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator)
	 * @see AnnotationBeanNameGenerator
	 * @see FullyQualifiedAnnotationBeanNameGenerator
	 */
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	/**
	 * The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components.
	 */
	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

	/**
	 * Indicates whether proxies should be generated for detected components, which may be
	 * necessary when using scopes in a proxy-style fashion.
	 * <p>The default is defer to the default behavior of the component scanner used to
	 * execute the actual scan.
	 * <p>Note that setting this attribute overrides any value set for {@link #scopeResolver}.
	 * @see ClassPathBeanDefinitionScanner#setScopedProxyMode(ScopedProxyMode)
	 */
	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	/**
	 * Controls the class files eligible for component detection.
	 * <p>Consider use of {@link #includeFilters} and {@link #excludeFilters}
	 * for a more flexible approach.
	 */
	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	/**
	 * Indicates whether automatic detection of classes annotated with {@code @Component}
	 * {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
	 */
	boolean useDefaultFilters() default true;

	/**
	 * Specifies which types are eligible for component scanning.
	 * <p>Further narrows the set of candidate components from everything in {@link #basePackages}
	 * to everything in the base packages that matches the given filter or filters.
	 * <p>Note that these filters will be applied in addition to the default filters, if specified.
	 * Any type under the specified base packages which matches a given filter will be included,
	 * even if it does not match the default filters (i.e. is not annotated with {@code @Component}).
	 * @see #resourcePattern()
	 * @see #useDefaultFilters()
	 */
	Filter[] includeFilters() default {};

	/**
	 * Specifies which types are not eligible for component scanning.
	 * @see #resourcePattern
	 */
	Filter[] excludeFilters() default {};

	/**
	 * Specify whether scanned beans should be registered for lazy initialization.
	 * <p>Default is {@code false}; switch this to {@code true} when desired.
	 * @since 4.1
	 */
	boolean lazyInit() default false;
	
	/***
	/***@Filter源码忽略
	/***
}

我们来看看使用得比较多的userDefaultFilters、lazyInit、excludeFilters、includeFilters属性。

  • userDefaultFiters:是否使用默认的过滤规则,默认为true
  • lazyInit:是否启用懒加载加载扫描到的Bean
  • excludeFiters:使用Filter,指定哪些类型不需要扫描
  • includeFiters:使用Filter,指定哪些类型需要扫描

@Filter源码

上面的ComponentScan的excludeFiters、includeFiters都使用了Filter,在这里我们再来了解一下Filter,但是在了解@Filter的使用之前,我们需要先了解FilterType枚举。

  • FilterType就是我们的过滤类型,如自定义过滤类型、注解过滤类型。
  • Filter的作用就是利用我们的多种过滤类型,指定我们怎么过滤,需要过滤哪些东西。
/**
 * Enumeration of the type filters that may be used in conjunction with
 * {@link ComponentScan @ComponentScan}.
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 2.5
 * @see ComponentScan
 * @see ComponentScan#includeFilters()
 * @see ComponentScan#excludeFilters()
 * @see org.springframework.core.type.filter.TypeFilter
 */
public enum FilterType {
	/**
	 * Filter candidates marked with a given annotation.
	 * @see org.springframework.core.type.filter.AnnotationTypeFilter
	 */
	ANNOTATION,

	/**
	 * Filter candidates assignable to a given type.
	 * @see org.springframework.core.type.filter.AssignableTypeFilter
	 */
	ASSIGNABLE_TYPE,

	/**
	 * Filter candidates matching a given AspectJ type pattern expression.
	 * @see org.springframework.core.type.filter.AspectJTypeFilter
	 */
	ASPECTJ,

	/**
	 * Filter candidates matching a given regex pattern.
	 * @see org.springframework.core.type.filter.RegexPatternTypeFilter
	 */
	REGEX,

	/** Filter candidates using a given custom
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 */
	CUSTOM
}

此枚举的作用就是定义过滤类型,但是这里我们只讲解使用频率较高注解和自定义过滤类型,其他的多种过滤类型大家可以去参考官方文档,自行摸索。

/**
 * Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters
 * include filter} or {@linkplain ComponentScan#excludeFilters exclude filter}.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
	/**
	 * The type of filter to use.
	 * <p>Default is {@link FilterType#ANNOTATION}.
	 * @see #classes
	 * @see #pattern
	 */
	FilterType type() default FilterType.ANNOTATION;

	/**
	 * Alias for {@link #classes}.
	 * @see #classes
	 */
	@AliasFor("classes")
	Class<?>[] value() default {};

	/**
	 * The class or classes to use as the filter.
	 * <p>The following table explains how the classes will be interpreted
	 * based on the configured value of the {@link #type} attribute.
	 * <table border="1">
	 * <tr><th>{@code FilterType}</th><th>Class Interpreted As</th></tr>
	 * <tr><td>{@link FilterType#ANNOTATION ANNOTATION}</td>
	 * <td>the annotation itself</td></tr>
	 * <tr><td>{@link FilterType#ASSIGNABLE_TYPE ASSIGNABLE_TYPE}</td>
	 * <td>the type that detected components should be assignable to</td></tr>
	 * <tr><td>{@link FilterType#CUSTOM CUSTOM}</td>
	 * <td>an implementation of {@link TypeFilter}</td></tr>
	 * </table>
	 * <p>When multiple classes are specified, <em>OR</em> logic is applied
	 * &mdash; for example, "include types annotated with {@code @Foo} OR {@code @Bar}".
	 * <p>Custom {@link TypeFilter TypeFilters} may optionally implement any of the
	 * following {@link org.springframework.beans.factory.Aware Aware} interfaces, and
	 * their respective methods will be called prior to {@link TypeFilter#match match}:
	 * <ul>
	 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
	 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
	 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
	 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
	 * </ul>
	 * <p>Specifying zero classes is permitted but will have no effect on component
	 * scanning.
	 * @since 4.2
	 * @see #value
	 * @see #type
	 */
	@AliasFor("value")
	Class<?>[] classes() default {};

	/**
	 * The pattern (or patterns) to use for the filter, as an alternative
	 * to specifying a Class {@link #value}.
	 * <p>If {@link #type} is set to {@link FilterType#ASPECTJ ASPECTJ},
	 * this is an AspectJ type pattern expression. If {@link #type} is
	 * set to {@link FilterType#REGEX REGEX}, this is a regex pattern
	 * for the fully-qualified class names to match.
	 * @see #type
	 * @see #classes
	 */
	String[] pattern() default {};
}

Filter中的type指定过滤类型,classes是指定的类为value属性的别名。

@ComponentScan实战

  • @实体类创建
package org.example.componentscan;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author shiKui
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;
}
  • Controller创建
package org.example.componentscan;

import org.springframework.stereotype.Controller;

@Controller
public class UserController {
}
  • UserDao创建
package org.example.componentscan;

import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
}
  • UserService创建
package org.example.componentscan;

import org.springframework.stereotype.Service;

@Service
public class UserService {
}
  • 配置类创建
@ComponentScan(value = "org.example.componentscan",useDefaultFilters = false,lazyInit = true,
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
        includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class}))
@Configuration
public class ComponentScanConfig {
    @Bean
    public User user() {
        return new User();
    }
}

指定扫描org.example.componentscan
不使用自定义的过滤器
开启懒加载加载Bean
使用excludeFilters,基于注解的方式排除Conntroller
使用includeFilters,基于注解的方式添加Controller,Service

  • 测试
@Test
public void componentScanAnnotationTest(){
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanConfig.class);
    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String beanName : beanDefinitionNames) {
        System.out.println("beanName:" + beanName + "===" + "bean对象:" + applicationContext.getBean(beanName));
    }
}
  • 测试结果
    在这里插入图片描述
    我们可以看到Controller被排除了,@Service注册了,因为我们没有使用默认的过滤方式,所以Dao也没有注册进来。

自定义类型过滤实战

自定义过滤类型需要实现TypeFilter接口,我们来看看TypeFilter接口的源码

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.type.filter;

import java.io.IOException;

import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;

/**
 * Base interface for type filters using a
 * {@link org.springframework.core.type.classreading.MetadataReader}.
 *
 * @author Costin Leau
 * @author Juergen Hoeller
 * @author Mark Fisher
 * @since 2.5
 */
@FunctionalInterface
public interface TypeFilter {

	/**
	 * Determine whether this filter matches for the class described by
	 * the given metadata.
	 * @param metadataReader the metadata reader for the target class
	 * @param metadataReaderFactory a factory for obtaining metadata readers
	 * for other classes (such as superclasses and interfaces)
	 * @return whether this filter matches
	 * @throws IOException in case of I/O failure when reading metadata
	 */
	boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
			throws IOException;
}

这个接口是个函数式接口,match方法如果返回true就注册,如果false反之。

  • 实现TypeFilter接口
package org.example.componentscan;

import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;

/**
 * @author shiKui
 */
public class MyFilterType implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return true;
    }
}

所有匹配到的都返回true,也就是都能注册进Spring

  • 重新定义一个配置类
package org.example.componentscan;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(value = "org.example.componentscan",includeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM,classes = MyFilterType.class),useDefaultFilters = false)
public class ComponentScanCustomConfig {

}

扫描org.example.componentscan,不使用默认的过滤,使用includeFilters 属性通过我们自定义的过滤器,注册组件。

  • 测试方法
@Test
public void componentScanCustomTest(){
     AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ComponentScanCustomConfig.class);
     String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
     for (String beanName : beanDefinitionNames) {
         System.out.println("beanName:" + beanName + "===" + "bean对象:" + applicationContext.getBean(beanName));
     }
 }
  • 测试结果
    在这里插入图片描述
    结果是意料之中。
    至此相信大家对@ComponentScan也有一定的了解了,如果大家想更深入的了解,可以跟着Spring的官方文档、源码去一步步深入了解。
    至此,如果我哪里不对,或者大家有什么想法可以在评论中提出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值