代码演示
给定一个类 One,然后看下的几种构造方法什么时候被调用
1、假设现在只有一个默认的空构造方法,代码如下:
@Component
public class One {
}
然后追踪源码,如下所示:
先拿到所有声明的构造方法
然后挨个判断构造方法上是否标注了注解 @Autowired、@Value 注解
AutowiredAnnotationBeanPostProcessor 类在空构造方法中就已经默默的把这两个注解放入到了内存中
如果构造方法中没标注上述两个注解,并且构造方法中也没有参数,那么就将这个 candidate 候选的构造方法对象赋值给 defaultConstructor 默认的构造方法对象
最后就是一堆的判断条件,如下所示:
现在这里假设的是默认的空参数构造方法,所以只能走到 else 逻辑,直接 new 一个数组,不过没啥作用,最终返回的是 null,因为 candidateConstructors 集合中并没有保存对象。
继续回到外面调用处,如下所示:
determineConstructorsFromBeanPostProcessors() 返回 null,表示没有找到构造方法,那么就只能走最后的兜底逻辑,也就是采用无参构造方法进行实例化 One 类了。
最后构造方法的反射实例化 One 类。
总结:其实因为类里面不显示书写构造方法的话,默认其实也是有一个无参构造方法存在的,不然你怎么创建对象,那没有构造方法你怎么去实例化对象。从而推断出是不是类里面只要有并且只有1个构造方法存在的时候,不管你是不是无参还是有参,其实从人类的认知里面,只有1个,那么肯定就用你嘛,别无选择。所以 Spring
里面也是这样的,合乎常理。
现在我就把只写一个有参数的构造方法,如下所示:
@Component
public class One {
public One(Two tw) {
System.out.println("带有 @Autowired 注解的1个有参构造方法 tw==" + tw);
}
}
测试后之后很显然是成功的,结果可以正常打印,One 也可以正常实例化,那分析下源码怎么走的,如下所示:
先拿到 One 类中所有的构造方法,目前仅有1个,如下所示:
然后挨个遍历构造方法,查找是否被 @Autowired 、@Value 注解修饰
最后就是一堆的判断条件,如下所示:
可以发现 else if() 逻辑成立,刚好就一个构造方法,并且还是一个有参的构造方法,所以最终 return 出去的就是这个有参构造方法。
然后直接进入 autowireConstructor() 方法,里面会把参数进行实例化,主要调用 getBean(),参数实例化好了之后,在通过反射调用构造方法实例化 One。
参数的实例化源码如下:
参数实例化完成之后,通过有参构造方法的调用实例化 One。
总结:只有一个构造方法时,Spring 只会认识这1个构造方法,并且也会把参数实例化。就和加了 @Autowired 注解一个效果感觉。
2、假设一个无参构造方法,还有一个1个参数的有参构造方法,如下所示:
@Component
public class One {
public One() {
System.out.println("空的不带 @Autowired 注解的构造方法");
}
public One(Two tw) {
System.out.println("带有 @Autowired 注解的1个有参构造方法 tw==" + tw);
}
}
Spring 会选择哪个构造方法进行实例化呢?继续追踪源码,如下:
调用 determineConstructorsFromBeanPostProcessors() 方法取选择用哪个构造方法进行实例化 bean
获取 One 类的所有构造方法,发现有两个,如下所示:
findAutowiredAnnotation() 挨个判断构造方法是否被 @Autowired、@Value 注解修饰,很显然目前这里还没有修饰,所以 ann 结果为 null
将无参构造方法赋值给变量 defaultConstructor ,表示这是无参构造方法,但是并没有什么意义说实话感觉
最终也是要走这一段逻辑判断,最终结果返回 null
继续回到外面调用处,如下所示:
determineConstructorsFromBeanPostProcessors() 返回 null,表示没有找到构造方法,那么就只能走最后的兜底逻辑,也就是采用无参构造方法进行实例化 One 类了
最后构造方法的反射实例化 One 类。
总结:当你有多个构造方法并且没有标注 @Autowired 、@Value 注解时,Spring 最终只会认识无参构造方法,此时如果没有无参构造方法,那么必然就不能够实例化,最终报错。
那么此时我就是不想用这个空构造方法,我就是想用这个有参构造方法,该怎么办么呢?这个时候就可以加上 @Autowired 注解,加上这个注解,Spring 就只会认这个有注解修饰的构造方法,其他的不会用了。
3、多个构造方法时,指定具体的构造方法实例化 bean,如下所示:
指定 Spring 用1个参数的构造方法实例化 One bean
@Component
public class One {
public One() {
System.out.println("空的不带 @Autowired 注解的构造方法");
}
public One(Two tw, Three th) {
System.out.println("带有 @Autowired 注解的2个有参构造方法 tw==" + tw + ",th=" + th);
}
@Autowired
public One(Two tw) {
System.out.println("带有 @Autowired 注解的1个有参构造方法 tw==" + tw);
}
}
分析下源码如下:
查找有没有可以使用的构造方法
获取到 One 类中所有的构造方法
挨个遍历构造方法是否被 @Autowired 注解修饰,目前这里只有1个被注解修饰
找到一个被 @Autowired 注解修饰的构造方法,然后加入到 candidates 候选集合中,并且把 requiredConstructor 变量赋值成了当前这个被 @Autowired 注解修饰的构造方法,为什么要赋值这个 requiredConstructor 变量呢?
为什么要赋值这个 requiredConstructor 变量呢?是因为怕你有很多构造方法时,你都给加上了 @Autowired 注解,这样对于 Spring 其实又不知道该怎么选择了,其实也合乎常理,换做是你,你也选择不知道用哪个。
所以直接就给你抛异常,除非你在把所有的注解 @Autowired(required = false) 这样就不会抛异常了,但是这样 Spring 只能自己去选择参数最多的那个构造方法进行实例化操作了,没有任何意义说实话。
话题扯回来,继续跟踪源码,又回到这一堆的判断逻辑,不过这个时候,if 判断条件成立,candidates 集合不为空,因为找到了被 @Autowired 注解修饰的构造方法,最后将这个构造方法对象返回出去
最终进入 autowirdConstructor() 方法
这是对构造方法中参数的赋值操作
最后在通过构造方法反射出 One 的实例。
注意在构造方法中出现了循环依赖,会抛出异常,如下所示:
@Component
public class One {
public One(Two two) {
System.out.println("空的不带 @Autowired 注解的构造方法 two="+two);
}
}
@Component
public class Two {
public Two(One one) {
System.out.println("空的不带 @Autowired 注解的构造方法 one="+one);
}
}
因为此时并没有实例化完成,还没有使用到三级缓存,Spring 中解决循环依赖是依赖三级缓存,所以在这里就会出现异常,异常如下:
另外,我们可以通过构造方法给 Controller 注入 Service 值,如下所示:
@Controller
public class HelloController {
private final HelloService helloService;
public HelloController(HelloService helloService) {
this.helloService = helloService;
System.out.println("======>"+this.helloService);
}
}
@Service
public class HelloService {
}
目前这种方式注入是市面上也比较常用的,也是比 @Autowired 注入方式更友好的,毕竟再也不用看黄色警告⚠️了
总结:
1、当有且仅有1个构造方法时,Spring 都只会使用这个构造方法创建实例。
2、Spring 优先选择 被 @Autowired 标注的构造方法
3、当有多个构造方法同时存在时,Spring 默认选择空构造方法,若此时没有空构造方法,就会报错。
4、当有多个方法同时存在时,想指定 Spring 具体用哪个构造方法,可以加上 @Autowired 注解来标识,如果此时有多个 @Autowired 同时存在,需要将所有的 required 修改成 false ,Spring 默认使用参数最多的构造方法