面試官:Spring 為什么不支持 static 字段的注入?
共 19649字,需瀏覽 40分鐘
·
2024-05-24 14:46
來(lái)源:juejin.cn/post/7283803914645569536
?? 歡迎加入小哈的星球 ,你將獲得: 專屬的項(xiàng)目實(shí)戰(zhàn) / Java 學(xué)習(xí)路線 / 一對(duì)一提問 / 學(xué)習(xí)打卡 / 每月贈(zèng)書
新項(xiàng)目:仿小紅書(微服務(wù)架構(gòu))正在更新中... , 全棧前后端分離博客項(xiàng)目 2.0 版本完結(jié)啦, 演示鏈接:http://116.62.199.48/ 。全程手摸手,后端 + 前端全棧開發(fā),從 0 到 1 講解每個(gè)功能點(diǎn)開發(fā)步驟,1v1 答疑,直到項(xiàng)目上線。目前已更新了261小節(jié),累計(jì)43w+字,講解圖:1806張,還在持續(xù)爆肝中.. 后續(xù)還會(huì)上新更多項(xiàng)目,目標(biāo)是將Java領(lǐng)域典型的項(xiàng)目都整一波,如秒殺系統(tǒng), 在線商城, IM即時(shí)通訊,Spring Cloud Alibaba 等等,戳我加入學(xué)習(xí),已有1400+小伙伴加入(早鳥價(jià)超低)
我們都知道Spring在創(chuàng)建一個(gè)bean的時(shí)候,還要去填充bean的屬性
大致流程如下:
-
反射創(chuàng)建bean—— createBeanInstance -
填充bean—— populateBean -
初始化bean—— initializeBean(包括前后置增強(qiáng)) -
注冊(cè)bean的銷毀方法—— registerDisposableBeanIfNecessary
這個(gè)填充bean的邏輯是在populateBean中
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
// ...
PropertyDescriptor[] filteredPds = null;
if (hasInstAwareBpps) {
if (pvs == null) {
pvs = mbd.getPropertyValues();
}
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
// here
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
}
// ...
if (pvs != null) {
applyPropertyValues(beanName, mbd, bw, pvs);
}
}
而除了applyPropertyValues可以填充bean的屬性外
更多的填充邏輯(字段注入)應(yīng)該是在InstantiationAwareBeanPostProcessor中的postProcessProperties里面,字段注入就是常用的@Autowired、@Resource注解
InstantiationAwareBeanPostProcessor是一個(gè)接口
它的子類中實(shí)現(xiàn)Autowired注入的是AutowiredAnnotationBeanPostProcessor,實(shí)現(xiàn)Resource注入的是CommonAnnotationBeanPostProcessor
接下來(lái)分析一下AutowiredAnnotationBeanPostProcessor是怎么進(jìn)行字段注入的
// AutowiredAnnotationBeanPostProcessor.postProcessProperties
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 找到需要Autowired的元數(shù)據(jù)(字段、方法)
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
// 注入
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
以上這段代碼是AutowiredAnnotationBeanPostProcessor實(shí)現(xiàn)的postProcessProperties
流程就是先找到需要通過(guò)findAutowiringMetadata找到需要Autowired的元數(shù)據(jù)(字段、方法) ,然后再inject
先看看findAutowiringMetadata
// AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
metadata = buildAutowiringMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
}
這個(gè)needsRefresh簡(jiǎn)單看看就好,我們是第一次進(jìn)入這個(gè)方法,所以這個(gè)metadata是null,那么這個(gè)方法返回的是true
public static boolean needsRefresh(@Nullable InjectionMetadata metadata, Class<?> clazz) {
return (metadata == null || metadata.needsRefresh(clazz));
}
那么會(huì)進(jìn)入到這個(gè)方法buildAutowiringMetadata
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 處理字段
ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});
// 處理方法
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
}
return;
}
if (method.getParameterCount() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation should only be used on methods with parameters: " +
method);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
elements.addAll(0, currElements);
// 獲取父類,繼續(xù)找
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return InjectionMetadata.forElements(elements, clazz);
}
這邊傳入的clazz就是bean的Class,忘記了可以找上面的代碼看一下
這里源碼寫了很多,我們暫時(shí)只關(guān)心注入字段的那一塊
ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});
處理字段的時(shí)候進(jìn)入了ReflectionUtils的doWithLocalFields方法
// ReflectionUtils.doWithLocalFields
public static void doWithLocalFields(Class<?> clazz, FieldCallback fc) {
for (Field field : getDeclaredFields(clazz)) {
try {
fc.doWith(field);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex);
}
}
}
繼續(xù)追溯一下可以得知,這里是獲取clazz的所有字段并進(jìn)行處理,這個(gè)FieldCallback是一個(gè)函數(shù)式接口,它的實(shí)現(xiàn)就是外面?zhèn)鬟M(jìn)來(lái)的這段代碼
field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
}
那么在這段代碼里面,又去找這個(gè)字段有沒有被@Autowired修飾
// AutowiredAnnotationBeanPostProcessor.findAutowiredAnnotation
@Nullable
private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
MergedAnnotations annotations = MergedAnnotations.from(ao);
// autowiredAnnotationTypes包含 @Autowired,@Value,@Inject
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
MergedAnnotation<?> annotation = annotations.get(type);
if (annotation.isPresent()) {
return annotation;
}
}
return null;
}
如果有@Autowired修飾,那么lambda中的ann不為null
最關(guān)鍵的地方來(lái)了,接下來(lái)會(huì)判斷這個(gè)字段是不是static的,如果是,那么會(huì)發(fā)出警告,并且直接返回,不進(jìn)行注入了
field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
// 判斷是否被static修飾
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
}
最終注入的邏輯在InjectionMetadata的inject中
// InjectionMetadata.inject
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
element.inject(target, beanName, pvs);
}
}
}
element的邏輯就暫時(shí)省略了,大概就是如果是字段,那么通過(guò)反射去注入,如果是方法,也通過(guò)反射去執(zhí)行
@Resource與以上大致同理
總結(jié)
總的來(lái)說(shuō),就一句話,spring在使用字段注入對(duì)靜態(tài)字段進(jìn)行注入時(shí),會(huì)忽略掉這個(gè)字段,不去注入
也就是說(shuō)Spring是有能力去注入靜態(tài)字段的,但是Spring沒有選擇注入,為什么呢?
可能是因?yàn)镾pring的設(shè)計(jì)理念是管理bean對(duì)象,只有屬于對(duì)象的字段Spring才去進(jìn)行管理,如果是static的話,那么這個(gè)字段屬于類了,這個(gè)時(shí)候Spring去進(jìn)行管理貌似不符合它的設(shè)計(jì)理念,所以Spring直接忽略掉了;另外如果一個(gè)bean修改了這個(gè)字段,那么所有bean的這個(gè)字段都會(huì)受到影響,因?yàn)檫@個(gè)字段是屬于類的,這個(gè)時(shí)候可能就會(huì)問題
那么有沒有辦法實(shí)現(xiàn)靜態(tài)字段注入呢?
可以的,在方法中打上@Autowired注解,在方法里面去對(duì)靜態(tài)字段進(jìn)行賦值,當(dāng)然這個(gè)方法也不能是靜態(tài)的,否則也會(huì)被spring會(huì)忽略掉
不過(guò)如果能夠不對(duì)靜態(tài)字段注入就盡量不要注入,因?yàn)閟pring本身就不鼓勵(lì)我們這么做,這種不鼓勵(lì)已經(jīng)深入到代碼里面了
?? 歡迎加入小哈的星球 ,你將獲得: 專屬的項(xiàng)目實(shí)戰(zhàn) / Java 學(xué)習(xí)路線 / 一對(duì)一提問 / 學(xué)習(xí)打卡 / 每月贈(zèng)書
新項(xiàng)目:仿小紅書(微服務(wù)架構(gòu))正在更新中... , 全棧前后端分離博客項(xiàng)目 2.0 版本完結(jié)啦, 演示鏈接:http://116.62.199.48/ 。全程手摸手,后端 + 前端全棧開發(fā),從 0 到 1 講解每個(gè)功能點(diǎn)開發(fā)步驟,1v1 答疑,直到項(xiàng)目上線。目前已更新了261小節(jié),累計(jì)43w+字,講解圖:1806張,還在持續(xù)爆肝中.. 后續(xù)還會(huì)上新更多項(xiàng)目,目標(biāo)是將Java領(lǐng)域典型的項(xiàng)目都整一波,如秒殺系統(tǒng), 在線商城, IM即時(shí)通訊,Spring Cloud Alibaba 等等,戳我加入學(xué)習(xí),已有1400+小伙伴加入(早鳥價(jià)超低)
2. @Transactional 中使用線程鎖導(dǎo)致了鎖失效,驚了!
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) Java 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
PS:因公眾號(hào)平臺(tái)更改了推送規(guī)則,如果不想錯(cuò)過(guò)內(nèi)容,記得讀完點(diǎn)一下“在看”,加個(gè)“星標(biāo)”,這樣每次新文章推送才會(huì)第一時(shí)間出現(xiàn)在你的訂閱列表里。
點(diǎn)“在看”支持小哈呀,謝謝啦
