【JAVA】Spring依賴注入

為什么要用Spring的依賴注入,解耦,統(tǒng)一管理,面向接口編程,易于程序的擴展,有利于程序的維護

解耦合,比如A需要用到B,我們需要把B對象 set 到 A對象里,那C也需要用到B對象,此時也需要把B set 到C里,那么怎么獲取到B對象的引用了?
比如A用到B,C,D,B又用到E,C又用到F,D又用到G,這樣靠手動操作是不是很麻煩。

常的java開發(fā)中,程序員在某個類中需要依賴其它類的方法,則通常是new一個依賴類再調(diào)用類實例的方法,這種開發(fā)存在的問題是new的類實例不好統(tǒng)一管理,spring提出了依賴注入的思想,即依賴類不由程序員實例化,而是通過spring容器幫我們new指定實例并且將實例注入到需要該對象的類中。依賴注入的另一種說法是“控制反轉(zhuǎn)”,通俗的理解是:平常我們new一個實例,這個實例的控制權(quán)是我們程序員,而控制反轉(zhuǎn)是指new實例工作不由我們程序員來做而是交給spring容器來做。

面向接口編程,接口不變,改動實現(xiàn)類就可以,不需要改寫現(xiàn)有代碼,減少對系統(tǒng)的影響。
在使用面向接口的編程過程中,將具體邏輯與實現(xiàn)分開,減少了各個類之間的相互依賴,當(dāng)各個類變化時,不需要對已經(jīng)編寫的系統(tǒng)進行改動,添加新的實現(xiàn)類就可以了,不在擔(dān)心新改動的類對系統(tǒng)的其他模塊造成影響。

注入方式

構(gòu)造函數(shù)注入,setter方法注入,注解注入

1.構(gòu)造函數(shù)注入

在bean標(biāo)簽的內(nèi)部使用constructor-arg標(biāo)簽就可以進行構(gòu)造函數(shù)注入了。
constructor-arg標(biāo)簽的屬性:
type:用于指定要注入的數(shù)據(jù)的數(shù)據(jù)類型,該數(shù)據(jù)類型也是構(gòu)造函數(shù)中某個或某些參數(shù)的類型
index:用于指定要注入的數(shù)據(jù)給構(gòu)造函數(shù)中指定索引位置的參數(shù)賦值,索引的位置從0開始
name:用于給指定構(gòu)造函數(shù)中指定名稱的參數(shù)賦值
value:用于提供基本類型和String類型的數(shù)據(jù)
ref:用于指定其他的bean類型數(shù)據(jù),就是在IOC容器中出現(xiàn)過的bean對象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountService" class="com.sks.service.imp.AccountServiceImpl">
        <constructor-arg type="java.lang.String" value="張三"/>
        <constructor-arg index="1" value="20"/>
        <constructor-arg name="birthday" ref="birthday"/>
    </bean>

    <bean id="birthday" class="java.util.Date"/>
</beans>

AccountServiceImpl 類

public class AccountServiceImpl implements AccountService {

    private String name;
    private Integer age;
    private Date birthday;
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String, String> myMap;
    private Properties myProps;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public AccountServiceImpl(String name, Integer age, Date birthday) {
        System.out.println("含參的構(gòu)造方法被調(diào)用了");
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public AccountServiceImpl() {
        System.out.println("構(gòu)造方法調(diào)用");
    }

    @Override
    public int addMoney(int money) {
        System.out.println("向賬戶中加錢:" + money);
        return 0;
    }

    @Override
    public void saveAccount(Account account) {
        System.out.println("saveAccount方法執(zhí)行了");
    }
}

測試

@Test
public void test8() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:bean.xml");;
    AccountService accountService = (AccountService) applicationContext.getBean("accountService");
    System.out.println(accountService.toString());
}
2.setter方法注入

在bean標(biāo)簽內(nèi)部使用property標(biāo)簽進行配置。
property標(biāo)簽的屬性:
name:用于指定注入時所調(diào)用的set方法名稱
value:用于提供基本類型和String類型的數(shù)據(jù)
ref:用于指定其他的bean類型數(shù)據(jù)
這里面我們注入了基本類型、包裝類型、日期類型數(shù)據(jù)。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountService" class="com.sks.service.imp.AccountServiceImpl">
        <!--注入基本類型、包裝類型、日期類型數(shù)據(jù)-->
       <property name="age" value="22"/>
        <property name="name" value="李四"/>
        <property name="birthday" ref="birthday"/>
    </bean>

    <bean id="birthday" class="java.util.Date"/>
