一、隐式扫描不到 Bean 的定义

针对Spring那必然会说到约定大于配置,当我们启动一个springboot的主启动类时,可以让整个项目启动,装载Bean实例。
当主启动类的位置发生了变化了之后,就会出现Bean找不到了,或者某某controller对应的接口找不到了等。。。

那在springboot的主启动类上面会标有SpringBootApplication的注解,其他的一个复合注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

在其中有ComponentScan注解,有一个basePackages属性,这个属性他的默认值为{}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
	@AliasFor("basePackages")
	String[] value() default {};
	...//省略
}

也就是我们默认在启动我们springboot的主启动类的时候basePackages属性是空的,那么在源码里面他会怎么做呢?
他会在ComponentScanAnnotationParser#parse 方法中去判断,如果为空,就去获取这个主启动类所在的包的位置,然后就去装载同包下面的Bean实例

spring boot 一直提示找不到模板文件 spring boot 找不到bean_反射

//通过在`basePackages属性`中显示的指定我们要加载Bean实例的位置
@ComponentScan(basePackages = {"com.example.spring_common_error_case"})
  • ComponentScan 一旦显式指定其它包,原来的默认扫描包就被忽略了。
  • ComponentScans 支持多个包的扫描范围指定。

二、定义的 Bean 缺少隐式依赖

当使用spring的@Service注解,来指明他是被spring管理的,但又在类中显示的写明了一个有参构造器

@Service
public class ServiceImpl {
    private String serviceName;
    
    public ServiceImpl(String serviceName){
        this.serviceName = serviceName;
    }
}

他有时候就会报错:说有参构造器的入参找不到

Parameter 0 of constructor in com.spring.puzzle.class1.example2.ServiceImpl required a bean of type ‘java.lang.String’ that could not be found.

在spring中,他会找这个bean对应的构造器,并通过反射构造出这个实例,并放置spring中管理

我们不能直接显式使用 new 关键字来创建实例。Spring 只能是去寻找依赖来作为构造器调用参数。
所以如果要上面不报错,就要在容器中加入serviceName这个参数,让spring去使用

//这个bean装配给ServiceImpl的构造器参数“serviceName”
@Bean
public String serviceName(){
    return "MyServiceName";
}

当我们显示的指定了两个构造器:无参构造器、有参构造器

@Service
public class ServiceImpl {
    private String serviceName;
    public ServiceImpl(String serviceName){
        this.serviceName = serviceName;
    }
    public ServiceImpl(String serviceName, String otherStringParameter){
        this.serviceName = serviceName;
    }
}

那么spring就会不知道使用,则默认的去使用无参构造器注入


三、原型 Bean 被固定

当我们在bean上面通过@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE),想要通过这个方式去每次获取一个多例的Bean实例

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceImpl {
}

其实这样子是不可以的,这种方式会在bean实例被创建的时候就一次性放入到容器中了,所以在后续拿的情况还会是单例

1、自动注入 Context

通过这种方式,每次去拿这个bean,就会是多例的,每次不一样

@RestController
public class HelloWorldController {

    @Autowired
    private ApplicationContext applicationContext;

    @RequestMapping(path = "hi", method = RequestMethod.GET)
    public String hi(){
         return "helloworld, service is : " + getServiceImpl();
    };
 
    public ServiceImpl getServiceImpl(){
        return applicationContext.getBean(ServiceImpl.class);
    }

}

2、使用 Lookup 注解

或者通过 Lookup 注解\

@RestController
public class HelloWorldController {
 
    @RequestMapping(path = "hi", method = RequestMethod.GET)
    public String hi(){
         return "helloworld, service is : " + getServiceImpl();
    };

    @Lookup
    public ServiceImpl getServiceImpl(){
        return null;
    }  

}

spring boot 一直提示找不到模板文件 spring boot 找不到bean_springboot_02

当对这个获取实例时,会走一个CGLIB 搞出的类进行代理拦截,所以在被@Lookup注解标记的方法中随便怎么写都行,这不太重要。