条件注解
在IoC篇曾简单介绍过条件注解并列举了四个常用的注解,它们内部存在@Conditional
注解,作用域基本是类和方法,接下来将会详细介绍条件注解的实现。
ConditionEvaluator->shouldSkip():
// 判断是否有Conditional注解,没有直接返回
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
......
// conditions是@Conditional的注解值
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// 这里执行注解值的 condition.matches 方法来进行匹配,返回布尔值
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
// 不符合的话,后续逻辑不添加bdm
return true;
}
}
return false;
SpringBootCondition->matches():
ConditionOutcome outcome = getMatchOutcome(context, metadata);
这里的conditions是类或方法条件注解的@Conditional注解值,如果有多个的话需要都满足才行,@ConditionalOnClass
的是OnClassCondition
,@ConditionalOnBean
的是OnBeanCondition
,@Profile
的是ProfileCondition
,判断是否符合执行的是注解值的matches
方法。
OnClassCondition
OnClassCondition->getMatchOutcome():
// 获取注解ConditionalOnClass的注解值,返回全限定名数组
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
List<String> missing = filter(onClasses, ClassNameFilter.MISSING,
classLoader);
// 只要有缺失就立即返回
if (!missing.isEmpty()) {
return ConditionOutcome
.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class", "required classes")
.items(Style.QUOTE, missing));
// 省略部分是注解ConditionalOnMissingClass的处理,逻辑类似
......
FilteringSpringBootCondition->filter():
// missing情况下,如果没有加载到该类,添加进matches集合中
if (classNameFilter.matches(candidate, classLoader)) {
matches.add(candidate);
}
return matches;
FilteringSpringBootCondition->ClassNameFilter:
protected enum ClassNameFilter {
PRESENT {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return isPresent(className, classLoader);
}
},
MISSING {
@Override
public boolean matches(String className, ClassLoader classLoader) {
return !isPresent(className, classLoader);
}
};
isPresent():
try {
forName(className, classLoader);
return true;
} catch (Throwable ex) {
return false;
}
}
@ConditionalOnClass
的处理就是加载给定类,没有则跳过,@ConditionalOnMissingClass
与其相反。
OnBeanCondition
假设ConditionalOnBean的值如下
OnBeanCondition->getMatchOutcome():
ConditionMessage matchMessage = ConditionMessage.empty();
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
// 将注解信息封装成BeanSearchSpec对象
// 如果没有指定types和names属性,看注解是否与@Bean配合使用,如果是的话,添加返回类的全限定名
BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnBean.class);
MatchResult matchResult = getMatchingBeans(context, spec);
// 只要有缺失就立即返回
if (!matchResult.isAllMatched()) {
String reason = createOnBeanNoMatchReason(matchResult);
return ConditionOutcome.noMatch(ConditionMessage
.forCondition(ConditionalOnBean.class, spec).because(reason));
}
// 省略部分是注解ConditionalOnMissingBean的处理,逻辑类似
......
OnBeanCondition->getMatchingBeans():
// 根据type判断目标bean是否存在
for (String type : beans.getTypes()) {
Collection<String> typeMatches = getBeanNamesForType(beanFactory, type,
typeExtractor, context.getClassLoader(), considerHierarchy);
......
// 存在放一个集合,不存在放一个集合
}
// 省略部分是对于annotations和names的处理,逻辑类似
......
return matchResult;
OnBeanCondition->getBeanNamesForType():
try {
return getBeanNamesForType(beanFactory, considerHierarchy,
ClassUtils.forName(type, classLoader), typeExtractor);
} catch (ClassNotFoundException | NoClassDefFoundError ex) {
// 这里也考虑到了加载类不存在的情况
return Collections.emptySet();
}
例子中@ConditionalOnBean封装成的BeanSearchSpec对象如下
最终执行的是BeanTypeRegistry
的getNamesForType()
方法
BeanTypeRegistry->getNamesForType():
// 遍历IoC容器的bdn和manualSingletonNames,当前类成员变量beanTypes没有name的话,则添加
updateTypesIfNecessary();
// 从beanTypes中遍历,获取对应的Class对象,如果是给定的子类的话,则添加其名称
return this.beanTypes.entrySet().stream().filter((entry) -> {
Class<?> beanType = extractType(entry.getValue(), typeExtractor);
return beanType != null && type.isAssignableFrom(beanType);
}).map(Map.Entry::getKey).collect(Collectors.toCollection(LinkedHashSet::new));
对于type,没有用IoC容器的getBeanNamesForType
方法,而是很巧妙地使用了遍历+查实例化对象的方式。
使用@ConditionalOnBean
存在bean加载顺序的问题,只有项目中最初扫描出来带有@Component
的bean才会在全部放入容器之后判断(即不能是@Import,@Bean等注解引入的bean)。因此建议条件注解只判断@Component层面是否有相应的bean
ProfileCondition
从ProfileCondition
的matches
方法中观察到,先是获取了@Profile注解的属性,然后查看环境属性是否包含了该注解,有则返回true,没有则返回false。
// 最终会走入该方法中
AbstractEnvironment->isProfileActive():
// 获取activeProfiles
Set<String> currentActiveProfiles = doGetActiveProfiles();
// profile是注解值对象
// currentActiveProfiles包含就返回true,否则返回false
return (currentActiveProfiles.contains(profile) ||
(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
总结
找到条件注解的@Conditional
属性值,查看其matches()
方法,就可以探索条件注解的实现原理了。
懒加载
前言:IoC之实例化与依赖注入
懒加载,指的是@Lazy注解,如果对bean使用,不会立即实例化,但是如果bean有引用或是依赖的话,还是会实例化的,如果对字段使用,那么不会立即注入依赖。
对bean引用的代码位于DefaultListableBeanFactory->preInstantiateSingletons()
。
假设对字段引用使用的是@Lazy @Autowired
注解。
DefaultListableBeanFactory->resolveDependency()
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
descriptor, requestingBeanName);
ContextAnnotationAutowireCandidateResolver->getLazyResolutionProxyIfNecessary():
return (isLazy(descriptor) // --1
? buildLazyResolutionProxy(descriptor, beanName)
: null);
ContextAnnotationAutowireCandidateResolver->isLazy(): // --1
// 遍历字段的注解,看是否有@Lazy注解
for (Annotation ann : descriptor.getAnnotations()) {
Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
if (lazy != null && lazy.value()) {
return true;
}
}
ContextAnnotationAutowireCandidateResolver->buildLazyResolutionProxy():
@Override
public Object getTarget() {
Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
......
}
ProxyFactory pf = new ProxyFactory();
pf.setTargetSource(ts);
Class<?> dependencyType = descriptor.getDependencyType();
if (dependencyType.isInterface()) {
pf.addInterface(dependencyType);
}
return pf.getProxy(beanFactory.getBeanClassLoader());
ProxyFactory->getProxy():
return createAopProxy().getProxy(classLoader);
ProxyCreatorSupport->createAopProxy():
return getAopProxyFactory().createAopProxy(this);
DefaultAopProxyFactory->createAopProxy():
// 如果是接口的话,就返回Jdk代理,否则返回Cglib代理
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
return new ObjenesisCglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
@Lazy作用于字段时,返回的是动态代理,在调用字段方法时,代理类调用getTarget()
方法从IoC容器中注入并获取对象,并调用对象的方法完成最终调用。
附录:io读取常见类和方法
文件输入流 FileInputStream
read()
读取一个字节,读到末尾返回-1,下同read(byte b[])
读取最多数组b长度的字节,将值填充入数组中,读完返回-1,下同read(byte b[], int off, int len)
读取len长度的字节,从数组的off下标开始,填充入数组中,注意off+len不能超过数组的长度,读完返回-1,下同getChannel()
返回文件通道,与NIO有关
缓冲输入流 BufferedInputStream(fileInputStream)
读取最多内置字节数组长度的字节(默认8192),将值填充入数组中,用读内置字节数组代替真实的IO
如上图所示,in是文件流,buf是内置字节数组,count是本次读取的字节长度,pos是下标,读了len个字节,则pos位移len
当pos到达count时,会再一次IO,如果count返回0,说明读取完毕,此时pos=count=0
字节数组输入流 ByteArrayInputStream(byte[])
实例化时,将一个字节数组放入内部,读取该字节数组,没有in对象,其它同缓冲输入流
如果pos和count相等,说明读取完毕
文件字符流 FileReader
以字符的形式读取文件,用读内部维护ByteBuffer代替真实的IO
如上图所示,在UTF-8编码中,一个汉字占三个字节,一个字母占一个字节,读取一次默认是两个字符。当position到达limit时,会再一次IO,如果position返回0,说明读取完毕,此时postion=limit=0
缓冲字符流 BufferedReader(fileReader)
先fileReader io填充ByteBuffer,然后从ByteBuffer中读取最多ByteBuffer长度的字节(默认8192),将字节转换成字符填充入数组中,用读内置字符数组代替真实的IO
当nextChar到达nChars时,会再一次读ByteBuffer,如果ByteBuffer读取完毕则读取完毕,此时nextChar=nChar
readLine()
读取一行