一、隐式扫描不到 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实例
//通过在`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;
}
}
当对这个获取实例时,会走一个CGLIB 搞出的类
进行代理拦截,所以在被@Lookup注解标记的方法中随便怎么写都行,这不太重要。