天天看點

@Autowired注解 -【Spring底層原理】

目錄

​​一、概述​​

​​二、執行個體分析​​

​​三、源碼追蹤​​

​​四、總結​​

一、概述

【1】注解用法

根據@Autowired注解的源碼,可以看到該注解可以作用在構造器、參數、方法、屬性,都是從容器中擷取參數元件的值

  • 标注在方法上:@Bean+方法參數,參數從容器中擷取,預設不寫@Autowired效果是一樣的,都能自動裝配
  • 标注在構造器上:如果元件上隻有一個有參構造,這個有參構造的@Autowired可以省略,參數位置的元件還是可以自動從容器中擷取
  • 标注在參數位置
  • 标注在屬性位置
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}
      

相關注解:

  • @Autowired:預設按類型裝配,如果我們想使用按名稱裝配,可以結合@Qualifier注解一起使用(Spring提供)
  • @Qualifier():指定裝配的bean,多個執行個體時可以結合@Autowired一起使用
  • @Primary:自動裝配時當出現多個bean候選者時,被注解為​

    ​@Primary​

    ​的bean将作為首選者
  • @Resource:預設按名稱裝配,當找不到與名稱比對的bean才會按類型裝配(不支援​

    ​@Primary​

    ​​和​

    ​@Autowired(required = false)​

    ​功能,JDK提供)
  • @Inject:需要導入javax.inject的包,和Autowired功能一樣,但是沒有​

    ​required=false​

    ​功能(JDK提供)

【2】自動裝配

Spring利用依賴注入(DI)完成對IOC容器中各個元件的依賴關系指派

​@Autowired​

​自動注入(Spring提供的):

  • 預設優先按照去容器中找對應的元件:applicationContext.getBean()
  • 如果找到多個相同類型的元件,再将屬性的名稱作為元件的ID去容器中查找
  • @Qualifier()注解:該注解指定需要裝配的元件ID,而不是使用屬性名
  • 自動裝配預設必須要對屬性指派,沒有就會報錯,可以使用​

    ​@Autowired(required = false)​

    ​指定非必須就不會報錯
  • @Primary注解:自動裝配時當出現多個bean候選者時,被注解為​

    ​@Primary​

    ​​的bean将作為首選者,否則将抛出異常,如果使用了​

    ​@Qualifier()​

    ​指定裝配的bean,則還是使用明确指定裝配的bean

@Resource(JSR250)和@Inject(JSR330)(JDK提供的)

@Resource:

  • 預設按照元件名稱進行裝配,也可以指定名稱進行裝配
  • 當找不到與名稱比對的bean會按類型裝配
  • 不支援​

    ​@Primary​

    ​​和​

    ​@Autowired(required = false)​

    ​功能
  • 如果同時指定了name和type,則從Spring上下文中找到唯一比對的bean進行裝配,找不到則抛出異常。
  • 如果指定了name,則從上下文中查找名稱(id)比對的bean進行裝配,找不到則抛出異常。
  • 如果指定了type,則從上下文中找到類似比對的唯一bean進行裝配,找不到或是找到多個,都會抛出異常。
  • 如果既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;如果沒有比對,則回退為一個原始類型進行比對,如果比對則自動裝配。

@Inject:

  • 需要導入javax.inject的包,和Autowired功能一樣,但是沒有​

    ​required=false​

    ​功能

【3】@Autowired和@Resource注解的差別

  • @Autowired由Spring提供,隻按照byType注入;@Resource由J2EE提供,預設按照byName自動注入,當找不到與名稱比對的bean會按類型裝配
  • @Autowired預設按類型裝配,預設情況下必須要求依賴對象存在,如果要允許null值,可以設定它的required屬性為false。如果想使用名稱裝配可以結合@Qualifier注解進行使用。
  • @Resource,預設按照名稱進行裝配,名稱可以通過name屬性進行指定,如果沒有指定name屬性,當注解寫在字段上時,預設取字段名進行名稱查找。如果注解寫在setter方法上預設取屬性名進行裝配。當找不到與名稱比對的bean時才按照類型進行裝配。但是需要注意的是,如果name屬性一旦指定,就隻會按照名稱進行裝配。

二、執行個體分析

這裡隻對@Autowired注解标注在屬性位置進行執行個體分析

【1】@Autowired注解

// 啟動類
@Test
public void TestMain() {
    // 建立IOC容器
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    UserService userService = applicationContext.getBean(UserService.class);
    System.out.println("userService:" + userService);
}

