天天看點

第二章 深入探讨控制反轉(Ioc)和依賴注入(DI)

 注:希望大家看後,請給我一點評價,無論寫的怎麼樣,希望你們能給我支援。提出你寶貴的意見。我會繼續完善。謝謝您。朋友。

第二章 深入探讨控制反轉(Ioc)和依賴注入(DI)

在第一章中已經對控制反轉(Ioc)和依賴注入(DI)有了一個初步的了解,現在讓我們通過具體的程式代碼來真正的體驗一下,它們真的有那麼神奇嗎?

在演練之前,我還要先講一下理論知識,然後在演練程式代碼。讓你也體會到其中的神奇效果。

Spring 中的控制反轉(IoC)

(1)IoC = Inversion of Control(由容器控制程式之間的關系)

IoC,用白話來講,就是由容器來控制程式中的各個類之間的關系,而非傳統實作中,直接在代碼中由程式代碼直接操控。比如在一個類(A)中通路另外一個類中(B)的方法時,我們需要先去new 一個B的對象,然後調用所需的方法。他們的關系很顯然在程式代碼中控制,同時它們之間的耦合度也比較大,不利于代碼的重用。而我們現在把這種控制程式之間的關系交給Ioc容器,讓它去幫你執行個體化你所需要的對象,而你直接在程式中調用就可以了。這也就是所謂"控制反轉"的概念的由來:控制權由應用程式的代碼中轉到了外部容器,控制權的轉移,是所謂反轉的由來。

可能你不知道Ioc容器到底,或确切的指的是什麼?其實這裡的容器就相當于一個工廠一樣。你需要什麼,直接來拿,直接用就可以了,而不需要去了解,去關心你所用的東西是如何制成的,在程式中展現為實作的細節,這裡就用到了工廠模式,其實Spring容器就是工廠模式和單例模式所實作的。在第三章中我将會詳細介紹Spring的Ioc容器。

對于初學者,我想簡單的先說明幾點,不要把applicationContext.xml,或帶有bean的配置檔案了解為容器,它們隻是描述了要用到的類(bean)之間的依賴關系。Spring中的容器很抽象,不像Tomcat,Weblogic,WebSphere等那樣的應用伺服器容器是可見的。Spring的Ioc容器給人的感覺好像就是那些配置檔案(applicationContext.xml),我剛開始學時,也以為就是那些帶bean的配置檔案,雖然它對你學習Spring沒什麼影響,但如果想更深沉的了解就會迷茫的,我們在這裡要正确了解Spring的Ioc容器,以後對我們學習會有很大的幫助的。其實它的容器是有一些類和接口來充當的,你可能又會很迷茫。這就是它與别的架構的不同之處,這一點也正在展現了它的無侵入性的一點,不像EJB需要專門的容器來運作,侵入性很大的重量級的架構。Spring隻是一種輕量級的無侵入性的架構。說白了Spring的Ioc容器就是可以執行個體化BeanFactory或ApplicationContext(擴充了BeanFactory)的類.

可能你對Ioc容器還是不太了解,慢慢來,剛剛接觸的人都會很迷茫。我現在通過講解一個例子來說明它的工作原理,你可能會恍然大悟,原來如此簡單。

首先打開你的IDE編譯器,我用的是Eclipse3.2+MyEclipse5.5.1

我舉了一個大家比較熟悉的例子,使用者登入驗證。如果使用者名為:admin 密碼為:1234.

就會在控制台輸出”恭喜你,登入成功!”反之輸出“對不起,登入失敗!”并在日志檔案中記錄登入資訊。這裡我用到的是log4j.(日志記錄器)。我嚴格按分層思想和Spring中提倡的按接口程式設計的思想來演練這個簡單的例子。可能你會想這麼簡單的為什麼要那麼麻煩呢?怎麼不用一個類就解決了,做為程式員,我們時刻要記住,我們的代碼要易維護,可重用,易擴充,低耦合,高内聚。如果你了解這些原則,你自然會明白這樣麻煩的好處了。

我先整體的講解一下工程中的目錄結果:如下圖

首先我們看一下UserLogin接口和它的實作部分UserLoginImpl:

package com.chap2;

public interface UserLogin  {

    public boolean login(String userName,String passWord);

}

@SuppressWarnings("unused")

public class UserLoginImpl implements UserLogin {

    public boolean login(String userName,String passWord) {

       if (userName.equals("admin") && passWord.equals("1234"))

           return true;

       else

           return false;

    }

上面的接口隻是簡單的定義了一個方法,用于驗證使用者身份是否合法。在實作中隻是簡單的資料比較,實際當中應該從資料庫中去取資料進行驗證的,我們隻要能說明問題就可以了。

然後在看一下業務邏輯層的UserService這裡應該再對該類進行提取接口的,我隻是寫了一個類,如果你有興趣的話,你可以在加上接口像上面的UserLogin一樣,這樣做是為了降低代碼的耦合度,提高封裝性。

package com.chap2.service;

import com.chap2.UserLogin;

public class UserService {

    private String userName;

    private String passWord;

    private UserLogin userLogin;

