《Spring编程常见错误50例》读书笔记
源码构建依赖下载可能遇到的问题:
https://blog.csdn.net/xiangxiaotian666/article/details/127399904

Spring Core
- 工厂 BeanFactory 内部有 Map
- Scan 负责扫描需要实例化的 Bean
- 最终使用反射的方式实例化:
- java.lang.Class.newInsance();
- java.lang.reflect.Constructor.newInstance();
- ReflectionFactory.newConstructorForSerialization();
- AOP 使用多态代理实现,需要代理哪些类就使用 @Aspect 中定义的 切点 来表示
Bean 定义
隐式扫描不到 Bean 的定义
:::color4
定义的类没有写到 Application 同级或者下级目录的时候就找不到 Bean 定义
:::
解决
可以在 Application 上写上:
@ComponentScans(value = { @ComponentScan(value ="com.spring.puzzle.class1.example1.controller") })-
@ComponentScan可以多个同时使用,且都生效。效果等同于@ComponentScans
@SpringBootApplication(scanBasePackages = {"com.xxx.xxxxx","com.xxx.xxx"})
原理
1 2 3 4 5 6 7 8 9 10 11 12
| @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 {
}
|
Spring 默认规则:ComponentScan 注解的 basePackages 属性指定扫描哪些 Bean 定义。没有指定,默认为空(即{})。此时解析启动类(配置类)的时候,就会为其填充默认值:

定义的 Bean 缺少隐式依赖
:::color4
自定义了构造器,但是没有将构造器的入参注入到 spring,导致 spring 使用对应的构造器实例化 Bean 时候找不到参数而失败
:::
1 2 3 4 5 6 7
| @Service public class ServiceImpl { private String serviceName; public ServiceImpl(String serviceName){ this.serviceName = serviceName; } }
|
解决
定义一个Bean,名字就是 serviceName,这个bean装配给ServiceImpl的构造器参数“serviceName”
1 2 3 4
| @Bean public String serviceName(){ return "MyServiceName"; }
|
原理
我们定义一个类为 Spring Bean A,如果再显式定义了构造器,那么这个 Bean A 在构建时,会自动根据构造器参数定义寻找对应类型的 Bean B,然后反射创建出这个 Bean B 作为 A 构造器的入参去反射创建 Bean A。

自定义多个构造器没有指定优先级
存在两个构造器,都可以调用时,最终 Spring 无从选择,只能尝试去调用默认构造器,而这个默认构造器又不存在,所以测试这个程序它会出错。
1 2 3 4 5 6 7 8 9 10
| @Service public class ServiceImpl { private String serviceName; public ServiceImpl(String serviceName){ this.serviceName = serviceName; } public ServiceImpl(String serviceName, String otherStringParameter){ this.serviceName = serviceName; } }
|
原型Bean不能如你所愿每次都创建
:::color4
定义了一个原型的 Bean A,在另外一个 Bean B 中使用 @Autowired 注入之后,希望每次调用都是一个新的对象,但是其实是同一个对象。因为 Bean B 其实只实例化了一次,A 作为被依赖方只有在 B 实例化的时候被调用
:::
1 2 3 4
| @Service @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class ServiceImpl { }
|
1 2 3 4 5 6 7 8 9 10 11
| @RestController public class HelloWorldController {
@Autowired private ServiceImpl serviceImpl;
@RequestMapping(path = "hi", method = RequestMethod.GET) public String hi(){ return "helloworld, service is : " + serviceImpl; }; }
|
访问多少次,结果都是一样的
解决
不能将 ServiceImpl 的 Bean 固定到属性上的,而应该是每次使用时都会重新获取一次。
方式一:每次从 ApplicationContext 中获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @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); } }
|
方式二: 使用 @Lookup
1 2 3 4 5 6 7 8 9 10 11 12 13
| @RestController public class HelloWorldController { @RequestMapping(path = "hi", method = RequestMethod.GET) public String hi(){ return "helloworld, service is : " + getServiceImpl(); };
@Lookup public ServiceImpl getServiceImpl(){ return null; } }
|
https://blog.csdn.net/qq_25863845/article/details/123475147
标记了 Lookup 的方法最终走入了CGLIB 搞出的类 CglibSubclassingInstantiationStrategy.LookupOverrideMethodInterceptor,这个方法的关键实现参考 LookupOverrideMethodInterceptor#intercept。方法调用最终并没有走入案例代码实现的return null语句,而是通过 BeanFactory 来获取 Bean。其实 **getServiceImpl** 方法实现中,随便怎么写都行。
为什么会使用cgLib?是因为在初始化实例的时候(参考 SimpleInstantiationStrategy#instantiate),会发现有方法标记了 Lookup,此时就会添加相应方法到属性methodOverrides 里面去(此过程由 AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors 完成),Lookup 会重写方法。也就是说方法内部的实现,在Bean被实例化的时候就被替换成 CGLIB 的实现了。
1 2 3 4 5 6 7 8 9 10 11 12
| @Override public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { if (!bd.hasMethodOverrides()) { return BeanUtils.instantiateClass(constructorToUse); } else { return instantiateWithMethodInjection(bd, beanName, owner); } }
|
原理
找到要自动注入的 Bean 后,通过反射设置给对应的field。这个field的执行只发生了一次,所以后续就固定起来了,并不会因为 ServiceImpl 标记了 SCOPE_PROTOTYPE 而改变。所以我们请求多少次都不会重新去Spring容器获取 ServiceImpl,而是直接获取 Controller 引用的那个对象。

注入
required a single bean, but 2 were found
:::danger
@Autowired 的一个接口有多个实现的时候,就无法工作了
:::
1 2 3 4 5 6 7 8 9 10 11 12
| @RestController @Slf4j @Validated public class StudentController { @Autowired DataService dataService;
@RequestMapping(path = "students/{id}", method = RequestMethod.DELETE) public void deleteStudent(@PathVariable("id") @Range(min = 1,max = 100) int id){ dataService.deleteStudent(id); }; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public interface DataService { void deleteStudent(int id); }
@Repository @Slf4j public class OracleDataService implements DataService{ @Override public void deleteStudent(int id) { log.info("delete student info maintained by oracle"); } }
@Repository @Slf4j public class CassandraDataService implements DataService{ @Override public void deleteStudent(int id) { log.info("delete student info maintained by cassandra"); } }
|
解决
让候选实现类具有优先级或压根可以不去选择。
方式一:设置优先级
1 2 3 4 5 6
| @Repository @Primary @Slf4j public class OracleDataService implements DataService{ }
|
方式二:指定精确 Bean名字
将属性名和Bean名字精确匹配,这样就可以让注入选择不犯难
1 2
| @Autowired DataService oracleDataService;
|
方式三:使用 @Qualifier 显式指定使用哪种
1 2 3
| @Autowired() @Qualifier("cassandraDataService") DataService dataService;
|
方式四:使用策略模式存到自己定义的 map 中
自己定义一个策略类,实现 InitializingBean