// Service
@Service
public class UserService {
    @Autowired(required = false)    // 指定非必須
    @Qualifier("userDao2")      // 指定裝配bean
    private UserDao userDao;
    @Override
    public String toString() {
        return "UserService{" +
                "userDao=" + userDao +
                '}';
    }
}

// Dao
@Repository
public class UserDao {
    private String label = "1";
    public void setLabel(String label) {
        this.label = label;
    }
    @Override
    public String toString() {
        return "UserDao{" +
                "label='" + label + '\'' +
                '}';
    }
}

// 配置類
@Configuration
@ComponentScan({"dao","service","controller"})
public class AppConfig {
    @Primary        // 首選裝配bean
    @Bean("userDao2")
    public UserDao userDao(){
        UserDao userDao = new UserDao();
        userDao.setLabel("2");
        return userDao;
    }
}
      

輸出結果如下,由于上面使用​

​@Qualifier("userDao2")​

​指定了要裝配的bean,是以這裡輸出的是label=’2‘:

@Autowired注解 -【Spring底層原理】
  • 如果将​

    ​@Qualifier("userDao2")​

    ​​改為​

    ​@Qualifier("userDao")​

    ​​,則裝配的是​

    ​label=’1‘​

【2】@Resource注解

@Service
public class UserService {
    @Resource(name = "userDao2",type = UserDao.class)
    private UserDao userDao;
    @Override
    public String toString() {
        return "UserService{" +
                "userDao=" + userDao +
                '}';
    }
}
      
  • 預設按照元件名稱進行裝配,也可以指定名稱進行裝配
  • 當找不到與名稱比對的bean會按類型裝配
  • 不支援​

    ​@Primary​

    ​​和​

    ​@Autowired(required = false)​

    ​功能
  • 如果同時指定了name和type,則從Spring上下文中找到唯一比對的bean進行裝配,找不到則抛出異常。
  • 如果指定了name,則從上下文中查找名稱(id)比對的bean進行裝配,找不到則抛出異常。
  • 如果指定了type,則從上下文中找到類似比對的唯一bean進行裝配,找不到或是找到多個,都會抛出異常。
  • 如果既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;如果沒有比對,則回退為一個原始類型進行比對,如果比對則自動裝配。

三、源碼追蹤

這裡對@Autowired注解底層進行源碼分析

參考:​​​​​

@Autowired是用來裝配bean的,肯定和bean的執行個體化有關,先經過了refresh方法,在finishBeanFactoryInitialization方法中getBean,然後走getObject的時候觸發bean的初始化。bean的初始化是一個很複雜地方,在AbstractAutowireCapableBeanFactory#doCreateBean方法中,先建立一個BeanWrapper,它的内部成員變量wrappedObject中存放的就是執行個體化的MyService對象,Spring Bean的生命周期源碼詳解 - 【Spring底層原理】,再往後進入populateBean方法進行屬性注入

Spring對autowire注解的實作邏輯位于類:​

​AutowiredAnnotationBeanPostProcessor#postProcessProperties​

​之中,——>findAutowiringMetadata——>buildAutowiringMetadata,核心代碼就在buildAutowiringMetadata方法裡面

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    } else {
        List<InjectedElement> elements = new ArrayList();
        // 需要處理的目标類
        Class targetClass = clazz;

        do {
            List<InjectedElement> currElements = new ArrayList();
            // 通過反射擷取該類所有的字段,并周遊每一個字段,并通過方法findAutowiredAnnotation周遊每一個字段的所用注解,并如果用autowired修飾了,則傳回auotowired相關屬性
            ReflectionUtils.doWithLocalFields(targetClass, (field) -> {
                MergedAnnotation<?> ann = this.findAutowiredAnnotation(field);
                if (ann != null) {
                    // 校驗autowired注解是否用在了static方法上
                    if (Modifier.isStatic(field.getModifiers())) {
                        if (this.logger.isInfoEnabled()) {
                            this.logger.info("Autowired annotation is not supported on static fields: " + field);
                        }

                        return;
                    }

                    // 判斷是否指定了required
                    boolean required = this.determineRequiredStatus(ann);
                    currElements.add(new AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement(field, required));
                }

            });
            // 和上面一樣的邏輯,但是是通過反射處理類的method
            ReflectionUtils.doWithLocalMethods(targetClass, (method) -> {
                Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
                if (BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                    MergedAnnotation<?> ann = this.findAutowiredAnnotation(bridgedMethod);
                    if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                        if (Modifier.isStatic(method.getModifiers())) {
                            if (this.logger.isInfoEnabled()) {
                                this.logger.info("Autowired annotation is not supported on static methods: " + method);
                            }

                            return;
                        }

                        if (method.getParameterCount() == 0 && this.logger.isInfoEnabled()) {
                            this.logger.info("Autowired annotation should only be used on methods with parameters: " + method);
                        }

                        boolean required = this.determineRequiredStatus(ann);
                        PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                        currElements.add(new AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement(method, required, pd));
                    }

                }
            });
            // 用@Autowired修飾的注解可能不止一個,是以都加在currElements這個容器裡面,一起處理
            elements.addAll(0, currElements);
            targetClass = targetClass.getSuperclass();
        } while(targetClass != null && targetClass != Object.class);

        return InjectionMetadata.forElements(elements, clazz);
    }
}
      
  • 擷取需要處理的目标類
  • 通過doWithLocalFields方法傳入目标類參數,通過反射擷取該類所有的字段,并周遊每一個字段,并通過方法findAutowiredAnnotation周遊每一個字段的所用注解,并如果用autowired修飾了,則傳回auotowired相關屬性
  • 判斷autowired注解是否用在了static方法上
  • 如有多個@Autowired修飾的注解,都加在currElements這個容器裡面,一起處理

