更新时间:2021-09-10 10:54:20 来源:极悦 浏览1729次
此篇文章会主要介绍Spring中两个非常重要的关于包扫描的基础类,由于Spring代码太庞大,因此本文不会细致地说明每一行代码地作用,只会讲清楚关键的地方有什么作用,以及一些子类可以重写的方法,用来覆盖默认扫描行为。最后会基于Spring提供的包扫描设施来写一个简单的例子来模仿MyBatis-Spring扫描Mapper接口,生成代理注册到容器中。我们主要关注ClassPathScanningCandidateComponentProvider以及ClassPathBeanDefinitionScanner这两个类,讲清楚这两个类的作用以及开发者需要关注的方法。
此类是Spring中包扫描机制最底层的类,用于扫描指定包下面的类文件,并且会根据用户提供的includeFilters以及excludeFilters来过滤掉不想注册的类,最后生成一个基本的BeanDefinition。
先看下两个比较重要的属性吧
/**
* 包含集合,如果类文件匹配includeFilters集合中任意一个TypeFilter条件,那么就通过筛选。
* 其中最常见的TypeFilter有AnnotationTypeFilter、AssignableTypeFilter
* AnnotationTypeFilter: 代表类是否被指定注解标注
* AssignableTypeFilter: 代表类是否继承(实现)自指定的超类
*/
private final List<TypeFilter> includeFilters = new LinkedList<TypeFilter>();
/**
* 排除集合, 如果类文件匹配excludeFilters集合中任何一个TypeFilter条件,那么就不会通过筛选
* 并且excludeFilters优先级高于includeFilters
*/
private final List<TypeFilter> excludeFilters = new LinkedList<TypeFilter>();
初始化,只看参数最长的那个构造方法
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters,
Environment environment) {
// 主要关注useDefaultFilters这个参数, 如果为true, 会注册一个默认的
// includeFilter
if (useDefaultFilters) {
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(null);
}
protected void registerDefaultFilters() {
// 注册一个注解的TypeFilter,意思是如果类定义时有被@Component注解标注
// 那么就会通过筛选,需要注意的是衍生注解也是会通过筛选的
// 比如@Service、@Controller、@Repository,它们有一个共同点,那就是这三个
// 注解本身就是被@Component注解标注的
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
// 下面是注册JAVA EE里的一些注解,一般开发也不用, 就省略了
}
接下来就是最重要的方法了,也就是扫描包文件,并且将符合筛选条件的类生成BeanDefinition。下面代码将日志打印剔除了。
/**
* 此方法会扫描指定包以及子包
* @param basePackage 需要扫描的包,形如com.wangtao.dao
* @return 返回一个符合筛选条件后的BeanDefinition集合
*/
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
// 将包名解析成路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 获取此包以及子包下的所有.class文件资源
Resource[] resources = this.resourcePatternResolver.
getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
// 得到类文件的元数据,是基于ASM字节码技术实现的,此时还没有加载类文件
// 包括类名、注解信息、父类、接口等等一系列信息
MetadataReader metadataReader = this.metadataReaderFactory.
getMetadataReader(resource);
// 匹配筛选条件,也就是上述includeFilters、excludeFilters这两个集合
if (isCandidateComponent(metadataReader)) {
// 创建一个BeanDefinition
// 只是简单的设置了beanClassName属性为类的完全限定名
ScannedGenericBeanDefinition sbd = new ScannedGenericBean
Definition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
// 这里会再有一个筛选条件,一般是根据类文件的元数据筛选
// 比如是不是具体类,是不是顶层类,是不是抽象类等
// 默认情况下只添加顶层的具体类,顶层的意思是可以独立实例化而不会依赖外部类
// 成员内部类需要外部类对象才能实例化,就不会通过。
if (isCandidateComponent(sbd)) {
candidates.add(sbd);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath
scanning", ex);
}
return candidates;
}
再看看两个isCandidateComponent方法的默认实现,一般来说我们可能需要重写这两个方法来改变默认的筛选条件。
// 根据excludeFilters、excludeFilters初步筛选
// 一目了然,基本不用再需要解释
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return isConditionMatch(metadataReader);
}
}
return false;
}
// 根据类文件元数据筛选
// 只扫描顶层具体类或者虽然是抽象类但是存在@Lookup标记的方法
// 后面那个是用于方法注入,我从来没用过。
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(
Lookup.class.getName()))));
总结:此类在默认情况下会将指定包以及子包下中被@Component(以及衍生注解)标记的顶层类创建一个BeanDefinition。
说到这插下Spring开启注解扫描的配置,有时我们可能只想在SpringMVC的配置文件中扫描@Controller标记的类,其它层扫描@Service、@Component、@Repository标记的类,就可以像下面这样分层配置。
springmvc.xml
<context:component-scan base-package="com.wangtao.controller"
use-default-filters="false">
<context:include-filter
type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
applicationContext.xml
<context:component-scan base-package="com.wangtao.service,com.wangtao.dao">
<context:exclude-filter
type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
也就是说SpringMVC中禁掉默认的includeFilters,添加了一个使用@Controller标记的条件,而applicationContext.xml使用默认的includeFilters,但是排除对@Controller标记的类。
此类继承自ClassPathScanningCandidateComponentProvider,除了拥有父类扫描包的功能外,还会对扫描后的BeanDefinition加工并注册到Spring容器中,所谓的加工就是指会设置一些类文件中用注解标记的一些属性值,如@Lazy、@Scope、@Primary等。
先看一些重要属性
/** 用于注册bean **/
private final BeanDefinitionRegistry registry;
/**
* 此类存储了BeanDefinition一些默认属性值
* lazyInit: false
* autowireMode: AbstractBeanDefinition.AUTOWIRE_NO
* initMethodName: null
* destroyMethodName: null
**/
private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults();
/** bean name 生成器 **/
private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
/**
* 默认值:true
* 相当于开启<context:annotation-config>
* 意味着我们可以使用@Autowired、@Resource、@PostConstruct、@PreDestroy注解
* 会自动帮我们注册解析这几个注解的BeanPostProcessor
*/
private boolean includeAnnotationConfig = true;
构造方法
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry,
boolean useDefaultFilters,
Environment environment,
ResourceLoader resourceLoader) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// 同ClassPathScanningCandidateComponentProvider
if (useDefaultFilters) {
registerDefaultFilters();
}
setEnvironment(environment);
setResourceLoader(resourceLoader);
}
接下来看最重要的扫描方法
/**
* 返回扫描真正注册bean的数量
*/
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// 注册几个BeanPostProcessor用来解析@Autowired、@Reource等几个注解
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
/**
* 将BeanDefinition集合返回,子类若有必要可以继续对BeanDefinition做修改
*/
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new
LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
// 得到所有符合扫描条件的BeanDefinition集合,接下来会对这些BeanDefinition加工
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 主要包括bean的scope属性(默认单例)以及代理策略(不需要代理,JDK动态代理、CGLIB)
// 来适配AOP
ScopeMetadata scopeMetadata = this.scopeMetadataResolver
.resolveScopeMetadata(candidate);
// 设置scope属性
candidate.setScope(scopeMetadata.getScopeName());
// 生成bean name
String beanName = this.beanNameGenerator
.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
// 根据beanDefinitionDefaults设置一些默认值
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// 如果是注解定义的Bean, findCandidateComponents默认实现返回的BeanDefinition
// 是一个ScannedGenericBeanDefinition,其实现了AnnotatedBeanDefinition接口
if (candidate instanceof AnnotatedBeanDefinition) {
// 解析@Scope、@Primary、@Lazy等属性并设置到BeanDefinition中
AnnotationConfigUtils.processCommonDefinitionAnnotations(
(AnnotatedBeanDefinition) candidate);
}
// 检查BeanDefinition
// 主要检查这个容器中是否已经存在此BeanDefinition
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder =
new BeanDefinitionHolder(candidate, beanName);
// 设置代理策略
definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(
scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 注册到Spring容器中
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
因此如果我们需要自定义扫描实现bean的注册,基本上就是要继承ClassPathBeanDefinitionScanner并且重写doScan方法了。大致框架就是
public class MyScanner extends ClassPathBeanDefinitionScanner {
public MyScanner(BeanDefinitionRegistry registry) {
super(registry)
}
@Overide
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 父类已经将这些bean注册了
Set<BeanDefinitionHolder> holders = super.doScan(basePackages);
for(BeanDefinitionHolder holder : holders) {
// 在这里修改BeanDefinition引用的对象即可
}
return holders;
}
}
以上就是极悦小编介绍的"Spring包扫描机制详解",希望对大家有帮助,想了解更多可查看Spring框架教程。极悦在线学习教程,针对没有任何Java基础的读者学习,让你从入门到精通,主要介绍了一些Java基础的核心知识,让同学们更好更方便的学习和了解Java编程,感兴趣的同学可以关注一下。
0基础 0学费 15天面授
Java就业班有基础 直达就业
业余时间 高薪转行
Java在职加薪班工作1~3年,加薪神器
工作3~5年,晋升架构
提交申请后,顾问老师会电话与您沟通安排学习