    public void setUserName(String userName) {

       this.userName = userName;

    public void setPassWord(String passWord) {

       this.passWord = passWord;

    public void setUserLogin(UserLogin userLogin) {

       this.userLogin = userLogin;

    public String validateUser() {

       if (userLogin.login(userName, passWord))

           return "恭喜你,登入成功!";

           return "對不起,登入失敗!";

     }

在這個業務方法裡其實是對userLogin中的方法的再次封裝,這裡面可能隐藏的用到了正面封裝的模式(Facade)或叫做門面模式。它的好處是為了不讓客戶直接通路UserLogin中的方法,在實際的開發中UserLogin的方法應該放在DAO層中,這裡的DAO是資料通路層的意思,其實就是對資料庫執行的CRUD(增,删,改,查)操作。大家不要因為我多講了一些就迷。我們應該養成好的程式設計習慣。也就是分工明細。層與層之間隻能通過接口通路。至于上面提到的模式。你可以不先管了,隻要你知道它裡面用到了就可以了,以後我會把常用到的模式給你講講的。

這裡的前台用戶端的調用,我沒用到html,jsp之類的頁面,隻是用了一個main()方法簡單的模拟一下。同樣可以起到上面講的效果的。

public class Client {

    public static void main(String[] args) {

       // TODO 自動生成方法存根

       Log log = LogFactory.getLog(Client.class);

       //初始化,并加載配置檔案。

       ApplicationContext context = new ClassPathXmlApplicationContext(

              "applicationContext.xml");

       BeanFactory beanFactory = (BeanFactory) context;

       //得到執行個體化的UserService

       UserService userService = (UserService) beanFactory

              .getBean("userService");

       //輸出并記錄登入資訊

       log.info(userService.validateUser());

先簡單的看一下運作的結果是什麼。

2007-11-21 11:04:44,765 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - <Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@c51355: display name [org.springframework.context.support.ClassPathXmlApplicationContext@c51355]; startup date [Wed Nov 21 11:04:44 CST 2007]; root of context hierarchy>

2007-11-21 11:04:44,843 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [applicationContext.xml]>

2007-11-21 11:04:45,031 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - <Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@c51355]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1ce2dd4>

2007-11-21 11:04:45,046 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - <Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1ce2dd4: defining beans [userLogin,userService]; root of factory hierarchy>

2007-11-21 11:04:45,078 INFO [com.chap2.util.Client] - <恭喜你,登入成功!>

你可能會想登入使用者的資訊在那?

所有的配置資訊和執行個體化的工作都交給了Spring的Ioc容器,看看配置檔案的配置。

<?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 [url]http://www.springframework.org/schema/beans/spring-beans-2.0.xsd[/url]">

    <bean id="userLogin" class="com.chap2.UserLoginImpl" />

    <bean id="userService" class="com.chap2.service.UserService">

       <property name="userName">

           <value>admin</value>

       </property>

       <property name="passWord">

           <value>1234</value>

       <property name="userLogin">

           <ref bean="userLogin" />

    </bean>

</beans>

上面的配置檔案隻是描述了我們所用到類的調用關系,和資料的指派<初始值>。我們在UserSerivce中要用到UserLogin中的方法,我們隻需在這裡簡單的設定它的一個屬性(property)就可以了,以前的程式設計要在代碼中實作了,通過先new 一個UserLogin的實作對象,然後在調用其中的方法。我們這裡隻需在配置檔案中簡單的配置一下就可以了,在程式用到這個方法時,容器會自動先執行個體化UserLoginImpl,然後把它交給UserService使用。在UserService中不用擔心執行個體化,以及管理它的生命周期了。全部讓Spring的Ioc容器管理記行了。這樣做減少了它們之間的依賴性,也就是降低它們的耦合度。

整個程式的工作流程是這樣的。

在main()方法中通過ApplicationContext來加載配置檔案,然後把它轉換為BeanFactory。這就是我們要講的Spring中控制反轉容器。它把所有的類都初始化,并放在這個容器中。在我們需要的時候隻需像 UserService userService = (UserService) beanFactory.getBean("userService");通過配置檔案中bean的id或name調用就可以了。不必在像以前的程式設計那樣UserService userSerivce=new UserService();這樣做還有一個好處就是讓容器來管理它的生命周期,我們隻需用就可以了,用完了在交給容器管理。而不用擔心什麼時候銷毀它。

附加一些log4j的檔案資訊。就是工程目錄下的log4j.properties.一定要放到src目錄下,要不然程式運作時,它會找不到的。或直接把它放到class目錄下。Log4j就是簡單的記錄一些日志資訊,為以後使用的。下面是它的配置。其實還有多中形式的配置,可以是java屬性檔案形式的。像userLogin.log, userLogin.log.1就是一些備份檔案。裡面記錄了程式運作時的一些資訊。

log4j.rootLogger=INFO, stdout,logfile

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n

log4j.appender.logfile=org.apache.log4j.RollingFileAppender

log4j.appender.logfile.File=userLogin.log

log4j.appender.logfile.MaxFileSize=2KB

# Keep three backup files.

log4j.appender.logfile.MaxBackupIndex=3

# Pattern to output: date priority [category] - message

log4j.appender.logfile.layout=org.apache.log4j.PatternLayout

log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

在這裡我就不具體的講了。在以後的章節中會講到的。

  今天先寫到這裡.

本文轉自 weijie@java 51CTO部落格,原文連結:http://blog.51cto.com/weijie/66456,如需轉載請自行聯系原作者