• Spring编程常见错误50例-Spring Bean定义常见错误


    隐式扫描不到Bean的定义

    问题

    当启动类放置在com.psj.A包中,控制器类放置在com.psj.B包中时,启动SpringBoot后是扫描不到控制器的


    原因

    SpringBoot的默认扫描规则是扫描启动类所在的包及其子包中是否存在控制器


    解决方式
    @SpringBootApplication
    // @ComponentScan("com.psj.B")  // 显式指定其它包,原来的默认扫描包就被忽略,即扫描不到com.psj.A下的控制器
    @ComponentScans(value = { @ComponentScan(value ="com.psj.B") })
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    定义的Bean缺少隐式依赖

    问题

    运行下面代码会出现错误:Parameter 0 of constructor in com.psj.demo.service.impl.ServiceImpl required a bean of type 'java.lang.String' that could not be found.

    @Service
    public class ServiceImpl {
         private String serviceName;
         public ServiceImpl(String serviceName){
             this.serviceName = serviceName;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    原因
    • 当创建一个Bean时调用的方法是AbstractAutowireCapableBeanFactory#createBeanInstance,它主要包含两大基本步骤:

      • 寻找构造器:Spring会先执行determineConstructorsFromBeanPostProcessors方法来获取构造器

      • 通过反射调用构造器创建实例:通过autowireConstructor方法带着构造器去创建实例

        • 调用instantiate方法时需要获取参数argsToUse
        • argsToUse通过调用createArgumentArray方法来构建调用构造器的参数数组,该方法的最终实现是从BeanFactory中获取Bean
        argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames,
        								getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1);
        
        • 1
        • 2
    • 定义一个类为 Bean后如果再显式定义构造器,则该Bean在构建时会自动根据构造器参数定义寻找对应的Bean,然后反射创建这个Bean


    解决方式
    // 在启动类中添加下面代码
    @Bean
    public String serviceName() {
        return "psj";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    原型Bean被固定

    • 在Spring框架中,Bean的生命周期由实例化、属性赋值和初始化/销毁三个主要阶段组成,而Bean的作用域是用来定义Bean的生命周期

    • Spring中的原型Bean(prototype Bean)是一种作用域,意味着每次需要一个新的Bean时,Spring都会创建一个新的实例。这和单例Bean(singleton Bean)不同,单例Bean意味着无论你请求多少次,Spring都只创建一个Bean实例。

    • 在Spring中可通过在Bean的配置类上添加@Scope("prototype")注解或在XML配置文件中设置元素的scope属性为prototype将Bean的作用域设置为原型

    • 使用原型Bean的主要好处是减少对象创建和销毁的开销(每次创建新的Bean实例则不需要额外的内存保存已创建的实例,也不需要额外的开销来处理这些实例的销毁),在处理大量对象或需要频繁创建新实例的情况下尤其有用。缺点是增加了应用程序的内存消耗(每个请求都会创建新的Bean实例)

    • 当使用原型Bean时,如果Bean中包含任何依赖则每次请求新的Bean实例时,这些依赖也需被重新创建


    问题

    通过下面代码定义Bean为原型Bean后,每次访问服务使用的Bean都是一样的,相当于创建的是单例Bean:

    @Service
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public class ServiceImpl {}
    
    • 1
    • 2
    • 3
    @RestController
    public class HelloWorldController {
         @Autowired
         private ServiceImpl serviceImpl;
         @RequestMapping(path = "hi", method = RequestMethod.GET)
         public String hi(){
            return "helloworld, service is : " + serviceImpl;
         };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    原因
    • 当属性成员serviceImpl声明为@Autowired后,在创建HelloWorldController Bean时,会先使用构造器反射出实例,然后来装配各个标记为 @Autowired的属性成员

    • 在执行过程会使用很多BeanPostProcessor来做完成工作,其中一种是AutowiredAnnotationBeanPostProcessor,它会寻找到ServiceImpl类型的

      Bean然后设置给对应serviceImpl成员

    • 由于field.set()方法的执行只发生了一次,后续就固定起来了,它并不会因为ServiceImpl标记了SCOPE_PROTOTYPE而改变

    @Override
    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
        Field field = (Field) this.member;
        Object value;
        // 寻找bean
        if (this.cached) {
            try {
                value = resolveCachedArgument(beanName, this.cachedFieldValue);
            }
            catch (BeansException ex) {
                // Unexpected target bean mismatch for cached argument -> re-resolve
                this.cached = false;
                logger.debug("Failed to resolve cached argument", ex);
                value = resolveFieldValue(field, bean, beanName);
            }
        }
        else {
            value = resolveFieldValue(field, bean, beanName);
        }
        if (value != null) {
    		// 将bean设置给成员字段
            ReflectionUtils.makeAccessible(field);
            field.set(bean, value);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 当一个单例的 Bean,使用autowired注解标记其属性时,该属性值会被固定下来

    解决方式
    // 思想:保证每次访问接口都会创建新的Bean
    @RestController
    @Slf4j
    public class HelloWorldController {
        @Autowired
        private ApplicationContext applicationContext;
    
        @RequestMapping(path = "hi", method = RequestMethod.GET)
        public String hi(){
             return "helloworld, service is : " + getServiceImpl();
        };
    
        // 方法1:自动注入Context
        public ServiceImpl getServiceImpl(){
        	return applicationContext.getBean(ServiceImpl.class);
        }
        
        // 方法2:使用@Lookup,通过该注解产生一个CGLIB子类,即创建一个新的Bean
        // 该方法体中的内容并不会被执行,内容是什么并不重要
        @Lookup
        public ServiceImpl getServiceImpl(){
            
            log.info("executing this method");
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    参考

    极客时间-Spring 编程常见错误 50 例
    https://github.com/jiafu1115/springissue/tree/master/src/main/java/com/spring/puzzle/class1

  • 相关阅读:
    携创教育:速速收藏!2022成人高考大纲来了
    java ssm mysql关于Access denied问题的解决办法
    【JavaWeb】虚拟路径和虚拟主机
    手动实现Transformer
    「Verilog学习笔记」用3-8译码器实现全减器
    下拉框判断是否重复选中值
    Allegro给pin添加花连属性操作指导
    【电力系统】热电联产机组优化调度问题附matlab代码
    ts学习03-函数
    mybatis选定的字段更新写法
  • 原文地址:https://blog.csdn.net/qq_41398418/article/details/132744487