</beans>

優(yōu)勢:創(chuàng)建對象時沒有明確的限制,可以直接使用默認(rèn)構(gòu)造函數(shù)。
缺點:如果又某個成員必須有值,則獲取對象有可能是set方法沒有執(zhí)行。

3.對集合類型數(shù)據(jù)進行注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountService2" class="com.sks.service.imp.AccountService2Impl">
        <property name="myStrs">
            <array>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </array>
        </property>

        <property name="myList">
            <list>
                <value>list1</value>
                <value>list2</value>
                <value>list3</value>
            </list>
        </property>

        <property name="mySet">
            <set>
                <value>set1</value>
                <value>set2</value>
                <value>set3</value>
            </set>
        </property>

        <property name="myProps">
            <props>
                <prop key="name">柯森</prop>
                <prop key="age">23</prop>
            </props>
        </property>

        <property name="myMap">
            <map>
                <entry key="key1" value="value1"/>
                <entry key="key2" value="value2"/>
                <entry key="key3">
                    <value>value3</value>
                </entry>
            </map>
        </property>  
    </bean>
</beans>
4.注解注入

假設(shè)此時只有一個AccountDao的實現(xiàn)類,并且這個類也加上了@Repository注解,那么我們這樣注入是可以成功的,但是如果容器中存在多個AccountDao的實現(xiàn)類,此時僅僅使用AccountDao是不能完成數(shù)據(jù)注入的,需要配合@Qualifier注解使用注入數(shù)據(jù)。

@Component
public class AccountService4Impl implements AccountService3 {

    //錯誤寫法,默認(rèn)會去容器中查找名稱為accountDao的bean
     //@Autowired
    //private AccountDao accountDao;

    //正確寫法
    //@Autowired
    //private AccountDao accountDao1

    //正確寫法
    @Autowired
    @Qualifier("accountDao1")
    private AccountDao accountDao;

    @Override
    public void addMoney(int money) {
        System.out.println("向賬戶中加錢....AccountService3Impl");
    }
}

@Value注解的基本使用
在使用@Value注入基本類型和String類型的數(shù)據(jù)時使用"#“號;使用@Value讀取配置文件的值時需要使用”$"符號,同時使用@PropertySource注解指定配置文件的位置。

@Component
@PropertySource("classpath:db.properties")
public class AccountService4Impl implements AccountService3 {

    @Autowired
    @Qualifier("accountDao1")
    private AccountDao accountDao;

    //使用SPEL表達式只注入值
    @Value("#{19 - 9}")
    private int age;

    @Value("zhangsan")
    private String name;

    //讀取操作系統(tǒng)的名稱
    @Value("#{systemProperties['os.name']}")
    private String osname;

    //讀取數(shù)據(jù)庫配置文件中的值
    @Value("${password}")
    private String password;

    @Override
    public void addMoney(int money) {
        System.out.println("向賬戶中加錢....AccountService3Impl");
    }
}

通過 xml 和 annotation 獲取到 Bean 的描述信息后, 肯定需要將其統(tǒng)一存儲和管理起來。 在 Spring 框架代碼中, Bean 的描述信息的最終存儲形式即為 BeanDefinition。

Spring的核心是控制反轉(zhuǎn)(IoC)和面向切面(AOP)

方便解耦,簡化開發(fā) (高內(nèi)聚低耦合)
Spring就是一個大工廠(容器),可以將所有對象創(chuàng)建和依賴關(guān)系維護,交給Spring管理,spring工廠是用于生成bean

AOP編程的支持
Spring提供面向切面編程,可以方便的實現(xiàn)對程序進行權(quán)限攔截、運行監(jiān)控等功能
聲明式事務(wù)的支持

方便集成各種優(yōu)秀框架
Spring不排斥各種優(yōu)秀的開源框架,其內(nèi)部提供了對各種優(yōu)秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持

降低JavaEE API的使用難度
Spring 對JavaEE開發(fā)中非常難用的一些API(JDBC、JavaMail、遠(yuǎn)程調(diào)用等),都提供了封裝,使這些API應(yīng)用難度大大降低

