前言
用过springboot的同学肯定知道springboot 的注解开发给我们带来了极大的方便,bean的注入我们可以使用@component、@Service 、@Repository 、@ManageBean(JDK自带注解) 将我们的bean很轻松的交给spring的IOC容器,再也不用写复杂的xml了。
使用注解注入Bean
补充一下@Service 、@Repository 其实相当是@Component的一个别名,我们可以看看@Service 、@Repository 源码
@Service.png
@Repository.png
我们建立一个简单的springboot项目来看一看springboot这些注解的使用,我们新建如下5个类,TestBean0 不使用注解,1-4使用注解
//com.example.demo.ioc.TestBean0
public class TestBean0 {
public String print(){
return "TestBean 0 " ;
}
}
//com.example.demo.ioc.TestBean1
@Repository
public class TestBean1 {
public String print(){
return "TestBean1 " ;
}
}
//com.example.demo.ioc.TestBean2
@Service
public class TestBean2 {
public String print(){
return "TestBean2 " ;
}
}
//com.example.demo.ioc.TestBean3
@Component
public class TestBean3 {
public String print(){
return "TestBean3 " ;
}
}
//com.example.demo.ioc.TestBean4
@ManagedBean
public class TestBean4 {
public String print(){
return "TestBean4 " ;
}
}
我们在启动类里通过applicationContext的getBeanDefinitionNames()获取已经注入的所有bean的名称
@ComponentScan("com.example.demo")
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
}
}
如下图我们只有加注解的bean被成功注入。
beans.png
自定义注解
在进行源码分析之前我们先了解一下自定义注解,一般我们定义一个注解如下,常用两个元注解,详细解释可参考https://www.cnblogs.com/lyy-2016/p/6288535.html
- @Target表示注解能作用在什么类型上,比如可以在方法上、类,接口上、参数上;
- @Retention定义注解的保留策略
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotion {
}
定义好自己的自定义注解如何让我们注解起作用呢?一般上有两种方式,第一使用原生的Java反射获取注解,进行处理,第二种是用过AOP扫描注解,对带注解的方法、类等做增强(当然Aop也是通过反射动态代理实现的)。具体实现本篇文章不做详细实现,感兴趣的可以去百度一下,算了不想买药的话就别百度 了,Google一下或者bing一下吧。
spring的注解扫描
思考
我们也知道自定注解的实现分为两种了,那spring是通过那种实现的,先来看看通过AOP实现,我们讨论的是注解进行Bean注入,那么在扫描之前这个bean肯定是没有被spring进行管理的,因此我们显然不能通过spring的Aop去扫描注解。当然我们要是通过Aspect也是可以实现,但是显然spring这么牛逼的框架怎么会去使用Aspect呢(当然不是说Aspect不好,相信spring开发者初衷肯定是自己实现),那么第二种方式走不通,肯定还是第一种方式了,使用最原生的反射实现。
源码跟踪分析
熟悉spring的同学一定知道spring相关注解的IOC容器的常用实现有AnnotationConfigApplicationContext、AnnotationConfigServletWebServerApplicationContext这两个,其中后者是springmvc的一些注解增强,AnnotationConfigApplicationContext 两者原理基本相同,读者可以自行分析后者源码。大概看一下
AnnotationConfigApplicationContext有哪些实现方法,看源码我们大概发现两类重要的方法,
- 第一个是registerBean的方法,根据方法名我们很容易知道其实将bean注入容器的入口,
-
第二方法scan 方法,我们也可以大胆猜测其是spring需要扫描的包路径
image.png
测试registerBean注册bean
接下来我们来使用下面main方法测试一下
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(TestBean3.class);
applicationContext.register(TestBean0.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
}
我们可以在看看容器中的beans,如下图
image.png
虽然已经在容器中可以查看这个bean了(细心的同学一定会问这个没有带注解的bean怎么也被注入了,这个问题我们后面源码分析),但是这个bean是没法使用的,熟悉spring源码的同学一定知道,在bean加入到容器中后一个很重要的方式,就是refresh(),因此我们对上述main做简单修改如下
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(TestBean3.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
applicationContext.refresh();
applicationContext.getBean(TestBean3.class).print();
}
测试scan方法
对上述main方法进行修改,我们在com.example.demo.ioc包外新建两个类TestBean5、6 带@Component注解,注意如果是springboot项目,请去掉启动类上面的所有注解(其实不去掉也没有关系,但是为了理解更清楚建议去掉)。
路径.png
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.scan("com.example.demo.ioc");
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
applicationContext.refresh();//必不可少
applicationContext.getBean(TestBean3.class).print();
}
继续对上述代码进行断电调试,查看容器中所有bean,如下图,我们可以看到com.example.demo.ioc包下带注解的类被容器成功注入。
image.png
深入源码
上面一系列只是去验证了注解如何才能被扫描注入,那spring到底是如何扫描,我们现在来一探究竟。
我们以scan()方法为入口进行分析,分析流程图大致如下,颜色深的为主要流程:
spring 注解源码分析.png
- 以scan方法为入口,scan作用是指定扫描包,传入包进入扫描,scan方法中掉用 ClassPathBeanDefinitionScanner#scan方法
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
this.scanner.scan(basePackages);
}
2.ClassPathBeanDefinitionScanner顾名思义是扫描classpath下的bean,进入到ClassPathBeanDefinitionScanner#scan的方法,其中我们关心的是doScan(basePackages),此方法猜测应该是进行具体注解扫描
public int scan(String... basePackages) {
// 记录扫描bean数量,后面容器需要用到
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
// 此处进行扫描
doScan(basePackages);
// 进行注册
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
- 进入ClassPathBeanDefinitionScanner#doScan 方法分析,此方法作用是返回已注册到容器中的bean信息集合,此方法中findCandidateComponents(basePackage)选取候选者,目测在其中进行了注解扫描操作。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
//创建集合存放合适bean定义信息,可以理解为bean信息
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 循环出入的包
for (String basePackage : basePackages) {
// 集合存放所有符合条件的候选者bean信息,此处findCandidateComponents方法是对符合条件bean过滤
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);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
- 进入findCandidateComponents(basePackage)分析,正常情况下会进入到else里面执行scanCandidateComponents(basePackage);
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
// 带组件索引处理,一般情况不进入
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
5.继续进入scanCandidateComponents(basePackage)分析,我们可以发现spring其实是将包路径转换为文件绝对路径进行扫描其下面所有*.class文件,并将其解析为自己的资源对象Resource,接着最终要一步,遍历筛选候选组件isCandidateComponent(metadataReader) ,在此操作之前将resource文件通过反射获取到类的元信息,一遍后面进行注解判断需要;
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// classpath下对应包下面的所有*.class文件,resolveBasePackage(basePackage) 方法将包名称转换为路径名称,拼接为文件绝对路径
String 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 {
//此处通过反射获取类元信息
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 进行过滤,重要
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
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 {
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;
}
- 在isCandidateComponent(metadataReader)方法里,做了两件事,第一件判断有没有excludeFilters注解(这种注解的bean不被注入),第二件是判断有没有includeFilters注解(这种注解就是@Component这种注解,可以进行注入),其实判断也很简单,返回true的成功候选者,如下图spring 中AnnotationConfigApplicationContext容器只识别@Component和@ManageBean两个注解,这段最核心的部分tf.match(metadataReader, getMetadataReaderFactory())进行判断
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;
}
image.png
- 猜测这个方法里面应该是通过反射获取到注解进行判断,进入tf.match(metadataReader, getMetadataReaderFactory())分析
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
if (this.matchSelf(metadataReader)) {
return true;
} else {
ClassMetadata metadata = metadataReader.getClassMetadata();
if (this.matchClassName(metadata.getClassName())) {
return true;
} else {
if (this.considerInherited) {
String superClassName = metadata.getSuperClassName();
if (superClassName != null) {
Boolean superClassMatch = this.matchSuperClass(superClassName);
if (superClassMatch != null) {
if (superClassMatch) {
return true;
}
} else {
try {
if (this.match(metadata.getSuperClassName(), metadataReaderFactory)) {
return true;
}
} catch (IOException var11) {
this.logger.debug("Could not read super class [" + metadata.getSuperClassName() + "] of type-filtered class [" + metadata.getClassName() + "]");
}
}
}
}
if (this.considerInterfaces) {
String[] var12 = metadata.getInterfaceNames();
int var13 = var12.length;
for(int var6 = 0; var6 < var13; ++var6) {
String ifc = var12[var6];
Boolean interfaceMatch = this.matchInterface(ifc);
if (interfaceMatch != null) {
if (interfaceMatch) {
return true;
}
} else {
try {
if (this.match(ifc, metadataReaderFactory)) {
return true;
}
} catch (IOException var10) {
this.logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" + metadata.getClassName() + "]");
}
}
}
}
return false;
}
}
}
8.断点在进入 this.matchSelf(metadataReader) 方法,可见此处应该通过反射获取注解信息和出入的includeFilter注解进行判断
// 获取注解注解信息
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
// 判断是否包含此注解
return metadata.hasAnnotation(this.annotationType.getName()) || this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName());
}
总结
通过上面的分析我们大概知道spring扫描注解的流程,其实也是很简单,总结一下就是对某个包或者类进行文件进行扫描读取,反射提取类的元信息,根据元信息的注解和spring需要识别的注解进行比较带spring的注入注解则进行注入。
网友评论