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

blog54

1、概述

【1】註解用法java

根據@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;
}
複製代碼

相關注解:markdown

  • @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】自動裝配app

Spring利用依賴注入(DI)完成對IOC容器中各個組件的依賴關係賦值ide

@Autowired自動注入(Spring提供的):oop

  • 默認優先按照去容器中找對應的組件:applicationContext.getBean()
  • 若是找到多個相同類型的組件,再將屬性的名稱做爲組件的ID去容器中查找
  • @Qualifier()註解:該註解指定須要裝配的組件ID,而不是使用屬性名
  • 自動裝配默認必需要對屬性賦值,沒有就會報錯,可使用@Autowired(required = false)指定非必須就不會報錯
  • @Primary註解:自動裝配時當出現多個bean候選者時,被註解爲@Primary的bean將做爲首選者,不然將拋出異常,若是使用了@Qualifier()指定裝配的bean,則仍是使用明確指定裝配的bean

@Resource(JSR250)和@Inject(JSR330)(JDK提供的)源碼分析

@Resource:post

  • 默認按照組件名稱進行裝配,也能夠指定名稱進行裝配
  • 當找不到與名稱匹配的bean會按類型裝配
  • 不支持@Primary@Autowired(required = false)功能
  • 若是同時指定了name和type,則從Spring上下文中找到惟一匹配的bean進行裝配,找不到則拋出異常。
  • 若是指定了name,則從上下文中查找名稱(id)匹配的bean進行裝配,找不到則拋出異常。
  • 若是指定了type,則從上下文中找到相似匹配的惟一bean進行裝配,找不到或是找到多個,都會拋出異常。
  • 若是既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;若是沒有匹配,則回退爲一個原始類型進行匹配,若是匹配則自動裝配。

@Inject:ui

  • 須要導入javax.inject的包,和Autowired功能同樣,可是沒有required=false功能

【3】@Autowired和@Resource註解的區別this

  • @Autowired由Spring提供,只按照byType注入;@Resource由J2EE提供,默認按照byName自動注入,當找不到與名稱匹配的bean會按類型裝配
  • @Autowired默認按類型裝配,默認狀況下必需要求依賴對象存在,若是要容許null值,能夠設置它的required屬性爲false。若是想使用名稱裝配能夠結合@Qualifier註解進行使用。
  • @Resource,默認按照名稱進行裝配,名稱能夠經過name屬性進行指定,若是沒有指定name屬性,當註解寫在字段上時,默認取字段名進行名稱查找。若是註解寫在setter方法上默認取屬性名進行裝配。當找不到與名稱匹配的bean時才按照類型進行裝配。可是須要注意的是,若是name屬性一旦指定,就只會按照名稱進行裝配。

2、實例分析

這裏只對@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‘:

image-20210313102341875

  • 若是將@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方式進行裝配;若是沒有匹配,則回退爲一個原始類型進行匹配,若是匹配則自動裝配。

3、源碼追蹤

這裏對@Autowired註解底層進行源碼分析

參考:blog.csdn.net/topdevelope…

@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的名字去拿到它

4、總結

@AutoWired自動注入過程圖:

image-20210313173345709

相關文章
相關標籤/搜索