Spring自動注入(@Autowired)與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 .