Spring的自動注入@Autowired與直接new例項的區別
最近專案中遇到了一個問題,在controller裡面呼叫的service可以正常使用,但是在把service對應的實現類直接new的時候,呼叫方法卻報空指標。
為什麼在new物件跟自動注入物件同時使用時會空指標,還有就算new物件怎麼處理才不會出現空指標的問題。
根本原因就在當spring框架幫我們管理的時候會幫我們自動的初始化接下來用到的一些屬性,而通過用new例項的方法去做,在例項中用到的某些屬性可能就需要我們自己去給set值做一個初始化,否則就有可能產生空指標的錯誤。
1)首先,我們先看一下正常的情況,把管理權交給spring替我們去管理:
在下面程式碼的第4行將UploadService 注入後,在第18行去對它當中的uploadBlock()方法做了一個呼叫(注意:這個時候第10行的程式碼如果是被註釋掉的,上面第4行的注入就會生效,反之失效!),我們在18行做引用的時候,在方法中用到了ReadFilePathProperties這一個類,在程式執行過程中框架就幫我們把它給初始化了,就不會出現空指標的錯誤,程式正常執行。
public class TestController { 1 @Autowired 2 private FileToByteArrayService fileToByteArrayService; 3 @Autowired 4 private UploadService uploadService; 5 @Test 6 public void testDemo() throws IOException { 7 //原檔案的位置(需根據自己情況修改) 8 String fileSrc = "D:\\tempfile\\elasticsearch-6.4.2.zip"; 9 byte[] bytes = fileToByteArrayService.fileToBytes(fileSrc); /** * 演示過程中,下面這行程式碼是否註釋掉說明: * 1.演示通過springboot注入方式去呼叫UploadService()中的方法需要註釋掉。 * 2.演示通過new例項方式進入到UploadService去呼叫方法出現空指標問題,則需要保留這行程式碼。 * 3.演示通過new例項方式進入到UploadService去呼叫方法,通過在UploadService中set值解決問題,則這行程式碼不能註釋掉。 */ 10 //UploadService uploadService = new UploadService(); try { 11 UploadBlockInputVo param = new UploadBlockInputVo(); 12 param.setFileName("elasticsearch-6.4.2"); 13 param.setOffset(0); 14 param.setLength(1000000 * 25); 15 param.setPartNumber(1); 16 param.setSuffix(".zip"); 17 param.setBytes(bytes); 18 uploadService.uploadBlock(param); } catch (Exception e) { e.printStackTrace(); } } }
2)接著我們來看通過new物件的形式,不把管理權交給spring。
當我們把第10行註釋的程式碼放開後,程式執行到第10行,這時我們在第4行注入UploadService,在第18行呼叫它的方法,注入呼叫方式就會失效,spring框架就不會替我們去管理它,這時候它這兒用到的就是通過第10行的new例項方法去對UploadService中的方法做了呼叫,而此時uploadBlock()這個方法中的ReadFilePathProperties這個東西並非被spring框架管理,所以就沒有被自動地初始化導致報了空指標的錯誤(注意:如果你這時候通過new方法去呼叫的方法裡面沒有需要被初始化的物件屬性(下面程式碼中有解釋),則程式依舊正常執行,否則報空指標)。
public class TestController {
1 @Autowired
2 private FileToByteArrayService fileToByteArrayService;
3 @Autowired
4 private UploadService uploadService;
5 @Test
6 public void testDemo() throws IOException {
7 //原檔案的位置(需根據自己情況修改)
8 String fileSrc = "D:\\tempfile\\elasticsearch-6.4.2.zip";
9 byte[] bytes = fileToByteArrayService.fileToBytes(fileSrc);
/**
* 演示過程中,下面這行程式碼是否註釋掉說明:
* 1.演示通過springboot注入方式去呼叫UploadService()中的方法需要註釋掉。
* 2.演示通過new例項方式進入到UploadService去呼叫方法出現空指標問題,則需要保留這行程式碼。
* 3.演示通過new例項方式進入到UploadService去呼叫方法,通過在UploadService中set值解決問題,則這行程式碼不能註釋掉。
*/
10 UploadService uploadService = new UploadService();
try {
11 UploadBlockInputVo param = new UploadBlockInputVo();
12 param.setFileName("elasticsearch-6.4.2");
13 param.setOffset(0);
14 param.setLength(1000000 * 25);
15 param.setPartNumber(1);
16 param.setSuffix(".zip");
17 param.setBytes(bytes);
18 uploadService.uploadBlock(param);
} catch (Exception e) {
e.printStackTrace();
}
}
}
劃重點:
在第18行呼叫uploadBlock方法時,在uploadService的uploadBlock方法中我們用下面第一行程式碼,
註釋掉第二行,不使用ReadFilePathProperties物件中的方法,這個時候程式也會正常執行。
String uploadPartPath = "D:\\myfile\\elasticsearch-6.4.2Zip";
// String uploadPartPath = readFilePathProperties.getPropertisInfo();
(3)最後,我們說一下如果不把管理權交給框架,我們還是想通過new例項去呼叫例項中的方法,我們應該怎麼辦。
第一種辦法,就是讓例項中的呼叫的方法中不存在使用另一個物件的情況,其實這個問題,上面第2個解釋中已經給出了一個答案。
第二種辦法,就是在uploadBlock方法中給ReadFilePathProperties物件set值,我們自己給他做初始化。通過這兩種方式處理過後,即使不由框架為我們管理,也可以達到我們的目的,避免出現空指標的問題。
public class UploadService {
@Autowired
private ReadFilePathProperties readFilePathProperties;
public void setReadFilePathProperties(ReadFilePathProperties readFilePathProperties) {
this.readFilePathProperties = readFilePathProperties;
}
public void uploadBlock(UploadBlockInputVo uploadBlockInputVo) {
//code...
ReadFilePathProperties readFilePathProperties = new ReadFilePathProperties();
setReadFilePathProperties(readFilePathProperties);
String uploadPartPath = readFilePathProperties.getPropertisInfo();
//code...
}
}
(4)總結
在程式的啟動時,spring會按照一定的載入鏈來載入並初始化spring容器中的元件。
例如:A中注入B,B中注入C,在A中呼叫B,來使用B中的C的方法時,如果不採用自動注入的方式呼叫,而用new建立B,就會出現空指標異常(因為B中的C並沒有被初始化)。如果B中沒有注入C,則可以使用new來建立B。
下面接著來看下注入順序問題:
Spring注入bean的順序,以及Spring如何保證事先載入依賴bean的問題
什麼是bean的例項化?什麼是bean的初始化?
bean例項化:是bean物件建立的過程。比如使用構造方法new物件,為物件在記憶體中分配空間。
bean初始化:是為物件中的屬性賦值的過程。
場景:Abean中有一個Bbean屬性,Bbean中有一個Abean屬性,Spring載入依賴bean順序?
首先初始化A物件例項為例進行講解整個過程。先說明:基於構造器的迴圈依賴spring是無法解決的。
1、首先Spring嘗試通過ApplicationContext.getBean()方法獲取A物件的例項,由於Spring容器中還沒有A物件例項,因而其會建立一個A物件。
2、然後發現其依賴了B物件,因而會嘗試遞迴的通過ApplicationContext.getBean()方法獲取B物件的例項。
3、但是Spring容器中此時也沒有B物件的例項,因而其還是會先建立一個B物件的例項。
4、需要注意這個時間點,此時A物件和B物件都已經建立了,並且儲存在Spring容器中了,只不過A物件的屬性b和B物件的屬性a都還沒有設定進去(未初始化)。
5、在前面Spring建立B物件之後,Spring發現B物件依賴了屬性A,因而還是會嘗試遞迴的呼叫ApplicationContext.getBean()方法獲取A物件的例項。
6、因為Spring中已經有一個A物件的例項,雖然只是半成品(其屬性b還未初始化),但其也還是目標bean,因而會將該A物件的例項返回。
7、此時,B物件的屬性a就設定進去了,然後還是ApplicationContext.getBean()方法遞迴的返回,也就是將B物件的例項返回,此時就會將該例項設定到A物件的屬性b中。
8、這個時候,注意A物件的屬性b和B物件的屬性a都已經設定了目標物件的例項了。
9、此時可能會比較疑惑的是,前面在為物件B設定屬性a的時候,這個A型別屬性還是個半成品。但是需要注意的是,這個A是一個引用,其本質上還是最開始就例項化的A物件。
10、而在上面這個遞迴過程的最後,Spring將獲取到的B物件例項設定到了A物件的屬性b中了。
11、這裡的A物件其實和前面設定到例項B中的半成品A物件是同一個物件,其引用地址是同一個,這裡為A物件的b屬性設定了值,其實也就是為那個半成品的a屬性設定了值。
Spring能夠輕鬆的解決屬性的迴圈依賴正式用到了三級快取,在DefaultSingletonBeanRegistry 中,有著3個map。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
....略
}
- ingletonObjects 它是我們最熟悉的朋友,俗稱“ 單例池 ”“ 容器 ”,快取建立完成單例Bean的地方。
- singletonFactories 對映建立Bean的原始工廠
- earlySingletonObjects 對映Bean的 早期 引用,也就是說在這個Map裡的Bean不是完整的,甚至還不能稱之為“ Bean ”,只是一個 Instance .
參考部落格連結:https://www.cnblogs.com/niudaben/p/13229956.html#_labelTop