springboot源码阅读--@autowired注解源码解析

透彻分析一下@autowired底层原理,源码的魅力令人着迷

前置构建阶段

1、首先看看这个类的UML结构,继承了BeanPostProcessor后置处理器类,bean创建会后置处理注解
在这里插入图片描述

2、此类核心方法主要包含postProcessMergedBeanDefinition,可以清晰看到findInjectionMetadata此方法,里面逻辑主要是构建injectionMetadata,构建注入相关的元信息。

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 对于给定的bean名称和类型,它首先尝试查找相关的InjectionMetadata,这可能包含了该bean的字段和方法的注入信息
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    
    // 使用找到的InjectionMetadata来验证bean定义中的配置成员是否与预期的注入元数据匹配。
    metadata.checkConfigMembers(beanDefinition);
}

在这里插入图片描述

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    // 如果beanName为空,则使用类名作为缓存键。
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // 首先尝试从并发缓存中获取InjectionMetadata。
    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);
                }
                // 为给定的类构建新的InjectionMetadata。
                metadata = buildAutowiringMetadata(clazz);
                // 将新构建的元数据更新到缓存中。
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    // 返回找到的或新构建的元数据。
    return metadata;
}

3、查找类及其所有父类中的字段和方法,以找出所有带有自动装配注解的字段和方法,并为它们创建一个统一的InjectionMetadata对象。其中autowiredAnnotationTypes主要包含 @autowired、@value、@Inject等注解方法

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    // 检查类是否含有自动装配注解,若无则直接返回空的InjectionMetadata。
    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) {
                
                // ... [代码部分省略以简化]
                
                boolean required = determineRequiredStatus(ann);
                // 创建一个新的AutowiredFieldElement并加入到列表。
                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))) {
                
                // ... [代码部分省略以简化]
                
                boolean required = determineRequiredStatus(ann);
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                // 创建一个新的AutowiredMethodElement并加入到列表。
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });

        // 将当前类的注入元素加入到总的注入元素列表的开头。
        elements.addAll(0, currElements);
        // 处理父类。
        targetClass = targetClass.getSuperclass();
    }
    // 循环直至Object类。
    while (targetClass != null && targetClass != Object.class);

    // 返回为元素列表创建的新的InjectionMetadata。
    return InjectionMetadata.forElements(elements, clazz);
}

在这里插入图片描述

注入阶段:

1、在创建Bean的属性注入阶段,调用后置处理器中的postProcessProPerties方法
在这里插入图片描述
在这里插入图片描述

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 获取与bean名称和类相关的InjectionMetadata。
    // 这包括该bean需要进行注入的所有字段和方法。
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    
    try {
        // 使用获取到的InjectionMetadata,实际进行属性的注入。
        metadata.inject(bean, beanName, pvs);
    }
    // 如果在注入过程中出现BeanCreationException,直接抛出。
    catch (BeanCreationException ex) {
        throw ex;
    }
    // 捕获其他异常,并以BeanCreationException的形式抛出,提供详细的错误信息。
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    // 返回原始的PropertyValues,因为这个方法主要关注依赖注入而不是修改属性。
    return pvs;
}

2、inject方法中,主要目的是将所有需要注入的元素(例如带有@Autowired等注解的字段或方法)注入到目标bean中。
在这里插入图片描述

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()) {
        // 遍历每个元素并注入到目标bean中。
        for (InjectedElement element : elementsToIterate) {
            // 对每个元素(字段或方法)执行注入操作。
            element.inject(target, beanName, pvs);
        }
    }
}

3、AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject方法中,首先检查字段的值是否已经被缓存。如果已缓存,则从缓存中获取,否则重新解析。然后,它确保字段是可访问的(特别是对于私有字段),并将解析的值设置到目标bean的相应字段中。
在这里插入图片描述

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 步骤1. 获取代表带有@Autowired注解的字段的Field对象。
    Field field = (Field) this.member;

    Object value;
    // 步骤2. 如果字段的值已经被缓存(即先前已解析过),则尝试从缓存中获取。
    if (this.cached) {
        try {
            // 从缓存中获取已解析的字段值。
            value = resolvedCachedArgument(beanName, this.cachedFieldValue);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // 如果缓存中的bean已被意外删除 -> 重新解析。
            value = resolveFieldValue(field, bean, beanName);
        }
    }
    else {
        // 步骤3. 如果字段值未被缓存,直接解析。
        value = resolveFieldValue(field, bean, beanName);
    }

    // 步骤4. 如果解析到的值不为null...
    if (value != null) {
        // 步骤4.1. 使字段可访问,这是必要的,特别是当字段是private时。
        ReflectionUtils.makeAccessible(field);
        // 步骤4.2. 实际将解析的值注入到目标bean的字段中。
        field.set(bean, value);
    }
}

