當前位置: 妍妍網 > 碼農

面試官:Spring 為什麽不支持 static 欄位的註入?

2024-05-26碼農

來源:juejin.cn/post/7283803914645569536

👉 歡迎 ,你將獲得: 專屬的計畫實戰 / 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:因公眾號平台更改了推播規則,如果不想錯過內容,記得讀完點一下在看,加個星標,這樣每次新文章推播才會第一時間出現在你的訂閱列表裏。

    「在看」支持小哈呀,謝謝啦