👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 每月贈書
新計畫: 仿小紅書 (微服務架構)正在更新中... , 全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 : http://116.62.199.48/ 。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了261小節,累計43w+字,講解圖:1806張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,
我們都知道Spring在建立一個bean的時候,還要去填充bean的內容
大致流程如下:
反射建立bean——
createBeanInstance
填充bean——
populateBean
初始化bean——
initializeBean
(包括前後置增強)
註冊bean的銷毀方法——
registerDisposableBeanIfNecessary
這個填充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的內容外
更多的填充邏輯(欄位註入)應該是在
InstantiationAwareBeanPostProcessor
中的
postProcessProperties
裏面,欄位註入就是常用的
@Autowired
、
@Resource
註解
InstantiationAwareBeanPostProcessor
是一個介面
它的子類別中實作Autowired註入的是
AutowiredAnnotationBeanPostProcessor
,實作Resource註入的是
CommonAnnotationBeanPostProcessor
接下來分析一下
AutowiredAnnotationBeanPostProcessor
是怎麽進行欄位註入的
// AutowiredAnnotationBeanPostProcessor.postProcessProperties
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 找到需要Autowired的後設資料(欄位、方法)
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.get class(), 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
實作的
postProcessProperties
流程就是先找到需要透過
findAutowiringMetadata
找到需要Autowired的後設資料(欄位、方法) ,然後再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;
}
這個
needsRefresh
簡單看看就好,我們是第一次進入這個方法,所以這個metadata是null,那麽這個方法返回的是true
public static boolean needsRefresh(@Nullable InjectionMetadata metadata, class<?> clazz) {
return (metadata == null || metadata.needsRefresh(clazz));
}
那麽會進入到這個方法
buildAutowiringMetadata
private InjectionMetadata buildAutowiringMetadata(final class<?> clazz) {
if (!AnnotationUtils.isCandidate class(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
class<?> target class = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 處理欄位
ReflectionUtils.doWithLocalFields(target class, 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(target class, 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);
// 獲取父類,繼續找
target class = target class.getSuper class();
}
while (target class != null && target class != Object. class);
return InjectionMetadata.forElements(elements, clazz);
}
這邊傳入的clazz就是bean的 class,忘記了可以找上面的程式碼看一下
這裏源碼寫了很多,我們暫時只關心註入欄位的那一塊
ReflectionUtils.doWithLocalFields(target class, 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
的
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);
}
}
}
繼續追溯一下可以得知,這裏是獲取clazz的所有欄位並進行處理,這個
FieldCallback
是一個函式式介面,它的實作就是外面傳進來的這段程式碼
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));
}
}
那麽在這段程式碼裏面,又去找這個欄位有沒有被
@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
最關鍵的地方來了,接下來會判斷這個欄位是不是static的,如果是,那麽會發出警告,並且直接返回,不進行註入了
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的邏輯就暫時省略了,大概就是如果是欄位,那麽透過反射去註入,如果是方法,也透過反射去執行
@Resource
與以上大致同理
總結
總的來說,就一句話,spring在使用欄位註入對靜態欄位進行註入時,會忽略掉這個欄位,不去註入
也就是說Spring是有能力去註入靜態欄位的,但是Spring沒有選擇註入,為什麽呢?
可能是因為Spring的設計理念是管理bean物件,只有屬於物件的欄位Spring才去進行管理,如果是static的話,那麽這個欄位屬於類了,這個時候Spring去進行管理貌似不符合它的設計理念,所以Spring直接忽略掉了;另外如果一個bean修改了這個欄位,那麽所有bean的這個欄位都會受到影響,因為這個欄位是屬於類的,這個時候可能就會問題
那麽有沒有辦法實作靜態欄位註入呢?
可以的,在方法中打上@Autowired註解,在方法裏面去對靜態欄位進行賦值,當然這個方法也不能是靜態的,否則也會被spring會忽略掉
不過如果能夠不對靜態欄位註入就盡量不要註入,因為spring本身就不鼓勵我們這麽做,這種不鼓勵已經深入到程式碼裏面了
👉 歡迎 ,你將獲得: 專屬的計畫實戰 / Java 學習路線 / 一對一提問 / 學習打卡 / 每月贈書
新計畫: 仿小紅書 (微服務架構)正在更新中... , 全棧前後端分離部落格計畫 2.0 版本完結啦, 演示連結 : http://116.62.199.48/ 。全程手摸手,後端 + 前端全棧開發,從 0 到 1 講解每個功能點開發步驟,1v1 答疑,直到計畫上線。 目前已更新了261小節,累計43w+字,講解圖:1806張,還在持續爆肝中.. 後續還會上新更多計畫,目標是將Java領域典型的計畫都整一波,如秒殺系統, 線上商城, IM即時通訊,Spring Cloud Alibaba 等等,
1.
2.
3.
4.
最近面試BAT,整理一份面試資料【Java面試BATJ通關手冊】,覆蓋了Java核心技術、JVM、Java並行、SSM、微服務、資料庫、數據結構等等。
獲取方式:點「在看」,關註公眾號並回復 Java 領取,更多內容陸續奉上。
PS:因公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下「在看」,加個「星標」,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。
點「在看」支持小哈呀,謝謝啦