• Spring--getBean()与@Autowired的对比


    原文网址:Spring--getBean()与@Autowired的对比_IT利刃出鞘的博客-CSDN博客

    简介

    说明

    本文介绍getBean()与@Autowired的对比。

    ApplicationContext#getBean()与@Autowired的简要对比

    @AutowiredgetBean()
    是否触发依赖注入

    是。

    如果注入的对象还未注册到容器,则会先注册它。

    否。

    如果注入的对象还未注册到容器,不会去注册它,只会获得一个null。

    单例与多例

    默认是单例。

    就算是将bean加注解改为多例,此时注入仍为单例。

    默认是单例。

    但可将bean加注解改为多例,此时getBean()获取即为多例。

    单例与多例

    注意:这里可能是因为@Controller注入比较特殊,只有这时将bean加注解改为多例,此时注入仍为单例。不过还需要验证。

    @Autowired实例

    1. package com.example.controller;
    2. import com.example.service.UserService;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.web.bind.annotation.GetMapping;
    5. import org.springframework.web.bind.annotation.RequestMapping;
    6. import org.springframework.web.bind.annotation.RestController;
    7. @RestController
    8. @RequestMapping("/hello")
    9. public class HelloController {
    10. @Autowired
    11. private UserService userService;
    12. @GetMapping("/test1")
    13. public String test1() {
    14. return userService.toString();
    15. }
    16. @GetMapping("/test2")
    17. public String test2() {
    18. return userService.toString();
    19. }
    20. }
    1. package com.example.service;
    2. import org.springframework.stereotype.Component;
    3. @Component
    4. public class UserService {
    5. }

    测试(都是单例的)

    http://localhost:8080/test1    结果:com.example.service.UserService@27a70937
    http://localhost:8080/test2    结果:com.example.service.UserService@27a70937
    再次http://localhost:8080/test2    结果:com.example.service.UserService@27a70937

    将UserService改为多例

    1. package com.example.service;
    2. import org.springframework.beans.factory.config.ConfigurableBeanFactory;
    3. import org.springframework.context.annotation.Scope;
    4. import org.springframework.stereotype.Component;
    5. @Component
    6. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    7. public class UserService {
    8. }

    测试(还是单例的)

    http://localhost:8080/test1    结果:com.example.service.UserService@12d4c297
    http://localhost:8080/test2   结果:com.example.service.UserService@12d4c297
    再次http://localhost:8080/test2    结果:com.example.service.UserService@12d4c297

    ApplicationContext实例

    1. package com.example.controller;
    2. import com.example.service.UserService;
    3. import org.springframework.beans.BeansException;
    4. import org.springframework.context.ApplicationContext;
    5. import org.springframework.context.ApplicationContextAware;
    6. import org.springframework.web.bind.annotation.GetMapping;
    7. import org.springframework.web.bind.annotation.RestController;
    8. @RestController
    9. public class HelloController implements ApplicationContextAware {
    10. private ApplicationContext applicationContext;
    11. @GetMapping("/test1")
    12. public String test1() {
    13. UserService userService = applicationContext.getBean(UserService.class);
    14. return userService.toString();
    15. }
    16. @GetMapping("/test2")
    17. public String test2() {
    18. UserService userService = applicationContext.getBean(UserService.class);
    19. return userService.toString();
    20. }
    21. @Override
    22. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    23. this.applicationContext = applicationContext;
    24. }
    25. }
    1. package com.example.service;
    2. import org.springframework.stereotype.Component;
    3. @Component
    4. public class UserService {
    5. }

    测试(都是单例的)

    http://localhost:8080/test1    结果:com.example.service.UserService@27a70937
    http://localhost:8080/test2    结果:com.example.service.UserService@27a70937
    再次http://localhost:8080/test2    结果:com.example.service.UserService@27a70937

    1. package com.example.service;
    2. import org.springframework.beans.factory.config.ConfigurableBeanFactory;
    3. import org.springframework.context.annotation.Scope;
    4. import org.springframework.stereotype.Component;
    5. @Component
    6. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    7. public class UserService {
    8. }

    将UserService改为多例

    1. package com.example.service;
    2. import org.springframework.beans.factory.config.ConfigurableBeanFactory;
    3. import org.springframework.context.annotation.Scope;
    4. import org.springframework.stereotype.Component;
    5. @Component
    6. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    7. public class UserService {
    8. }

    测试(是多例了)

    http://localhost:8080/test1    结果:com.example.service.UserService@393b1c
    http://localhost:8080/test2     结果:com.example.service.UserService@23c2e5c2
    再次http://localhost:8080/test2     结果:com.example.service.UserService@5e743c55

    触发依赖注入

    结论

    @Autowired可以触发依赖注入,ApplicationContext.getBean()不能触发依赖注入。

    也就是说:如果要获取的那个Bean还不存在,@Autowired方式可以去转而实例化那个对象,从而能获取Bean;ApplicationContext.getBean()不能转而去实例化对象,只能获得null。

    问题复现

    目标:在Controller中注入ApplicationEventPublisher(Spring的事件发布器)。

    本处使用@Autowired是可以获取的,但使用ApplicationContext.getBean()就不可以。

    @Autowired注入的方式

    1. package com.example.controller;
    2. import org.springframework.beans.factory.annotation.Autowired;
    3. import org.springframework.context.ApplicationEventPublisher;
    4. import org.springframework.web.bind.annotation.GetMapping;
    5. import org.springframework.web.bind.annotation.RestController;
    6. @RestController
    7. public class HelloController {
    8. @Autowired
    9. private ApplicationEventPublisher applicationEventPublisher;
    10. @GetMapping("/test1")
    11. public String test1() {
    12. System.out.println(applicationEventPublisher);
    13. return "test1 success";
    14. }
    15. }

    访问:http://localhost:8080/test1

    结果(成功注入)

    org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@5bf0fe62, started on Thu Mar 18 19:10:33 CST 2021

    ApplicationContext.getBean()方式

    1. package com.example.controller;
    2. import org.springframework.beans.BeansException;
    3. import org.springframework.context.ApplicationContext;
    4. import org.springframework.context.ApplicationContextAware;
    5. import org.springframework.context.ApplicationEventPublisher;
    6. import org.springframework.web.bind.annotation.GetMapping;
    7. import org.springframework.web.bind.annotation.RestController;
    8. @RestController
    9. public class HelloController implements ApplicationContextAware {
    10. private ApplicationEventPublisher applicationEventPublisher;
    11. @GetMapping("/test1")
    12. public String test1() {
    13. System.out.println(applicationEventPublisher);
    14. return "test1 success";
    15. }
    16. @Override
    17. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    18. applicationEventPublisher = applicationContext.getBean(ApplicationEventPublisher.class);
    19. }
    20. }

    启动时就报错(找不到这个bean)

    1. Description:
    2. A component required a bean of type 'org.springframework.context.ApplicationEventPublisher' that could not be found.
    3. Action:
    4. Consider defining a bean of type 'org.springframework.context.ApplicationEventPublisher' in your configuration.

    原因初探

    refresh()// AbstractApplicationContext
        prepareBeanFactory //AbstractApplicationContext
            beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
            beanFactory.registerResolvableDependency(ResourceLoader.class, this);
            beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
            beanFactory.registerResolvableDependency(ApplicationContext.class, this);
                // DefaultListableBeanFactory类(实现了ConfigurableListableBeanFactory接口)
                registerResolvableDependency(Class dependencyType, @Nullable Object autowiredValue)
                    resolvableDependencies.put(dependencyType, autowiredValue); //DefaultListableBeanFactory类
                    
    private final Map, Object> resolvableDependencies = new ConcurrentHashMap<>(16);

    一些特殊实例对象是存放在DefaultListableBeanFactory#resolvableDependencies变量中的,在容器启动时,如果发现需要注入这些特定的实例对象,就直接在该变量中获取。通过BeanFactory#getBean方法不会去DefaultListableBeanFactory#resolvableDependencies取,所以取不到它。

    处理注入的代码:findAutowireCandidates //DefaultListableBeanFactory.java

    1. for (Map.Entry, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
    2. Class autowiringType = classObjectEntry.getKey();
    3. if (autowiringType.isAssignableFrom(requiredType)) {
    4. Object autowiringValue = classObjectEntry.getValue();
    5. autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
    6. if (requiredType.isInstance(autowiringValue)) {
    7. result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
    8. break;
    9. }
    10. }
    11. }

    编写程序复现

    1. package com.example.tmp;
    2. import org.springframework.beans.BeansException;
    3. import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    4. import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    5. import org.springframework.stereotype.Component;
    6. @Component
    7. public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    8. @Override
    9. public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    10. beanFactory.registerResolvableDependency(MyBean.class, new MyBean());
    11. }
    12. }
    1. package com.example.tmp;
    2. public class MyBean {
    3. }

    使用@Autowired注入

    1. package com.example.controller;
    2. import com.example.tmp.MyBean;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.web.bind.annotation.GetMapping;
    5. import org.springframework.web.bind.annotation.RestController;
    6. @RestController
    7. public class HelloController {
    8. @Autowired
    9. private MyBean myBean;
    10. @GetMapping("/test1")
    11. public String test1() {
    12. System.out.println(myBean);
    13. return "test1 success";
    14. }
    15. }

    访问:http://localhost:8080/test1

    后台结果(成功注入)

    com.example.tmp.MyBean@f08fdce

    使用ApplicationContext#getBean() 

    1. package com.example.controller;
    2. import com.example.tmp.MyBean;
    3. import org.springframework.beans.BeansException;
    4. import org.springframework.context.ApplicationContext;
    5. import org.springframework.context.ApplicationContextAware;
    6. import org.springframework.web.bind.annotation.GetMapping;
    7. import org.springframework.web.bind.annotation.RestController;
    8. @RestController
    9. public class HelloController implements ApplicationContextAware {
    10. private MyBean myBean;
    11. @GetMapping("/test1")
    12. public String test1() {
    13. System.out.println(myBean);
    14. return "test1 success";
    15. }
    16. @Override
    17. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    18. myBean = applicationContext.getBean(MyBean.class);
    19. }
    20. }

    启动即报错(无法获得bean)

    1. Description:
    2. A component required a bean of type 'com.example.tmp.MyBean' that could not be found.
    3. Action:
    4. Consider defining a bean of type 'com.example.tmp.MyBean' in your configuration.

    源码追踪

    追踪1:追踪“编写程序复现”的getBean

    getBean(MyBean.class)
        getBeanFactory().getBean(requiredType) //AbstractApplicationContext.class
            getBean(Class requiredType) //DefaultListableBeanFactory.java

    下边的程序都是DefaultListableBeanFactory中的

    getBean(Class requiredType)
        getBean(requiredType, (Object[]) null)
            resolveBean(ResolvableType.forRawClass(requiredType), args, false)
                resolveNamedBean(requiredType, args, nonUniqueAsNull)
                    getBeanNamesForType(requiredType);
                        getBeanNamesForType(type, true, true)
                            getBeanNamesForType(resolved, includeNonSingletons, allowEagerInit)
                                resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);

    追踪2:正常的bean通过ApplicationContext.getBean()获取

    把上边的程序稍微改造:

    1. package com.example.tmp;
    2. import org.springframework.stereotype.Component;
    3. @Component
    4. public class MyBean {
    5. }
    1. package com.example.controller;
    2. import com.example.tmp.MyBean;
    3. import org.springframework.beans.BeansException;
    4. import org.springframework.context.ApplicationContext;
    5. import org.springframework.context.ApplicationContextAware;
    6. import org.springframework.web.bind.annotation.GetMapping;
    7. import org.springframework.web.bind.annotation.RestController;
    8. @RestController
    9. public class HelloController implements ApplicationContextAware {
    10. private MyBean myBean;
    11. @GetMapping("/test1")
    12. public String test1() {
    13. System.out.println(myBean);
    14. return "test1 success";
    15. }
    16. @Override
    17. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    18. myBean = applicationContext.getBean(MyBean.class);
    19. }
    20. }

    追踪它,跟上边一样的,也是追踪到了DefaultListableBeanFactory#getBeanNamesForType(resolved, includeNonSingletons, allowEagerInit),但这是数据有所变化

    1. public String[] getBeanNamesForType(@Nullable Class type, boolean includeNonSingletons, boolean allowEagerInit) {
    2. if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
    3. return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
    4. }
    5. // includeNonSingletons为true,会使用this.allBeanNamesByType
    6. //此时,cache一项都没有
    7. Map, String[]> cache =
    8. (includeNonSingletons ? this.allBeanNamesByType : this.singletonBeanNamesByType);
    9. // type:“class com.example.tmp.MyBean”,此步resolvedBeanNames为null
    10. String[] resolvedBeanNames = cache.get(type);
    11. // 此判断为false,不会进入
    12. if (resolvedBeanNames != null) {
    13. return resolvedBeanNames;
    14. }
    15. //通过type获得bean的名字。结果为:myBean
    16. resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
    17. if (ClassUtils.isCacheSafe(type, getBeanClassLoader())) {
    18. cache.put(type, resolvedBeanNames);
    19. }
    20. return resolvedBeanNames;
    21. }

    与“追踪1:追踪“编写程序复现”的getBean”的区别在于:doGetBeanNamesForType获取到了bean的名字(也就是说,找到了bean)

    总体流程

    getBean(Class requiredType)
        getBean(requiredType, (Object[]) null)
            resolveBean(ResolvableType.forRawClass(requiredType), args, false)
                // namedBean有两个属性:beanName(String类型),beanInstance(泛型,实例对象)
                NamedBeanHolder namedBean = resolveNamedBean(requiredType, args, nonUniqueAsNull)
                    String[] candidateNames = getBeanNamesForType(requiredType);
                        getBeanNamesForType(type, true, true)
                            getBeanNamesForType(resolved, includeNonSingletons, allowEagerInit)
                                resolvedBeanNames = doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, true);
                    // 调用到AbstractBeanFactory#getBean
                    return new NamedBeanHolder<>(beanName, (T) getBean(beanName, requiredType.toClass(), args));
                // 获得MyBean实例对象
                namedBean.getBeanInstance();

  • 相关阅读:
    java源码系列:HashMap底层存储原理详解——5、技术本质-原理过程-算法-取模会带来一个什么问题?什么是哈希冲突?为什么要用链表?
    网络安全系列-四十: suricata 的运行模型Running mode讲解
    OpenDataV低代码平台新增组件流程
    力扣刷题 day55:10-25
    Docker部署Go+Mysql+Redis
    Java面试
    单片机设计_RTC时钟(ACM32F403)
    upload-labs通关(Pass06-Pass10)
    Mybatis集成日志组件,logback日志框架
    spring三级缓存
  • 原文地址:https://blog.csdn.net/feiying0canglang/article/details/126244735