Spring容器就是一個實現(xiàn)了ApplicationContext接口的對象
Spring 容器是 Spring 框架的核心,是用來管理對象的。容器將創(chuàng)建對象,把它們連接在一起,配置它們,并管理他們的整個生命周期從創(chuàng)建到銷毀。

如何實例化一個Bean?

1.構(gòu)造方法
2.通過靜態(tài)工廠方法
3.通過實例工廠方法

Spring通過解析我們的配置元數(shù)據(jù),以及我們提供的類對象得到一個Beanfinition對象。通過這個對象可以實例化出一個java bean對象。

依賴注入

依賴注入主要分為兩種方式
1.構(gòu)造函數(shù)注入
2.Setter方法注入

1.IOC控制反轉(zhuǎn)

將我們創(chuàng)建對象的控制權(quán)反轉(zhuǎn)交給spring去創(chuàng)建

  • 之前創(chuàng)建對象是這么寫的
UserService userService = new UserServiceImpl();
userService.addUser();
  • 現(xiàn)在都由spring來創(chuàng)建,后面開發(fā)也都通過掃描配置文件里注入的bean,對象直接從spring容器獲取不需要自己創(chuàng)建
在xml里配置bean
<bean id="userServiceId" class="com.itheima.a_ioc.UserServiceImpl"></bean>

1 獲得容器
String xmlPath = "com/itheima/a_ioc/beans.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
2獲得內(nèi)容 -- 不需要自己new,都是從spring容器通過id獲得
UserService userService = (UserService) applicationContext.getBean("userServiceId");
userService.addUser();

2.DI依賴注入

service里是這么寫的,通過setBookDao方法進行注入bookDao

private BookDao bookDao;

public void setBookDao(BookDao bookDao) {
    this.bookDao = bookDao;
}

@Override
public void addBook(){
    this.bookDao.save();
}

上面可以像下面這樣用spring配置文件實現(xiàn),使用<property> 用于進行屬性注入

<!-- 創(chuàng)建service -->
<bean id="bookServiceId" class="com.itheima.b_di.BookServiceImpl" >
   <property name="bookDao" ref="bookDaoId"></property>
</bean>
   
<!-- 創(chuàng)建dao實例 -->
<bean id="bookDaoId" class="com.itheima.b_di.BookDaoImpl"></bean>

3.核心Api

BeanFactory :這是一個工廠,用于生成任意bean,采取延遲加載,第一次getBean時才會初始化Bean,懶加載
ApplicationContext:是BeanFactory的子接口,功能更強大。(國際化處理、事件傳遞、Bean自動裝配、各種不同應(yīng)用層的Context實現(xiàn))。當(dāng)配置文件被加載,就進行對象實例化,餓漢式,直接會實例bean

ClassPathXmlApplicationContext 用于加載classpath(類路徑、src)下的xml
加載xml運行時位置 --> /WEB-INF/classes/...xml

FileSystemXmlApplicationContext 用于加載指定盤符下的xml
加載xml運行時位置 --> /WEB-INF/...xml
通過java web ServletContext.getRealPath() 獲得具體盤符

4.裝配bean

  • 默認(rèn)構(gòu)造
    <bean id="" class="">
  • 靜態(tài)工廠
    常用與spring整合其他框架(工具)
    靜態(tài)工廠:用于生成實例對象,所有的方法必須是static
    <bean id="" class="工廠全限定類名" factory-method="靜態(tài)方法">

例如:bean.xml配置文件

<bean id="userServiceId" class="com.itheima.c_inject.b_static_factory.MyBeanFactory" factory-method="createService"></bean>
public class MyBeanFactory {
    public UserService createService(){
      return new UserServiceImpl();
    }
}

String xmlPath = "com/itheima/c_inject/b_static_factory/beans.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService userService = applicationContext.getBean("userServiceId" ,UserService.class);
userService.addUser();
通過userServiceId獲得factory并調(diào)用createService方法
  • 實例工廠
    必須先有工廠實例對象,通過實例對象創(chuàng)建對象。提供所有的方法都是“非靜態(tài)”的。

5.作用域

image.png

singleton 單例,默認(rèn)值。
prototype 多例,每執(zhí)行一次getBean將獲得一個實例。例如:struts整合spring,配置action多例。

6.生命周期

  • 初始化和銷毀
    <bean id="" class="" init-method="初始化方法名稱" destroy-method="銷毀的方法名稱">
    目標(biāo)類