最後傳回包含所有帶有autowire注解修飾的一個InjectionMetadata集合,如下

  • targetClass:要處理的目标類
  • elements:上述方法擷取到的是以elements集合
public InjectionMetadata(Class<?> targetClass, Collection<InjectionMetadata.InjectedElement> elements) {
    this.targetClass = targetClass;
    this.injectedElements = elements;
}
      

有了目标類,與所有需要注入的元素集合之後,我們就可以實作autowired的依賴注入邏輯了,實作的方法如下:

public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) {
    if (!this.validatedBeanNames.contains(beanName)) {
        if (!this.shouldSkip(this.beanFactory, beanName)) {
            List<String> invalidProperties = new ArrayList();
            PropertyDescriptor[] var6 = pds;
            int var7 = pds.length;

            for(int var8 = 0; var8 < var7; ++var8) {
                PropertyDescriptor pd = var6[var8];
                if (this.isRequiredProperty(pd) && !pvs.contains(pd.getName())) {
                    invalidProperties.add(pd.getName());
                }
            }

            if (!invalidProperties.isEmpty()) {
                throw new BeanInitializationException(this.buildExceptionMessage(invalidProperties, beanName));
            }
        }

        this.validatedBeanNames.add(beanName);
    }

    return pvs;
}
      

調用InjectionMetadata中定義的inject方法:

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Collection<InjectionMetadata.InjectedElement> checkedElements = this.checkedElements;
    Collection<InjectionMetadata.InjectedElement> elementsToIterate = checkedElements != null ? checkedElements : this.injectedElements;
    if (!((Collection)elementsToIterate).isEmpty()) {
        Iterator var6 = ((Collection)elementsToIterate).iterator();

        while(var6.hasNext()) {
            InjectionMetadata.InjectedElement element = (InjectionMetadata.InjectedElement)var6.next();
            element.inject(target, beanName, pvs);
        }
    }
}
      

進行周遊,然後調用inject方法,inject方法其實作邏輯如下:

protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) throws Throwable {
    if (this.isField) {
        Field field = (Field)this.member;
        // 暴力破解的方法,通過反射技術對對象進行執行個體化和指派
        ReflectionUtils.makeAccessible(field);
        field.set(target, this.getResourceToInject(target, requestingBeanName));
    } else {
        if (this.checkPropertySkipping(pvs)) {
            return;
        }

        try {
            Method method = (Method)this.member;
            ReflectionUtils.makeAccessible(method);
            // 注入的bean的名字,這個方法的功能就是根據這個bean的名字去拿到它
            method.invoke(target, this.getResourceToInject(target, requestingBeanName));
        } catch (InvocationTargetException var5) {
            throw var5.getTargetException();
        }
    }
}
      
  • 使用了反射技術,分成字段和方法去處理的。
  • makeAccessible這樣的可以稱之為暴力破解的方法,通過反射技術對對象進行執行個體化和指派
  • getResourceToInject方法的參數就是要注入的bean的名字,這個方法的功能就是根據這個bean的名字去拿到它

四、總結

@AutoWired自動注入過程圖: