spring的依賴注入功能如何幫助應用解藕的?
理論
1.依賴注入(DI dependency injection):
-
依賴:我們要完成一件事不能靠自己完成,需要依賴於其一些外部的一些事物,吃飯這件事我們就不能獨立完成,我們除了帶上自己的嘴,我們要依賴餐具,食物。物件的依賴同理。
-
注入:中國式的教育往往不是學生主動去獲取知識,而是老師給學生腦袋注入知識。
-
依賴注入:java是面向物件的程式設計,當我們要做很多事情時往往都會依賴於諸多的物件協作來相互完成一些操作,我們知道物件的方法和屬性的呼叫都要依賴於類的例項來完成的我們要用其他物件的方法和屬性通常要先擁有對應的例項,也就是需要兩步。
第一步:建立物件(A a=new A()
第二步:使用建立的物件例項去呼叫方法和屬性(a.method和a.variable)。
如果我們做的事情很簡單隻有幾個物件的相互依賴顯然用上述的方式就可以完成,但是當我們做的事情很複雜時,可能涉及到幾十,幾百,幾千,上萬甚至更多的物件時,物件之間又相互依賴我們都用上述的方法去實現就複雜得不可想象了,首先建立物件的程式碼繁瑣,其次物件大量建立後,考慮資源我們需要去維護物件的生命週期,維護難浪費資源,物件之間依賴關係變得不可維護。在這種背景下spring到來了,它提供了物件的管理容器,例項的生命週期(建立(init-mothod),使用,銷燬(destroy-mothod))由它來維護.物件之間相互依賴不再是需要某個物件就自己去建立物件,然後使用物件,而是變為需要某個物件,spring就會為我們注入某個依賴的物件。物件的獲取從主動變為被動,這就是依賴注入。
實踐
依賴注入的三種方式(裝配bean的方式)逐漸脫離xml配置,最後在spring5中進化為springBoot
1.spring原始xml配置bean
-
什麼是xml配置bean?
把物件的建立和物件之間的依賴放到xml中來完成,然後交給spring的容器管理。
-
為何要用xm配置bean的方式?
物件的管理容易,物件間的依賴簡單,解決了傳統物件之間依賴呼叫麻煩問題。
-
如何使用xml配置bean?
1)新增介面
2)新增介面實現類
3)配置xml
4)新增單元測試
- 新增介面
package com.example.demo;
//定義一個充電工具的介面
public interface ChargeTool {
// 具有充電的這個功能
public void charge(String name);
}
- 新增介面實現類
package com.example.demo.impl;
import com.example.demo.ChargeTool;
//實現充電介面的具體實現類有了充電功能
public class ChargeToolImpl implements ChargeTool {
@Override
public void charge(String name) {
System.out.println(name + "正在開始充電...");
}
}
- 配置xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="charge" class="com.example.demo.impl.ChargeToolImpl"></bean>
<!-- 構造方法注入 -->
<bean id="phone" class="com.example.demo.Phone">
<constructor-arg name="charge" ref="charge"/>
</bean>
<!--
<!-- setter方法注入使用這種方式需要Phone中要給charg增加setter方法-->
<bean id="phone" class="com.example.demo.Phone">
<property name="charge" ref="charge"/>
</bean> -->
</beans>
- 新增測試
為了和沒有spring依賴注入思想之前物件之間依賴呼叫做對比,把之前的方式也列出來
1)沒有使用依賴注入思想之前的實現
package com.example.demo;
import org.junit.Test;
import com.example.demo.impl.ChargeToolImpl;
public class Phone1 {
private ChargeToolImpl charge;
public void charging() {
charge = new ChargeToolImpl();
charge.charge("手機");
}
@Test
public void test() {
charging();
}
}
2)使用依賴注入後實現
package com.example.demo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.demo.impl.ChargeToolImpl;
public class Phone {
private ChargeToolImpl charge;
public Phone(ChargeToolImpl charge) {
this.charge =charge;
}
public void charging() {
charge.charge("手機");
}
public static void main(String args[]) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test.xml");
Phone phone = (Phone) context.getBean("phone");
phone.charging();
}
}
-
xml配置bean有何優缺點?
顯示的一些配置多,它們將bean的型別以字串的形式設定在了class屬性中。誰能保證設定給class屬性的值是真正的類呢?Spring的XML配置並不能從編譯期的型別檢查中受益。即便它所引用的是實際的型別,如果你重新命名了類,儘管藉助IDE檢查XML的合法性,使用能夠感知Spring功能的IDE,如Spring Tool Suite,能夠在很大程度上幫助你確保Spring XML配置的合法性。
- 自動化配置:自動掃包和註解配置bean
- 什麼是自動掃包和註解配置bean?
spring自動去掃描我們配置路徑下面的類,根據一些註解標識來自動建立bean,如果我們要在bean引入另一個bean,那我們只需要新增一些簡單註解就可以引入使用了,不用去配置xml。
- 為何要用自動掃包和註解配置bean的方式?
當我們使用了spring的xml配置檔案體驗完bean的建立和注入後,似乎還是不滿足於現狀,還是嫌配置太麻煩了,而且還有上述xml配置存在的問題也是不可忽視的。所以我們想要一種簡化配置工作的方式,自動給我們建立裝配bean,我們只需要簡單的使用一些配置就可以使用。那麼這種方式就產生了,不再需要我們去顯示的配置每一個bean和裝備bean之間的依賴。
- 如何使用自動掃包和註解配置bean?
1)配置自動掃包
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example.demo"/>
</beans>
2)添加註解
package com.example.demo.impl;
import org.springframework.stereotype.Component;
import com.example.demo.ChargeTool;
//實現充電介面的具體實現類有了充電功能
@Component
public class ChargeToolImpl implements ChargeTool {
@Override
public void charge(String name) {
System.out.println(name + "正在開始充電...");
}
}
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Phone {
@Autowired
private ChargeTool charge;
public void charging() {
charge.charge("手機");
}
}
3)新增測試
package com.example.demo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
public static void main(String args[]) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test.xml");
Phone phone = (Phone) context.getBean("phone");
phone.charging();
}
}
-
自動掃包和註解配置bean有何優缺點?
相比原始的spring的xml配置bean的方式,自動掃包已經簡化我們手動的很多配置,我們更多的就只是通過註解引入使用。開發中總會遇到一些場景,我們開發某個功能需要藉助第三方庫中的元件裝配到我們的應用中,在這種情況下因為類不在我們編寫的程式碼包下,又不想用再去引入xml裝配進來,我們不能通過添加註解方式就把他們引入進來。
3.顯示通過javaConfig配置
- 什麼是 javaConfig置bean?
學習完 spring通過xml配置裝配bean和自動掃包裝配bean後,我們還想要一種建立靈活並且有,spring的bean管理的功能,顯示去看到物件的建立。
- 為何要用 javaConfig配置bean的方式?
儘管自動掃包引入註解方式已經夠簡潔方便了,我們開發中總會遇到一些場景,我們開發某個功能需要藉助第三方庫中的元件裝配到我們的應用中,在這種情況下因為類不在我們編寫的程式碼包下,我們不能通過添加註解方式就把他們引入進來,我們又不想用再去引入xml裝配進來,這個時候javaConfig的配置就非常重要了,我們就可以採用這種顯示方式配置,更為強大靈活,型別安全對重構非常友好,因為他就是通過java程式碼實現,我們只需要把javaConfig放入單獨包中,這樣對於它的意圖就不會產生困惑了,裡面不能又任何邏輯業務相關程式碼。更好的讓我們理解自動掃包裝配和更加靈活的顯示建立bean的管理,在不同場景可以多種混合使用。
- 如何使用javaConfig置bean?
1)建立 javaConfig,去除自動掃包 Componentscan
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.demo.ChargeTool;
import com.example.demo.Phone;
import com.example.demo.impl.ChargeToolImpl;
//@Configuration註解表明這個類是一個配置類,該類應該包含在Spring應用上下文中如何建立bean的細節
@Configuration
public class BeanConfig{
@Bean(name="phone")
public Phone getPhoneBean(ChargeTool charge) {
Phone phone=new Phone();
phone.setCharge(charge);
System.out.println("...建立了Phone的物件:"+phone);
return phone;
}
@Bean(name="charge")
public ChargeTool getChargeToolBean() {
ChargeTool charge=new ChargeToolImpl();
System.out.println("...建立了Phone的物件:"+charge);
return charge;
}
}
2)去除建立bean上的註解
package com.example.demo.impl;
import com.example.demo.ChargeTool;
//實現充電介面的具體實現類有了充電功能
public class ChargeToolImpl implements ChargeTool {
@Override
public void charge(String name) {
System.out.println(name + "正在開始充電...");
}
}
package com.example.demo;
public class Phone {
private ChargeTool charge;
public void setCharge(ChargeTool charge) {
this.charge = charge;
}
public void charging() {
charge.charge("手機");
}
}
3)建立單元測試,驗證 javaConfig中同個類的例項是否只建立一次(建立bean的方法只執行一次)
package com.example.demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.demo.config.BeanConfig;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes=BeanConfig.class)
public class SpringTest {
@Autowired
private Phone phone;
@Autowired
private Phone phone1;
@Test
public void test() {
phone.charging();
phone1.charging();
}
}
- javaConfig配置bean有何優缺點?
@Bean註解的方法可以採用任何必要的Java功能來產生bean例項,這裡所存在的可能性僅僅受到Java語言的限制,所以建立bean的方式更加明確靈活,顯示的可以使用原始的物件建立,使用。同時把bean的管理交給了spring容器,還可以把第三方倉庫中的bean裝配進來,兩者方式更好的結合,
如果我們一個介面有多個實現,依賴裝配會有問題嗎?
如果一個介面有多個實現類時,在自動依賴注入中 spring就會無法抉擇產生歧義丟擲昇常。 ChargeTool是一個介面,並且有兩個類實現了這個介面,分別為PhoneChargeToolImpl,VideoChargeToolImpl因為這兩個實現均在javaConfig中配置了bean,在建立應用上下文時將其建立為Spring應用上下文裡面的bean。然後當Spring試圖自動裝配ChargeTool引數時,它並沒有唯一、無歧義的可選值。當確實發生歧義性的時候, Spring提供了多種可選方案來解決這樣的問題。你可以將可選bean中的某一個設為首選( primary)的bean,或者使用限定符( qualifier)來幫助,Spring將可選的bean的範圍縮小到只有一個bean.或許你會說把方法中注入引數ChargeTool換成對應實現類PhoneChargeToolImpl和VideoChargeToolImpl就可以解決了,但是這樣就失去了介面定義的意義了。
1)定義介面動作
package com.example.demo;
//定義一個充電工具的介面
public interface ChargeTool {
// 具有充電的這個功能
public void charge();
}
2)定義介面動作實現類
package com.example.demo.impl;
import com.example.demo.ChargeTool;
public class VideoChargeToolImpl implements ChargeTool {
@Override
public void charge() {
System.out.println("電視正在開始充電...");
}
}
package com.example.demo.impl;
import com.example.demo.ChargeTool;
public class PhoneChargeToolImpl implements ChargeTool {
@Override
public void charge() {
System.out.println("手機正在開始充電...");
}
}
3)定義介面動作主體
package com.example.demo;
public class Phone {
private ChargeTool charge;
public void setCharge(ChargeTool charge) {
this.charge = charge;
}
public void charging() {
charge.charge();
}
}
package com.example.demo;
public class Video {
private ChargeTool charge;
public void setCharge(ChargeTool charge) {
this.charge = charge;
}
public void charging() {
charge.charge();
}
}
4)定義javaConfig
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.demo.ChargeTool;
import com.example.demo.Phone;
import com.example.demo.Video;
import com.example.demo.impl.PhoneChargeToolImpl;
import com.example.demo.impl.VideoChargeToolImpl;
//@Configuration註解表明這個類是一個配置類,該類應該包含在Spring應用上下文中如何建立bean的細節
@Configuration
public class BeanConfig{
@Bean(name="phone")
public Phone getPhoneBean(@Qualifier("phoneCharge") ChargeTool charge) {
Phone phone=new Phone();
phone.setCharge(charge);
System.out.println("...建立了Phone的物件:"+phone);
return phone;
}
@Bean(name="video")
public Video getVideoBean(@Qualifier("videoCharge") ChargeTool charge) {
Video video=new Video();
video.setCharge(charge);
System.out.println("...建立了video的物件:"+video);
return video;
}
@Bean(name="phoneCharge")
public ChargeTool getChargeToolBean() {
ChargeTool phoneCharge=new PhoneChargeToolImpl();
System.out.println("...建立了PhoneChargeToolImpl的物件:"+phoneCharge);
return phoneCharge;
}
@Bean(name="videoCharge")
public ChargeTool getVideoToolBean() {
ChargeTool videoCharge=new VideoChargeToolImpl();
System.out.println("...建立了VideoChargeToolImpl的物件:"+videoCharge);
return videoCharge;
}
}
4)定義單元測試
package com.example.demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.demo.config.BeanConfig;
@RunWith(SpringRunner.class)
@ContextConfiguration(classes=BeanConfig.class)
public class SpringTest {
@Autowired
private Phone phone;
@Autowired
private Video video;
@Test
public void test() {
phone.charging();
video.charging();
}
}
spring相關部落格請