Spring两个核心IoCDI(二)
DI(依赖注入)就是从IoC容器中获取对象并赋值给某个属性,这就是依赖注入的过程。
关于依赖注入有3种方式:
1、属性注入
2、构造方法注入
3、setter注入
目录
1、属性注入
2、 构造方法注入
3、Setter方法注入
4、3种注入方式优缺点分析
属性注入
构造方法注入:
Setter注入
5、@Autowired存在的问题以及解决方法
面试题
3种注入方式优缺点
@Autowired与@Resource的区别:
@Autowired的装配顺序
1、属性注入
@Service
public class UserService {public void doService(){System.out.println("UserService.doService");}
}
@ResponseBody
@Controller
public class UserController {@Autowiredprivate UserService userService;public void sayHi() {userService.doService();System.out.println("UserController.sayHi");}@RequestMapping("/hello")public String hello(){return "hello";}
}
@SpringBootApplication
public class SpringIoCDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDemoApplication.class, args);UserController bean = context.getBean(UserController.class);bean.sayHi();
}
}
通过@Autowired注解,spring就会从容器中取出UserService对象赋值给userService属性,如果不加这个注解,就会在userService.doService()这里报空指针异常。
ApplicationContext的对象也在spring IoC容器中,我们也能通过 @Autowired拿到它的对象,
@Autowired
private ApplicationContext context;然后用context再去获取别的Bean对象。
但是我们一般不这样用。
2、 构造方法注入
@Service
public class UserService {public void doService(){System.out.println("UserService.doService");}
}
@Controller
public class UserController2 {private UserService userService;public UserController2(UserService userService){this.userService = userService;}public void sayHi() {userService.doService();System.out.println("UserController.sayHi");}
}
@SpringBootApplication
public class SpringIoCDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDemoApplication.class, args);UserController2 uc = context.getBean(UserController2.class);uc.sayHi();
}
}
spring在创建UserController2对象的时候,因为这里只提供了一个需要UserService对象参数的构造方法,所以容器就会把UserService对象注入给this.userSercice属性,因此uc.sayHi()能成功运行。
但是为了标准化一般建议把无参的构造方法也写上,
@Controller
public class UserController2 {private UserService userService;public UserController2() {}public UserController2(UserService userService){this.userService = userService;}public void sayHi() {userService.doService();System.out.println("UserController.sayHi");}
}
此时再运行起来就会报错,
因为spring在创建UserController2对象的时候有无参的构造方法就是用了无参的构造方法,所以就没有给userService属性赋值,因此userService.doService()这行会报空指针异常。
为了让spring创建UserController2对象使用有UserService参数的构造方法,我们需要使用@Autowired注解
import com.bit.springiocdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController2 {private UserService userService;public UserController2() {}@Autowiredpublic UserController2(UserService userService){this.userService = userService;}public void sayHi() {userService.doService();System.out.println("UserController.sayHi");}
}
所以只有一个构造方法时,@Autowired可以省略;有多个构造方法时需要通过添加@Autowired来指定spring用哪一个来创建对象。
3、Setter方法注入
import com.bit.springiocdemo.config.UserConfig;
import com.bit.springiocdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;@Controller
public class UserController3 {private UserService userService;private UserConfig userConfig;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}@Autowiredpublic void setUserConfig(UserConfig userConfig) {this.userConfig = userConfig;}public void sayHi() {userService.doService();userConfig.doConfig();System.out.println("UserController.sayHi");}
}
@SpringBootApplication
public class SpringIoCDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDemoApplication.class, args);UserController3 uc3 = context.getBean(UserController3.class);uc3.sayHi();
}
}
通过setter方法来为属性赋值,需要给setter方法添加@Autowired属性,否则在sayHi()方法中就会报快指针异常。
4、3种注入方式优缺点分析
属性注入
简洁方便,但是只能用于IoC容器,并且不能注入一个Final修饰的属性。
fianl修饰的属性要么要初始化,要么通过构造方法进行赋值。
构造方法注入:
优点:
- 可以注入final修饰的属性,就是在构造方法中进行赋值
- 注入的对象不会被修改
- 依赖对象在使用前一定会被完全初始化的,因为依赖是在类的构造方法中注入的,而构造方法是在类加载时就会执行的
- 通用性好,构造方法是jdk支持的,换任何框架都是适用的
缺点:注入多个对象比较麻烦
Setter注入
优点:方便在类实例话之后,还能对该对象进行配置或者注入
@Controller
public class UserController3 {private UserService userService;private UserConfig userConfig;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}@Autowiredpublic void setUserConfig(UserConfig userConfig) {this.userConfig = userConfig;}public void sayHi() {userService.doService();userConfig.doConfig();System.out.println("UserController.sayHi");}
}
就这个代码来说,就是在UserController3实例化好之后,还能通过主动调用setter方法来给属性赋值,更换注入的对象,所以这也是一个缺点
缺点:
- 注入对象可能会被改变,因为setter可能多次调用,就有被修改的风险
- 不能注入一个final修饰的属性
当前Spring官方更推荐构造方法注入方式,程序员更推荐属性注入方式。
5、@Autowired存在的问题以及解决方法
当一个类型有多个Bean时,使用@Autowired就会报错
@AllArgsConstructor
@Data
public class UserInfo {private Integer id;private String name;private Integer age;
}
@Configuration
public class UserInfoConfig {@Bean(name = {"user1","lisi"})public UserInfo user1() {UserInfo userInfo = new UserInfo(1,"lisi",15);return userInfo;}@Beanpublic UserInfo user2() {UserInfo userInfo = new UserInfo(2,"kiku",31);return userInfo;}
}
从这里可以看到UserInfo类型在IoC容器中存了两个对象,一个是通过user1()创建的,一个是user2()创建的
@Controller
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate UserInfo userInfo;public void sayHi() {userService.doService();System.out.println(userInfo);System.out.println("UserController.sayHi");}
}
所以这里通过添加@Autowired给userInfo属性注入对象时,不知道该使用容器中的哪一个来进行注入
@SpringBootApplication
public class SpringIoCDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIoCDemoApplication.class, args);UserController bean = context.getBean(UserController.class);bean.sayHi();
}
}
报错信息以及建议:
建议中有两种解决方式,
一种是通过添加@Primary来选择一个Bean对象作为主要选择的、默认的,比如:
@Primary @Bean(name = {"user1","lisi"}) public UserInfo user1() {UserInfo userInfo = new UserInfo(1,"lisi",15);return userInfo; }
这样就会选择user1()创建的对象来注入。
另一种是通过@Qualifier来指定要注入的对象是谁。
@Controller
public class UserController {@Autowiredprivate UserService userService;@Autowired@Qualifier("user2")private UserInfo userInfo;public void sayHi() {userService.doService();System.out.println(userInfo);System.out.println("UserController.sayHi");}
}
当两个同时@Primary和@Qualifier都存在时,会使用@Qualifier()指定的,而不是默认的。
另外@Qualifier除了可以加在属性上,还可以加在参数上:
@Autowiredpublic UserController2(UserService userService, UserConfig userConfig,@Qualifier("user1") UserInfo userInfo) {this.userService = userService;this.userConfig = userConfig;this.userInfo = userInfo;}
除了spring建议的这两种,我们还可以通过@Resource来解决:
// @Autowired
// @Qualifier("user2")@Resource(name = "user2")private UserInfo userInfo;
@Resource作用上就等于@Autowired+@Qualifier,
这个注解是jakarta提供的。
面试题
3种注入方式优缺点
@Autowired与@Resource的区别:
1、来源不同,@Autowired是spring框架提供的注解,@Resource是jdk提供的注解。
2、@Autowired是按照类型注入的(如果该类型只有一个Bean,直接注入;如果该类型有多个bean,就按属性名称注入,但是@Autowired不能指定bean的名称来注入),
@Resource是按照名称注入的