public class UserServiceImpl implements UserService {
    @Override
    public void addUser() {
        System.out.println("e_lifecycle add user");
    }
    
    public void myInit(){
        System.out.println("初始化");
    }
    public void myDestroy(){
        System.out.println("銷毀");
    }
}

spring配置

<!--  
    init-method 用于配置初始化方法,準(zhǔn)備數(shù)據(jù)等
    destroy-method 用于配置銷毀方法,清理資源等
-->
<bean id="userServiceId" class="com.itheima.e_lifecycle.UserServiceImpl" 
init-method="myInit" destroy-method="myDestroy" ></bean>
    
<!-- 將后處理的實現(xiàn)類注冊給spring -->
<bean class="com.itheima.e_lifecycle.MyBeanPostProcessor"></bean>

測試

@Test
public void demo02() throws Exception{
    //spring 工廠
    String xmlPath = "com/itheima/e_lifecycle/beans.xml";
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
    UserService userService = (UserService) applicationContext.getBean("userServiceId");
    userService.addUser();
        
    //要求:1.容器必須close,銷毀方法執(zhí)行; 2.必須是單例的
       //applicationContext.getClass().getMethod("close").invoke(applicationContext);
    // * 此方法接口中沒有定義,實現(xiàn)類提供
    applicationContext.close(); 
}

BeanPostProcessor 后處理Bean

spring 提供一種機制,只要實現(xiàn)此接口BeanPostProcessor,并將實現(xiàn)類提供給spring容器,spring容器將自動執(zhí)行,在初始化方法前執(zhí)行before(),在初始化方法后執(zhí)行after() 。 配置<bean class="">
spring提供工廠勾子,用于修改實例對象,可以生成代理對象,是AOP底層。


public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("前方法 : " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
        System.out.println("后方法 : " + beanName);
        // bean 目標(biāo)對象
        // 生成 jdk 代理
        return Proxy.newProxyInstance(
                    MyBeanPostProcessor.class.getClassLoader(), 
                    bean.getClass().getInterfaces(), 
                    new InvocationHandler(){
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("------開啟事務(wù)");
                            
                            //執(zhí)行目標(biāo)方法
                            Object obj = method.invoke(bean, args);
                            
                            System.out.println("------提交事務(wù)");
                            return obj;
                        }});
    }
}

將后處理的實現(xiàn)類注冊給spring
<bean class="com.itheima.e_lifecycle.MyBeanPostProcessor"></bean>

問題1:后處理bean作用某一個目標(biāo)類,還是所有目標(biāo)類?
所有
問題2:如何只作用一個?
通過“參數(shù)2”beanName進行控制

Bean的生命周期,生命周期詳情

  1. instantiate bean對象實例化
  2. populate properties 封裝屬性
  3. 如果Bean實現(xiàn)BeanNameAware 執(zhí)行 setBeanName
  4. 如果Bean實現(xiàn)BeanFactoryAware 或者 ApplicationContextAware 設(shè)置工廠 setBeanFactory 或者上下文對象 setApplicationContext
  5. 如果存在類實現(xiàn) BeanPostProcessor(后處理Bean) ,執(zhí)行postProcessBeforeInitialization
  6. 如果Bean實現(xiàn)InitializingBean 執(zhí)行 afterPropertiesSet
  7. 調(diào)用<bean init-method="init"> 指定初始化方法 init
  8. 如果存在類實現(xiàn) BeanPostProcessor(處理Bean) ,執(zhí)行postProcessAfterInitialization
  9. 執(zhí)行業(yè)務(wù)處理
  10. 如果Bean實現(xiàn) DisposableBean 執(zhí)行 destroy
  11. 調(diào)用<bean destroy-method="customerDestroy"> 指定銷毀方法 customerDestroy

參考:
http://www.lxweimin.com/p/0e629af94415

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,818評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,914評論 6 342
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • 北京人藝原創(chuàng)小劇場話劇《催眠》自2016年8月在實驗劇場首演以來,以頗具懸疑感的情節(jié)與極富現(xiàn)實意義的指向性贏得了不...
    易鹿閱讀 817評論 0 0
  • 著\MX先生 第二章 (上) 鬼上身? 林小曉只見眼前一片漆黑,她不知被誰點了睡穴,只能昏...
    MX先生閱讀 354評論 0 1