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是按照名稱注入的