4、resolveFieldValue方法中,通过beanFactory.resolveDependency方法从Spring的bean工厂中解析字段的值
在这里插入图片描述
5、resolveDependency方法中,首先尝试获取一个延迟解析代理。如果无法获得,它会进一步尝试解析依赖。doResolveDependency 是实际进行解析工作的方法。
在这里插入图片描述
6、doResolveDependency方法中尝试解析一个特定的依赖,首先查找所有可能的匹配的 bean,然后选择一个最佳匹配的 bean。如果存在多个匹配的 bean 或没有找到匹配的 bean,它会进行相应的处理。

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
            @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

    // ... [代码部分省略以简化]

    try {
        // 如果存在快捷解决依赖的方法,使用它
        Object shortcut = descriptor.resolveShortcut(this);
        if (shortcut != null) {
            return shortcut;
        }

        // 获取依赖的类型
        Class<?> type = descriptor.getDependencyType();
        
        // ... [代码部分省略以简化]

        // 步骤1. 根据依赖描述符查找匹配的bean
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
        
        // 如果没有找到匹配的bean
        if (matchingBeans.isEmpty()) {
            if (isRequired(descriptor)) {
                // 如果依赖是必需的,抛出异常
                raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
            }
            return null;
        }

        String autowiredBeanName;
        Object instanceCandidate;

        // 当找到多个匹配的bean
        if (matchingBeans.size() > 1) {
            // 确定最佳的自动装配候选者
            autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
            if (autowiredBeanName == null) {
                if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
                    // 如果不能确定唯一的bean,尝试解析不唯一的依赖
                    return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
                }
                else {
                    return null;
                }
            }
            instanceCandidate = matchingBeans.get(autowiredBeanName);
        }
        else {
            // 只找到一个匹配的bean
            Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
            autowiredBeanName = entry.getKey();
            instanceCandidate = entry.getValue();
        }

        // 添加自动装配的bean名到集合
        if (autowiredBeanNames != null) {
            autowiredBeanNames.add(autowiredBeanName);
        }

        // 步骤2. 如果候选者是一个类,实例化它
        if (instanceCandidate instanceof Class) {
            instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
        }
        
        Object result = instanceCandidate;
        
        // ... [代码部分省略以简化]
        
        return result;
    }
    // ... [代码部分省略以简化]
}

7、findAutowireCandidates方法中,首先基于给定的类型获取所有可能的bean名。接着,对于每一个可能的候选bean,它检查该bean是否是一个合适的自动注入候选,如果是,它将这个bean添加到结果集中。最后,方法返回找到的所有合适的候选bean。
在这里插入图片描述
在这里插入图片描述
8、resolveCandidate方法中,最后发现@Autowired 的整个流程最终还是从Spring容器中获取一个bean实例并注入到相应的字段或构造函数参数中。
在这里插入图片描述
9、AutowiredFieldElement#inject 方法内部,通过resolveFieldValue(field, bean, beanName)方法,来确定了正确的bean值并满足某个字段的 @Autowired 注解,将使用反射来实际设置这个值。具体地说,它会使用 Field 类的 set 方法来为目标对象的这个字段设置相应的值。至此注入的链路就完结了
在这里插入图片描述

好的,我可以为你提供一个简单的登录案例,使用Spring Boot框架和相关注解来实现。 首先,我们需要创建一个名为"UserController"的控制器类。该类将处理用户登录请求,并返回相应的响应。 ```java @Controller public class UserController { @Autowired private UserService userService; @PostMapping("/login") @ResponseBody public ResponseEntity<String> login(@RequestBody UserDto userDto) { User user = userService.login(userDto.getUsername(), userDto.getPassword()); if (user != null) { return new ResponseEntity<>("Login Successful", HttpStatus.OK); } else { return new ResponseEntity<>("Login Failed", HttpStatus.UNAUTHORIZED); } } } ``` 在上面的代码中,我们使用了@Controller注解来标记该类为控制器类,@Autowired来注入UserService实例,@PostMapping注解用于处理HTTP POST请求,@ResponseBody注解用于将响应体直接返回给客户端,而无需使用模型和视图。@RequestBody注解用于将请求体转换为UserDto对象,然后我们使用UserService实例进行用户登录验证,如果用户存在,则返回成功响应,否则返回失败响应。 接下来,我们需要创建一个UserService类,该类将处理用户登录逻辑。 ```java @Service public class UserService { @Autowired private UserRepository userRepository; public User login(String username, String password) { User user = userRepository.findByUsername(username); if (user != null && user.getPassword().equals(password)) { return user; } else { return null; } } } ``` 在上面的代码中,我们使用了@Service注解来标记该类为服务类,@Autowired用于注入UserRepository实例,该类将处理用户登录逻辑。我们首先通过调用UserRepository的findByUsername方法来获取用户实例,然后比较用户密码是否与传递的密码相同。如果是,则返回用户实例,否则返回null。 最后,我们需要创建一个名为"UserDto"的数据传输对象类,该类将用于从请求体中提取用户名和密码。 ```java @Data public class UserDto { private String username; private String password; } ``` 在上面的代码中,我们使用了@Data注解来生成getter和setter方法,以及equals、hashCode和toString方法,这些方法将用于从请求体中提取用户名和密码。 最后,我们需要创建一个名为"UserRepository"的接口,该接口将扩展JpaRepository接口,并提供自定义方法来查询用户实例。 ```java @Repository public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); } ``` 在上面的代码中,我们使用了@Repository注解来标记该接口为存储库接口,该接口将扩展JpaRepository接口,并提供自定义方法findByUsername来查询用户实例。 以上就是使用Spring Boot框架和相关注解实现登录案例的全部内容。希望可以对你有所帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值