前言
用过Spring系列的框架的同学们,肯定对@Autowired
注解并不陌生,我们可以通过@Autowired
来对成员变量、方法以及构造函数进行修饰,使Spring对其完成Bean的自动装配功能。
再学校期间,我一直都是将@Autowired
来修饰要使用的变量(毕竟代码量少),例如:
@Autowired
UserService userService;
在最近,我发现公司的项目都不用@Autowired
来修饰成员变量,而是用来修饰构造函数,在构造函数中再去对相应的bean进行赋值,例如:
UserService userService;
@Autowired
UserController(UserService userService){
this.userService = userService;
}
那这两种方案,都有什么区别吗?为什么公司要选择代码量较多的方案呢?
区别
首先,@Autowired
注解都是为了让Spring去完成自动装配的功能,其功能上是没有区别的。因此,我们都可以用两种方法来实现自动装配:
在此案例中,我们使用两种不同的方式,在对UserService进行装配,并验证其功能
@Slf4j
@Service
public class UserService {
UserService(){
log.info("userService自动装配成功");
}
public void test(){
log.info("调用test方法成功");
}
}
参数装配
@RestController
@RequestMapping(value = "/user/")
public class UserController {
@Autowired
UserService userService;
@GetMapping(value = "/test")
public String test(){
userService.test();
return "success";
}
}
我们可以看到,Spring对UserService完成了自动装配,我们访问相应的路径,可以看到成功的调用了test()方法:
构造函数装配
@RestController
@RequestMapping(value = "/user/")
public class UserController {
UserService userService;
@Autowired
UserController(UserService userService){
this.userService = userService;
}
@GetMapping(value = "/test")
public String test(){
userService.test();
return "success";
}
}
我们可以看到,Spring也是正常完成了UserService的装配,访问对应页面,也是成功调用了相应方法:
通过上面实验我们可以看到,不管是通过修饰参数,还是通过修饰构造方法,都能够完成自动注入,其功能也能够正常访问。
那么,这两者的区别是什么呢?了解Java类加载顺序的同学可能明白了,或许,会和加载和注入顺序有关。
Java类加载和注入顺序
在上面的代码中,类加载的顺序应该是这样的:
静态变量或静态语句块–>实例变量或初始化语句块–>构造方法–>@Autowired
因此,注入通常是在最后执行的,但如果我们将注解修饰在构造方法上,那注入操作就会在实例对象时执行。
也就是说,如果我们将注解修饰在构造方法上,那么我们的自动装配时机就不会排在最后一步。那么,在实际生产环境中,又有什么区别吗?
我们来看看下面情况,我们使用两种方法来进行自动装配。
@Slf4j
@Service
public class UserService {
UserService(){
log.info("userService自动装配成功");
}
public String getNane(){
return "刘欣";
}
}
使用构造方法
@RestController
@RequestMapping(value = "/user/")
public class UserController {
UserService userService;
String username;
@Autowired
UserController(UserService userService){
this.userService = userService;
this.username = userService.getNane();
}
@GetMapping(value = "/test")
public String test(){
return username;
}
}
运行代码,我们可以看到,程序正常运行
访问页面,也能正常获取数据:
使用参数
@RestController
@RequestMapping(value = "/user/")
public class UserController {
@Autowired
UserService userService;
String username = userService.getNane();
@GetMapping(value = "/test")
public String test(){
return username;
}
}
运行代码,我们发现在启动SpringBoot应用程序时,便抛出了异常:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userController' defined in file [.../UserController.class]: Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [.../UserController]: Constructor threw exception; nested exception is java.lang.NullPointerException
异常告诉我们,因为实例化bean失败,而导致了空指针异常。
那么这是为什么呢?与之前的代码不同的是,我们在参数中直接调用了getName方法,而在该程序中,加载顺序应该是这样的:
定义UserService变量 - 定义userName变量 - 调用getName方法 - 构造方法 - 实例化Bean。
我们看到,在UserService还未实例化时,我们就调用了getName方法,因此导致了NullPointerException。
总结
在开发过程中,如果有使用到bean的实例变量的定义,应使用构造方法的形式去实现自动注入。
我们在实际进行开发过程中,应该去考虑多种方案的优缺点以及区别,而不是一味的去寻求所谓有简便化开发,去追寻所谓的效率。可能,在我们追寻所谓的效率时,出现问题后,会更花费我们的精力。