Spring入門筆記

一、理論知識

1. 依賴注入、控制反轉

依賴注入:在運行期,由外部容器動態地將依賴對象注入到組件中
控制反轉:應用本身不負責依賴對象的創建及維護,依賴對象的創建及維護是由外部窗口負責得。這樣控制權就由應用轉移到了外部容器,控制權的轉移就是所謂的反轉。

2.spring 的主要特性。

(1 )降低組件之間的耦合度,實現軟件各層之間的解耦。
(2 )可以使用容器提供的衆多服務,如:事務管理服務、消息服務、JMS 服務、持久化服務等等。
(3 )容器提供單例模式支持,開發人員不再需要自己編寫實現代碼。
(4 )容器提供了AOP 技術,利用它很容易實現如權限攔截,運行期監控等功能。
(5 )容器提供的衆多輔作類,使用這些類能夠加快應用的開發,如:JdbcTemplate 、HibernateTemplate.
(6 )對主流的應用框架提供了集成支持。

3. 常用技術

控制反轉/ 依賴注入--- 面向切入編程--- 與主流框架的整合、管理---

 

二、基本實例

1. 準備搭建環境

dist/spring.jar

lib/jakata-commons/commons-loggin.jar

如果使用了切面編程,還需下列jar 文件:

lib/aspectj/aspectjweaver.jar 和aspectjrt.jar

lib/cglib/cglib-nodep-2.1.3.jar

如果使用了jsr-250 中的註解,還需要下列jar 文件:

lib/j2ee/common-annotations.jar

2. 搭建並測試環境

建立名爲spring_01_base 項目 ,根據需求導入jar 包。建立一個Junit 測試單元SpringEnvTest ,測試代碼如下:
@Test

    public   void testEnv() {

ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

    }

beans.xml 配置文件在此省略(見下)。運行此測試如無錯,則說明環境搭建成功。
說明: beans.xml 可以在類路徑下進行配置,也可以在具體的目錄下配置。可以是一個配置文件,也可以是多個配置文件組成 String 數組傳入。

3. 實例

作如下準備工作:(1 )建立UseDao 接口,代碼如下:
package com.asm.dao;

public interface UserDao {

    void save();

}

(2 )建立UserDao 接口的實現類,UserDaoImpl
package com.asm.dao.impl;

import com.asm.dao.UserDao;

public class UserDaoImpl implements UserDao{

    public void save() {

       System. out .println( " 執行 save 方法 ..." );

    }

}

(3 )在src 目錄下配置此beans.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"

    xsi:schemaLocation = "http://www.springframework.org/schema/beans           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >

    < bean id = "userDaoImpl" class = "com.asm.dao.impl.UserDaoImpl" />

</ beans >
說明:bean 代表一個實質的java 類,通過它的id 可以獲取一個此類的一個對象。
補充 :讓xml 配置文件在編譯時提示

[windows][preferences][myeclipse][files and editors][xml][xml catalog] 點add ,在出現窗口的location 中選“file system ”, 然後在spring 解壓目錄的dist/resources 目錄中選擇“spring-beans-2.5.xsd ”, 並將key Type 值改爲“Schema Location ”,key 值爲:http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
(4 )Junit 測試單元SpringEnvTest 中增加如下代碼測試:

    @Test

public   void base() {

ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

       UserDao userDao = (UserDao) ctx.getBean( "userDaoImpl" );

       userDao.save();

}
以上的代碼就是通過配置文件 beans.xml 獲取所需要的實例對象。

4. 三種bean 的生成方式

除了上面使用的類直接生成方式,還有bean 靜態工廠及bean 實例工廠。
bean 靜態工廠的配置如下:
< bean id = "userDaoImpl2" class = "com.asm.dao.impl.UserDaoImplFactory" factory-method = "getUserDaoImpl" />

相應的工廠類代碼如下:
package com.asm.dao.impl;

public class UserDaoImplFactory {

    public static UserDaoImpl getUserDaoImpl(){

       return new UserDaoImpl();

    }

}
bean 實例工廠的配置如下:
< bean id = "factory" class = "com.asm.dao.impl.UserDaoImplFactory2" />

< bean id = "userDaoImpl3" factory-bean = "factory" factory-method = "getUserDaoImpl" />

相應的工廠類的代碼如下:
package com.asm.dao.impl;

public class UserDaoImplFactory2 {

    public UserDaoImpl getUserDaoImpl() {

       return new UserDaoImpl();

    }

}

5.bean 的作用域

singleton: 返回bean 的同一個實例,也是默認的作用域(無狀態bean 使用此作用域)

prototype: 每次請求都會創建一個實例(有狀態bean 使用此作用域)
request 、session 、global session 這三個作用域主要用在web 應用中

6.bean 的生命週期

(1) 什麼時候初始化bean 實例

當scope =singleton ,即默認情況,會在裝載配置文件時實例化。如果希望在調用getBean 時才初始化,可以使用lazy-init="true" 補充:如果希望希望該配置文件中的所有bean 都延遲初始化,則應在beans 根結點中使用lazy-init="true" 。

當scope=prototype 時 ,在調用getBean() 方法時纔會初始化。
(2 )生命週期:

構造器、init 方法、獲取bean 後的操作、destroy 方法(ctx.close 、注意 如果bean 的scope 設爲prototype 時,當ctx.close 時,destroy 方法不會被調用)

7. 屬性注入Setter 方式

(1 )簡單屬性(如String ):
< bean  id = "userServiceBean" class = "com.asm.service.UserServiceBean" >

       < property name = "id" value = "10" ></ property >

       < property name = "username" value = " 張某某 " ></ property >

</ bean >
(2) 對象屬性- 外部bean 注入:在上面的<bean> 中增加如下配置:

< property name = "userDao" ref = "userDaoImpl" />
對象屬性- 內部bean 注入:在上面的<bean> 中增加如下配置:
< property name = "userDao" >

       < bean id = "temp" class = "com.asm.dao.impl.UserDaoImpl" />

</ property >   

(3) 集合屬性注入:
    < property name = "list" >

           < list >

              < value > List 值一 </ value >

              < value > List 值二 </ value >

              < value > List 值三 </ value >

           </ list >

       </ property >

 

       < property name = "set" >

           < set >

              < value > Set 值二 </ value >

              < value > Set 值一 </ value >

              < value > Set 值三 </ value >

           </ set >

       </ property >

 

       < property name = "map" >

           < map >

              < entry key = "one" value = " " />

              < entry key = "two" value = " " />

              < entry key = "three" value = " " />

           </ map >

       </ property >

       < property name = "pro" >

           < props >

              < prop key = "p1" > first </ prop >

              < prop key = "p2" > second </ prop >

              < prop key = "p3" > third </ prop >

           </ props >

       </ property >

注意: 在相應的字段上一定要有setter 方法, 才能注入。

補充 :使用繼承。在beans.xml 中的配置如下:

< bean abstract = "true" id = "parent" >

    < property name = "username" value = " 張某某 " ></ property >

</ bean >

< bean  id = "XXX"  class = "com.asm.vo.User" parent = "parent" >

    < property name = "password" value = "123456" ></ property >

</ bean >

相當於在 XXX bean 實例中也有 username 屬性設置。

8. 屬性注入構造器方式

< bean id = "userServiceBean2" class = "com.asm.service.UserServiceBean" >

       < constructor-arg index = "0" value = " 李某某 " />

       < constructor-arg index = "1" ref = "userDaoImpl" />

       < constructor-arg index = "2" >

           < list >

              < value > List 值一 </ value >

              < value > List 值二 </ value >

              < value > List 值三 </ value >

           </ list >

       </ constructor-arg >

</ bean >

UserServiceBean 對應的構造方法代碼如下:
public UserServiceBean(String username, UserDao userDao, Set<String> set) {

       this . username =username;

       this . userDao =userDao;

       this . set =set;

}
注意 :此方法會覆蓋掉默認的構造方法,導致要依賴默認構造方法的配置不可用,因此我們還應爲此類提供一個默認的構造器。

三、使用註解方式注入

1. 準備

註解方式的注入主要針對對象屬性的注入。
使用註解功能要引用註解包,另beans.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:context = "http://www.springframework.org/schema/context"

    xsi:schemaLocation = "http://www.springframework.org/schema/beans

      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

      http://www.springframework.org/schema/context          http://www.springframework.org/schema/context/spring-context-2.5.xsd" >

    < context:annotation-config /> <!-- 開啓註解功能 -->

</ beans >

2.Resource 註解實例

拷貝上一個項目爲spring_02_annotation 項目 ,修改UserServiceBean 爲如下形式:

package com.asm.service;

public class UserServiceBean {

    @Resource (name = "userDaoImpl" )

    private UserDao userDao ;

    private UserDao userDao2 ;

    @Resource

    public void setUserDao2(UserDao userDao2) {

       this . userDao2 = userDao2;

    }

    public void test() {

       userDao .save();

       userDao2 .save();

    }

}

然後在bean.xml 中的配置如下:

< bean id = "userDaoImpl" class = "com.asm.dao.impl.UserDaoImpl" />

< bean id = "userServiceBean" class = "com.asm.service.UserServiceBean" />
簡要說明 :Resouce 註解可以在字段上標記,也可以在對應的setter 方法上標記。此註解可以不使用name 屬性,它會自動去查找匹配的類型(先以字段名稱爲name 的值查找,如找不到會再根據依賴對象的類型查找)。但只要使用了name 屬性,就應確保name 的值在xml 中有相應的bean Id 對應。它是屬於java 本身的註解, Resource 默認按屬性名稱裝配

3.Autowired 註解實例

@Autowired (required= false )

@Qualifier ( "userDaoImplXXX" )

private UserDao userDao3 ;
說明 :Autowired 默認是按照類型來查找對應的bean 實例注入,如果想注入指定名稱的bean 實例,可以使用Qualifier 註解來指定名字。Required 屬性設爲true 時,如果不能成功注入則會報告異常,如果爲設爲false 而不能成功注入,則會將userDao3 設爲null 。同樣地,它也實用於setter 方法。它屬於spring 特有的註解,Autowired 默認按類型裝配。

4. 自動裝配

自動裝配(瞭解,不建議使用):除了要設置字段的setter 方法外,還應在beans.xml 配置文件中設置如下內容:
< bean id = "userServiceBean2"

    class = "com.asm.service.UserServiceBean2" autowire = "byType" />
說明:除了byType 外,autowire 的可選屬性如下:

byName :根據類中的字段名來查找對應的bean, 如不能成功注入,則字段設爲null.

byType :根據類型裝配,如果發現多個類型都能夠匹配,則拋出異常。

Consturctor :也byType 相似,不同之處在於它應用於構造器的參數,如果容器中沒有找到與構造器參數類型一致的bean, 則拋出異常。

Autodetect :通過bean 類的自省機制來決定是使用consturctor 還是byType 方式進行自動裝配。如果發現默認的構造器,那麼將使用byType 方式。

四、自動掃描管理 bean

1. 準備工作

在前面使用註解的時候,除了<beans> 結點配置增加了名稱空間說明,另還增加了 < context:annotation-config /> 配置,它的作用就是註冊一個處理器。通常情況下,我們要使用某個bean 實例,總會配置<bean> 相關內容。Spring 最新版本可以簡化這一操作,即是說我們只要在配置文件作如下設置:

< context:component-scan base-package = " 包名 " /> 便可以自動管理指定包名及子包下標住了@service (業務層組件)、@controller (控制層組件)、@repository (數據訪問組件)或@component (泛指組件)的類,並把它們作一個實例bean, 相當於在beans.xml 中配置了<bean> 元素。需要說明的是,使用了此配置同時意味着還註冊了註解配置的處理器,所以在這些類中用到了註解配置時並不需要再配置 < context:annotation-config />
爲什麼提出自動掃描管理:在一些比較大的項目中,涉及到的bean 實例會有很多,如果依次對每個bean 實例進行配置,不但配置內容繁瑣,而且配置文件也會顯得雜亂。因此spring 提出了自動掃描bean ,它依據在xml 文件中指定的包名和類中標記的component 系列註解。

2. 實例

建立spring_03_autoscan 項目 ,內容基本和前面兩個項目一樣,只是在要納入spring 管理的類前增加了component 這樣的註解。Beans.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:context = "http://www.springframework.org/schema/context"

    xsi:schemaLocation = "http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

           http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd" >

    < context:component-scan base-package = "com.asm.service" />

    < context:component-scan base-package = "com.asm.dao.impl" />

</ beans >

說明:以上的配置會自動管理service 包和impl 包及它們子包下的帶有component 標記的類,上面兩行配置代碼等價於
< context:component-scan base-package = "com.asm" />
下面以UserServiceBean 爲例進行說明,代碼如下:

package com.asm.service;

@Service ( "usb" ) @Scope ( "singleton" )

public class UserServiceBean {

    @Resource (name = "userDaoImpl" )

    private UserDao userDao ;

    private UserDao userDao2 ;

 

    @Autowired (required = true )

    @Qualifier ( "userDaoImpl" )

    private UserDao userDao3 ;

    @Resource

    public void setUserDao2(UserDao userDao2) {

       this . userDao2 = userDao2;

    }

    public UserServiceBean() {

    }

    @PostConstruct

    public void init() {

       System. out .println( "init method is called" );

    }

    public void test() {

       System. out .println( "********************************" );

       userDao .save();

       userDao2 .save();

       System. out .println( userDao3 );

       // userDao3.save();

       System. out .println( "********************************" );

    }

}

說明 :如果使用 Service 這些註解時不指定名稱,這些實例bean 的名稱就是類名(但首字母小寫),也可以指定實例bean 的名字,比如這裏指定其名字爲“usb ”,scope 註解配置了bean 的作用範圍, PostConstruct 註解指定了bean 的init 方法。關於其它的一些註解配置參文檔(3.11-3.12 )。
它的junit 測試代碼如下:
public class AutoScanTest {

    @Test

    public void base () {

       ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

       UserServiceBean udb = (UserServiceBean) ctx.getBean( "usb" );

       udb.test();

    }

}
小結 使用自動掃描管理的核心:配置掃描的包、類前的component 標記、瞭解常用註解。

 

五、 AOP 技術

1. 引出問題

建立spring_04_aop 項目 ,在該項目下有一個UserDao 接口,代碼如下:
package com.asm.dao;

public interface UserDao {

    void save();

    void update();

}

該接口的實現類UseDaoImp ,代碼如下:

package com.asm.dao.impl;

import com.asm.dao.UserDao;

public class UserDaoImp implements UserDao {

    private String username ;

    public UserDaoImp() {

    }

    public UserDaoImp(String username) {

       this . username = username;

    }

    public String getUsername() {

       return username ;

    }

    @Override

    public void save() {

       System. out .println( "save method is called " + username );

    }

    @Override

    public void update() {

       System. out .println( "update method is called" + username );

    }

}

需求如下:如果實現類的username!=null, 纔可以調用save 與update 方法,爲null 則不能調用。當然要解決此問題,可以在save 與update 方法內部進行判斷,但是如果在方法內部進行判斷,代碼則失去了靈活性,如果以後的需求改變,比如變成username.equals 時,則又要在update/save 方法中重新進行一次判斷。如果save/update 這樣的方法很多,這樣就會很麻煩。其實要解決此問題,可以通過動態代理技術實現。這裏的需求其實就是根據要求來攔截一些業務方法,這種編程問題稱之爲橫切性關注點

代理的目標對象必須實現了一個接口--- 橫切關注點

2. 動態代理實現[JDK]

建立代理工廠 ProxyFactory ,代碼如下:
package com.asm.dao.impl.factory;

public class ProxyFactory implements InvocationHandler {

    private Object target ;

    public Object createUserDaoImp(Object target) {

       this . target = target;

return Proxy.newProxyInstance ( this . target .getClass().getClassLoader(), this . target .getClass().getInterfaces(), this );

    }

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

       UserDaoImp udi = (UserDaoImp) target ;

       Object result = null ;

       if (udi.getUsername() != null ) {

           result = method.invoke( target , args);

       }

       return result;

    }

}

簡析動態代理:此類根據傳遞的target 對象來生成此目標對象的代理對象,需要強調的是動態代理技術所要代理的對象必須實現一個接口newProxyInstance 參數說明:第一個參數是目標對象的類裝載器,第二個參數是目標對象的接口,第三個參數是回調對象,生成的代理對象要執行方法時就是依靠這個回調對象的invoke 方法來進行目標對象的方法回調。關於動態代理的其它細節不在此討論。

建立junit 測試代碼,內容如下:

public class AopProxyTest {

    @Test   // 用戶名爲空,不執行方法

    public void testProxy(){

       ProxyFactory pf= new ProxyFactory();

       UserDao ud=(UserDao) pf.createUserDaoImp( new UserDaoImp());

       ud.save();

    }

    @Test   // 用戶名爲不爲空,才執行 save 方法

    public void testProxy2(){

        ProxyFactory pf = new ProxyFactory();

    UserDao ud=(UserDao) pf .createUserDaoImp( new UserDaoImp( " 張某 " ));

        ud.save();

    }

}

3.cglib 實現代理

JDK 的Proxy 實現代理要求被代理的目標對象必須實現一個接口,而如果目標對象沒有實現接口則不能使用Proxy 來代理。其實也可以藉助cglib 來實現代理。操作步驟如下
步驟一、建立 UserDaoImp2 類,它與 UserDaoImp 的唯一區別就是沒有實現任何接口。
步驟二、導入cglib.jar 包,創建cglib 代理工廠,代碼如下:
package com.asm.dao.impl.factory;

public class CglibFactory implements MethodInterceptor {

    private Object target ;

    public Object createUserDaoImp2(Object target) {

       this . target = target;

       Enhancer enhancer = new Enhancer();

       enhancer.setSuperclass( this . target .getClass());
// cglib 創建的代理對象,其實就是繼承了要代理的目標類,然後對目標類中所有非 final 方法進行覆蓋,但在覆蓋方法時會添加一些攔截代碼。

       enhancer.setCallback( this ); // 註冊回調器

       return enhancer.create();

    }

    @Override

    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)

           throws Throwable {

       UserDaoImp2 udi = (UserDaoImp2) target ;

       Object result = null ;

       if (udi.getUsername() != null ) {

           // 前置通知

           try {

              result = methodProxy.invoke( target , args);

              // 後置通知

           } catch (Exception e) {

              e.printStackTrace();

              // 例外通知

           } finally {

              // 最終通知

           }

       }

       return result;

    }

}

說明:注意註釋的通知,通知就是攔截到方法後執行的一些代碼,比如前置通知,就是說在回調目標方法時執行的一些操作。

步驟三、建立測試代碼(省略,和五.2 的測試代碼相似)

4.aop 理論知識

橫切性關注點:對哪些方法攔截,攔截後怎麼處理,這些關注就稱之爲橫切性關注點
切面(aspect ):類是對物體特徵的抽象,而切面是指對橫切性關注點的抽象。

連接點(joinpoint ): 被攔截到的點,因爲spring 只支持方法類型的連接點,所以在spring 中連接點指的就是被攔截到的方法。實際上連接點還可以是字段或構造器。
切入點(pointcut ):對連接點進行攔截的定義。
通知(advice ):所謂通知就是指攔截到連接點之後的要執行的代碼。通知分爲前置、後置、異常、最終。環繞通知五類。
目標對象:代理的目標對象。
織入(weave ):將切面應用到目標對象並導致代理對象創建的過程
引入(introduction ):在不修改代碼的前提下,引入可以在運行期 爲類動態地添加一些方法或字段。

5. 基於spring 的AOP 實現

步驟一、導入spring 開發的基本包(包括切面及註解包)
步驟二、編寫切面類TheInterceptor ,代碼如下:

package com.asm.dao.impl.factory;

@Aspect

public class TheInterceptor {

    @Pointcut ( "execution (* com.asm.dao.impl.UserDaoImp.*(..))" )  

    // 聲明一個切入點 ( 第一個 * 後要留一個空格 )

    private void anyMethod() {

    }

 

    @Before ( "anyMethod()" ) // 前置通知

    public void before() {

       System. out .println( " 前置通知 " );

    }  

    @AfterReturning ( "anyMethod()" ) // 後置通知

    public void afterReturning(){

       System. out .println( " 後置通知 " );

    }  

}

簡析:Aspect 註解聲明此類爲一個切面類,Pointcut 註解用來聲明一個切入點,括號中的參數是切入點的表達式,這裏的表達式的意思是對UserDaoImp 類的所有方法進行攔截。關於切入點表達式後面會有詳細的說明。anyMethod 是爲切入點起一個名字,後面的“通知”都要依賴這個名字。
步驟三、beans.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:context = "http://www.springframework.org/schema/context"

    xmlns:aop = "http://www.springframework.org/schema/aop"

    xsi:schemaLocation = "http://www.springframework.org/schema/beans            http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

            http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd

           http://www.springframework.org/schema/aop           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd" >

    < aop:aspectj-autoproxy /> <!-- 開啓切面編程功能 -->

    < bean id = "userDaoImp" class = "com.asm.dao.impl.UserDaoImp" />   

< bean id = "theInterceptor"

       class = "com.asm.dao.impl.factory.TheInterceptor" />

</ beans >

說明:要想切面類起作用,首先要把切面類納入spring 容器管理。
步驟四、編寫junit 測試單元
    @Test

public void base() {

ApplicationContext ctx = new ClassPathXmlApplicationContext ( "beans.xml" );   

    // UserDaoImp udii=(UserDaoImp) ctx.getBean("userDaoImp");

    UserDao ud = (UserDao) ctx.getBean( "userDaoImp" );

    System. out .println(ud.getClass().getName());

    ud.save();

}
說明:由於開啓了切面編程功能,所以當我們獲取一個被切面類監控管理的 bean 對象 —UserDaoImp 時,它實際上獲取的是此對象的一個代理對象,而在 spring 中對代理對象的處理有如下 原則 :( 1 )如果要代理的對象實現了接口,則會按照 Proxy 的方式來產生代理對象,這即是說產生的代理對象只能是接口類型,比如起用上面注掉的代碼就會報錯,而且通過下面的打印語句我們也可以看出產生的是一個代理對象。( 2 )要代理的對象未實現接口,則按 cglib 方式來產生代理對象。   另還要注意:要想 spring 的切面技術起作用,被管理的 bean 對象只能是通過 spring 容器獲取的對象。比如這裏如果直接 new UseDaoImp 對象,則 new 出的對象是不能被 spring 的切面類監控管理。
補充 :測試被代理對象未實現接口時,spring 切面技術的應用。
步驟一、修改切面類 TheInterceptor 切入點爲如下內容:
@Pointcut ( "execution (* com.asm.dao.impl.*.*(..))"
說明:攔截 impl 包下的所有類所有方法
步驟二、在beans.xml 中增加如下內容:
< bean id = "userDaoImp2" class = "com.asm.dao.impl.UserDaoImp2" />
步驟三、junit 測試代碼如下:
public void base2() {

    ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

    UserDaoImp2 udi2 = (UserDaoImp2) ctx.getBean( "userDaoImp2" );

    System. out .println(udi2.getClass().getName());

    System. out .println(udi2.getClass().getSuperclass ().getName());

    udi2.save();

}
說明: UseDaoImp2 未實現任何接口,因此在 spring 中利用切面技術來管理此類使用的動態代理技術實質是 cglib 的動態代理方式,所以產生的代理對象實質是被代理對象的一個子類,通過上面的控制檯打印語句可以看出。
小結:( 1 )聲明 aspect 的切面類要納入 spring 容器管理才能起作用。( 2 )被管理的 bean 實例要通過容器的 getBeans 方法獲取。 3 )依據被管理的 bean 是否實現接口, spring 採取兩種方式來產生代理對象。( 4 )在 xml 文件中啓用 <aop:aspectj-autoproxy/>

6. 通知應用實例(基於註解)

在前一節,我們應用了前置通知和後置通知,除了這兩個通知外,下面接着演示其它通知的應用。

(1 )最終通知

在切面類 TheInterceptor 中增加如下代碼即可:略去測試。
@After ( "anyMethod()" ) // 最終通知

    public void after() {

    System. out .println( " 最終通知 " );

}

(2 )異常通知

爲了演示此實例,我們在UseDaoImp 中增加如下代碼以拋出異常:

int i=1/0; 在然後在切面類 TheInterceptor 中增加如下代碼:
@AfterThrowing ( "anyMethod()" )   // 例外通知

public void AfterThrowing() {

    System. out .println( " 例外通知 " );

}
當獲取代理對象並調用save 方法時會拋出異常,例外通知便會得以執行。

(3 )環繞通知

@Around ( "anyMethod()" ) // 環繞通知

public Object around(ProceedingJoinPoint pjp) throws Throwable {

    System. out .println( " 進入環繞 " );

    //if(){    // 進行一些判斷,再執行環繞

    Object result = pjp.proceed();

    //}

    System. out .println( " 退出環繞 " );

    return result;

}
注意的是方法的參數及拋出異常類型的固定寫法(方法名可以是任意得),另在該方法中必須執行 pjp.proceed() 才能讓環繞通知中的兩處打印代碼得以執行。即是說要想環繞通知的攔截處理代碼起作用必須調用 pjp.proceed 方法。 補充 :環繞通知通常可以用來測試方法的執行時間,在 pjp.proceed 前獲取一個時間,在 pjp.proceed 方法後再獲取一個時間。最後兩個時間相減即可得方法執行時間。

(4 )傳遞參數給通知

首先在UseDao 接口中增加如下代碼:
String add(String name);
然後再在UserDaoImp 中實現此方法,代碼如下:
public String add(String name) {

    System. out .println( "add method is called [ " + name+ " ]" );

    return " 添加成功 " ;

}
需求:獲取調用add 方法傳遞的參數。操作步驟如下:在切面類增加如下代碼:
@Before ( "anyMethod() && args(name)" ) // 前置通知 , 只針對 UseDaoImp add 方法

public void beforeAdd(String name) {

    System. out .println( " 前置通知 :" + name);

}
說明:在前置通知的方法中有一個參數,然後再把此參數作爲攔截條件(即是說攔截帶有一個 String 類型參數的方法)。 args 的名字和 beforeAdd 方法參數名字相同。
測試代碼:
public void advieeTest () {

    ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );

    UserDao ud=(UserDao) ctx.getBean( "userDaoImp" );

    ud.add( "xxx" );

}

(5 )獲取方法的返回值

我們知道add 方法有一個返回值,我們對此方法進行攔截並獲取返回值,在切面類中增加如下代碼:
@AfterReturning (pointcut = "anyMethod()" , returning = "result" )

// 後置通知 , 監聽返回結果,針對 UserDaoImp getUsername() 方法

public void afterReturningRes(String result) {

    System. out .println( " 後置通知 , 返回結果 :" + result);

}
說明: afterReturningRes 方法的參數就是要返回的參數類型, returning 標記的就是的結果,它的取值與該方法參數名相同。   測試代碼同( 4 )。

(6 )獲取拋出的異常

切面類的增加如下代碼:

@AfterThrowing (pointcut= "anyMethod" ,throwing= "e" )

public void catchException(Exception e){

    System. out .println( " 獲取拋出的異常: " +e); 

}
throwing 的取值和方法的參數名相同,測試代碼省略。

7. 通知應用實例(基於XML )

步驟一、複製 TheInterceptorX 類爲 TheInterceptorXML ,並去掉所有註解。

步驟二、建立beansXML.xml 配置文件,內容如下:
< aop:aspectj-autoproxy /> <!-- 開啓切面編程功能 -->

< bean id = "userDaoImp" class = "com.asm.dao.impl.UserDaoImp" />

< bean id = "aspectBean"

    class = "com.asm.dao.impl.factory.TheInterceptorXML" />

< aop:config >

< aop:aspect id = "asp" ref = "aspectBean" >     -- 聲明一個切面類

       < aop:pointcut id = "thecut"                  -- 聲明一個切入點

       expression = "execution(* com.asm.dao.impl.UserDaoImp.*(..))" />

       < aop:after-returning pointcut-ref = "thecut" method = "afterReturningRes" returning = "result" />

       < aop:around pointcut-ref = "thecut" method = "around" />

       < aop:after-throwing pointcut-ref = "thecut" method = "catchException" throwing = "e" />                  

       < aop:after pointcut-ref = "thecut" method = "after" />

       < aop:before pointcut-ref = "thecut" method = "before" />

    </ aop:aspect >

</ aop:config >

測試代碼如下:
public void advieeTest() {

       ApplicationContext ctx = new ClassPathXmlApplicationContext( "beansXML.xml " );

       UserDao ud=(UserDao) ctx.getBean( "userDaoImp" );

       ud.add( "xxx" );      

}
未解決問題:不能成功傳參給前置通知。

8. 解析切入點表達式

1. 格式: execution( 返回值 空格 方法選擇) 。兩部分組成,中間一定要有空格
返回值:可以是* ,說明攔截任何方法。Java.lang.String( 全名) ,攔截返回值爲String 類型的方法。 常用的實例如下:

方法選擇:包名[ 類名].* ()。設定要攔截的方法簽名。

表達式(省略 execution

說明

(java.lang.String 方法選擇略 )

攔截返回值爲 String 類型的方法

(!void  方法選擇略 )

攔截返回值非空的方法

(* com.asm..*.*(..))

攔截 com.asm 包及子包下每個類的全部方法

(* com.asm.*.*(..))

攔截 com.asm 包下每個類的全部方法

(* com.asm.User.*(..))

攔截 asm 包下 User 類的所有方法

(* com.asm.User.*
(java.lang.String,..))

攔截 User 類中第一個參數爲 String, 後面參數任一的方法

待增加

 

待增加

 

9. 總結

面向切面的常見應用(如權限攔截)、spring 的aop 依賴兩種方式實現代理(依被代理的對象是否實現接口而定)、通知概念、基於註解與基於XML 兩種方式來配置切面、基本步驟(依要攔截的方法來設定切入點,依據業務需求實現攔截通知代碼,切面納入spring 容器管理,要被監控的類只能是通過Spring 容器獲取)、切入點的格式。

六、與 JDBC 集成

1. 搭建環境

建立spring_05_integrationJdbc 項目 ,此項目使用dbcp 作爲數據源來整合JDBC 技術。因此除了spring 所需的jar 包,還應導入dbcp 的jar 包:commons-dbcp-1.2.2.jar 及此 jar 所依賴的兩個apache 開源jar 包:commons-collections-3.1.jar 、commons-pool.jar 。由於涉及到數據庫操作,還應導入數據庫驅動包,這裏選擇的是mySQL 驅動。

2. 基於註解的事務管理

步驟一、配置事務管理器。

建立beans.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:aop = "http://www.springframework.org/schema/aop"

    xmlns:tx = "http://www.springframework.org/schema/tx"

    xmlns:context = "http://www.springframework.org/schema/context"

    xsi:schemaLocation = "

       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd

       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd

       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd " >

< bean id = "theDatasource"

       class = "org.apache.commons.dbcp.BasicDataSource" >

    < property name = "driverClassName" value = "com.mysql.jdbc.Driver" />

    < property name = "url" value = "jdbc:mysql://localhost:3306/sjdbc" />

    < property name = "username" value = "root" />

    < property name = "password" value = "123456" />

    < property name = "initialSize" value = "2" />

    < property name = "maxActive" value = "100" />

    < property name = "maxIdle" value = "2" />

    < property name = "minIdle" value = "1" />

</ bean >

< bean id = "txManager"     class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >

    < property name = "dataSource" ref = "theDatasource" />

</ bean >

< tx:annotation-driven transaction-manager = "txManager" />

說明:首先是配置了一個數據源,以供數據源事務管理器 配置引用。接着配置了數據源事務管理器,隨後開啓了基於 @Transactional 註解的事務管理器,開啓後,只要是被 spring 管理的 bean 且打有 @Transactional 註解的 bean 都會受配置的事務管理。關於這裏的配置可參看 spring 文檔 9.5.6

步驟二、準備使用環境。

建立 UserDao 接口,代碼如下:
package com.asm.dao;

public interface UserDao {

    void save(User user);

    void delete (User user);

    void update(User user);

    User get( int id);

    List<User> getUsers();

}

建立 UserDaoImp 類實現 UseDao 接口,代碼如下:
package com.asm.dao.impl;

@Transactional

public class UserDaoImp implements UserDao {

    private JdbcTemplate jdbcTemplate ;

 

    public void setDatasouce(DataSource datasource) {

       jdbcTemplate = new JdbcTemplate(datasource);

    }

    public void delete(User user) {

       jdbcTemplate .update( "delete from user where id=?" , new Object[] { user.getId() },

              new int [] { java.sql.Types. INTEGER });

    }

 

    public User get( int id) {

       return (User) jdbcTemplate .queryForObject( "select * from user where id=?" ,

              new Object[] { id }, new int [] { java.sql.Types. INTEGER }, new RowMapper() {

                  public Object mapRow(ResultSet rs, int arg1) throws SQLException {

                     User user = new User();

                     user.setId(rs.getInt( "id" ));

                     user.setName(rs.getString( "name" ));

                     return user;

                  }

              });

    }

 

    @SuppressWarnings ( "unchecked"

    public List<User> getUsers() {

       return (List<User>) jdbcTemplate .query( "select * from user" , new RowMapper() {

          

           public Object mapRow(ResultSet rs, int arg1) throws SQLException {

              User user = new User();

              user.setId(rs.getInt( "id" ));

              user.setName(rs.getString( "name" ));

              return user;

           }

       });

    }

 

    public void save(User user) {

       jdbcTemplate .update( "insert into user(name) values(?)" , new Object[] { user.getName() },

              new int [] { java.sql.Types. VARCHAR });

    }

 

    public void update(User user) {

       jdbcTemplate .update( "update user set name=? where id=?" , new Object[] { user.getName(),

              user.getId() }, new int [] { java.sql.Types. VARCHAR , java.sql.Types. INTEGER });

    }

}

步驟三、把UserDaoImp 納入spring 容器管理。

在beans.xml 中增加對應的配置內容如下:

< bean id = "userDaoImp" class = "com.asm.dao.impl.UserDaoImp" >

    < property name = "datasouce" ref = "theDatasource" />

</ bean >
結合配置文件解析UserDaoImp 實現類:(1 )此類作爲一個bean 實例納入spring 容器管理,使用setter 注入方式完成對datasource 的注入,實質是完成的 JdbcTemplate 對象的初始化。(2 )該類CRUD 方法都使用了Spring 容器提供的 JdbcTemplate 對象來簡化了CRUD 操作,在spring 文檔的 11.2.1 . 節 對JdbcTemplate 類作了較詳細的介紹。(3 )此類打上了 @Transactional 註解,表示此類中的業務方法都會受beans.xml 配置的 < tx:annotation-driven transaction-manager = "txManager" /> 管理

步驟四、編寫測試類

package com.asm.test;

public class TestSJ {

    private UserDao ud = (UserDao) new ClassPathXmlApplicationContext( "beans.xml" ).getBean( "userDaoImp" );

    public static void main(String[] args) {

       TestSJ sj = new TestSJ();

       sj.save();

sj.delete();

    }

    public void save() {

       User user = new User();

       user.setName( " 張某某 " );

       ud .save(user);

    }

    public void delete() {

       User user = new User();

       user.setId(1);

       ud .delete(user);

    }

// 其它測試方法省略 ...   

}

說明 注意這裏通過 getBean 獲取的是 UserDao 接口對象,而非 UserDao 接口的實現類 UserDaoImp 對象,因爲 spring 的事務管理也是利用了 aop 技術,所以必須要面向接口,如果想通過 getBean 獲取它的實現類對象將會報錯。

步驟五、感知事務管理。

在UserDaoImp 的delete 方法中增加如下代碼:

jdbcTemplate .update( "delete from user where id=?" , new Object[] { user.getId() },   new int [] { java.sql.Types. INTEGER });

int i=5/0;

jdbcTemplate .update( "delete from user where id=2" );

spring默認的事務管理方式:運行期異常進行事務回滾,非運行期異常不進行事務回滾。因此增加上面的代碼後,會出現ArithmeticException運行期異常。所以當出現此異常時,delete方法中的數據庫操作都會進行回滾,因而id=1和2這兩條記錄都不會被刪除。 如果把UseDaoImp類前標記@Transactional的註解去掉,id=1的記錄會被刪除,因爲失去了spring容器的事務管理。
小結spring事務:
(1)在beans.xml中配置事務管理器,可以是依賴於數據源的事務管理器,也可以其它的事務管理器(比如和JPA集成的事務管理器等),這些事務管理器類都繼承自AbstractPlatformTransactionManager抽象類。(2)事務管理器配置後,要想讓配置的事務管理器對某些類的事務管理起作用,可以有兩種方式配置:一種是聲明式配置(兩種:基於註解或基於XML),一種是編程是配置。(3)上面的一些操作,都是基於註解的聲明式事務配置:關鍵兩點:開啓基於事務的註解支持(<tx:annotation-driven transaction-manager="txManager" />);被管理的類打上事務註解標記。(4)除了可以在類前聲明事務標記,也可以在類的方法中具體聲明詳細的事務標記。(5)事務標記具有多個可選屬性,具體可參文檔9.5.6.1。

3. 簡析事務註解屬性

@Transactional 註解具有大屬性:

(1 )傳播屬性 propagation

可選屬性

說明

REQUIRED

業務方法需要在一個事務中運行,如果方法運行時,已經處在一個事務中,那麼加入到該事務,否則自己創建一個新的事務。

REQUIRESNEW

不管是否存在事務,業務方法總會爲自己發起一個新的事務,如果方法已經運行在一個事務中,則原有事務會被掛起,新的事務會被創建,直到方法執行結束,新事務纔算結束,原先的事務才恢復執行。

SUPPORTS

如果業務方法在某個事務範圍內被調用,則業務方法成爲該事務的一部分。如果業務方法在事務範圍外被調用,則方法在沒有事務的環境下執行

NOT_SUPPORTED

聲明業務方法不需要事務,如果方法沒有關聯到一個事務,容器不會爲它開啓事務,如果方法在一個事務中被調用,則該事務會被掛起,在該方法調用結束後,原先的事務恢復執行

NEVER

指定業務方法絕對不能在事務範圍中執行,如果業務方法在某個事務中執行,容器會拋出例外;只有業務方法沒有關聯到任何事務,才能正常執行。

MANDATORY

指定業務方法只能在一個已經存在的事務中執行,業務方法不能發起自己的事務。如果業務方法在沒有事務的環境下調用,容器就會拋出例外

NESTED

如果一個活動的事務存在,它會自己產生一個單獨的而且擁有多個可以回滾的保存點的事務,然後嵌套運行在活動的事務中,實質它可以看成是一個內部事務且具有回滾保存點,所以內部自身的事務回滾並不會引起外部活動事務的回滾,它只是回滾到內部事務的保存點。 如果沒有活動事務,則按REQIIRED 屬性執行。需要注意的是此配置只對DataSourceTransactionManager 事務管理器生效。


說明:上面多次提到業務方法,它實質就是UserDaoImp 中save 等這樣的方法。但是這些方法前會有一個設定了詳細屬性的 @Transactional 註解。比如:
@Transactional (propagation= "" ,isolation= "" ,noRollbackFor= "" )
(2 )隔離級別屬性 isolation :這個是依賴數據庫系統而言,數據庫系統提供四種事務隔離級別。(具體的事務隔離級別分析在些略過,可以參看網絡資源及相關文檔)
(3 )只讀屬性 readOnly :false-- 讀寫性、true-- 只讀性事務.

(4 )超時屬性 timeout :int 型,以秒爲單位。

(5 )回滾屬性:根據拋出的異常決定是否回滾事務,它包括四種可選屬性。參文檔 9.5.6 .1 的表 9.3

4. 擴展:抽取dpcp 配置文件。

如果希望把dbcp 的配置單獨放到一個properties 配置文件中去,可以使用如下做法。操作步驟:在src 的路徑下建立dbcp.properties 文件,內容如下:
driverClassName= com.mysql.jdbc.Driver

url= jdbc : mysql://localhost:3306/sjdbc

username= root

password= 123456

initialSize= 2

maxActive= 100

maxIdle= 2

minIdle= 1
然後把此配置讀取數據源bean 中去,形式如下:
< bean id = "theDatasource"

    class = "org.apache.commons.dbcp.BasicDataSource" >

    < property name = "driverClassName" value = "${driverClassName}" />

    < property name = "url" value = "${url}" />

... 省略部分屬性的獲取,形式如下:

< property name = "xxx" value = "${dbcp.properties 配置文件中的鍵 }" />

...

</ bean >

注意的是使用${}來獲取值,必須把properties文件引入到spring的容器管理,因此要想成功通過${}來獲取相應的值,還應beans.xml中增加如下內容:

<context:property-placeholder location="classpath:dbcp.properties" />

而要想使用<context:XXX/>還應在beans.xml增加名稱空間:xmlns:context="http://www.springframework.org/schema/context"

和schema 信息:http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
關於這裏的名稱空間及schema 信息可參A2.8 節。

5. 基於XML 的事務管理

步驟一、拷貝UserDaoImp 爲UserDaoImp2 ,然後把內面所有關於事務的註解去掉

步驟二、建立beansXML.xml 文件,並配置事務管理器(和六.2. 步驟一 完全相同)。
步驟三、配置事務
< aop:config >

< aop:pointcut id = "txPointcut"

    expression = "execution(* com.asm.dao.impl.UserDaoImp2.*(..)) " />

        < aop:advisor advice-ref = "txAdvice" pointcut-ref = "txPointcut" />

</ aop:config >

< tx:advice id = "txAdvice" transaction-manager = "txManager" >

    < tx:attributes >

< tx:method name = "get*" read-only = "true" />
                                     
  --> 攔截 get 開頭的方法,並設置只讀屬性

       < tx:method name = "*" />          --> 攔截所有方法   

    </ tx:attributes >

</ tx:advice >

解析 :根據txManager 事務管理器來配置一個事務通知器:txAdvice 。而<tx:method> 中的name 屬性指定方法名(可以使用通配符),其它屬性就好比註解的傳播屬性配置(可參六.3(1) )。然後再使用切面配置一個切入點,再對這個切入點引入事務通知器。

步驟四、把UserDaoImp2 納入spring 容器管理。即在beansXML.xml 做如下配置:

< bean id = "userDaoImp2" class = "com.asm.dao.impl.UserDaoImp2" >

       < property name = "datasouce" ref = "theDatasource" />

</ bean >
步驟五、編寫寫測試類,測試事務管理器是否起作用,省略...

5. 總結與JDBC 的集成

(1 )首先是配置好一個事務管理器(2 )基於註解:一是涉及到事務的類打上事務註解標記,二是在xml 配置文件中開啓事務註解功能;基於配置:事務通知器加進apo 配置中。(3 )涉及到事務的類必須納入spring 容器管理,事務才能起作用。(4 )強調 :不論是基於註解還是基於xml 配置實現事務,都要依賴於動態代理技術。以UserDaoImp 說明,它實現了接口,所以它的代理對象實質是一個接口對象,因而通過getBean 獲取的UseDaoImp 實質上是一個接口對象,所以特別要注意類型轉換(參六.2. 步驟四. 說明 )。(5 )爲了簡化CRUD 操作,我們通常會使用spring 提供的JdbcTemplate 類。

七、 SSH 集成實例

1. 分析實例及準備環境

建立spring_06_SSH 項目 ,此項目主要演示struts1.x 、hibernate 、spring 的集成。集成前的一個比較關鍵的因素是搭建好環境,這一步要求我們對集成的框架所用到的jar 包有比較清楚的認識。下面列表展示了三個框架所用到的jar 包。
hibernate3.3 所用到的jar 包 :

antlr-2.7.6.jar                   開源語法分析生成器(lib/required)

commons-collections-3.1.jar       Commons 集合類庫,與連接池有關(lib/required)

dom4j-1.6.1.jar                   xml 解析類庫(lib/required)

javassist-3.9.0.GA.jar             分析,編輯和創建java 字節碼類庫(lib/required)

jta-1.1.jar                       事務處理api   (lib/required)       

slf4j-api-1.5.8.jar               日誌處理 (lib/required)--> 用log4j 實現

hibernate3.jar                    核心
ehcache-1.2.3.jar                  二級緩存(lib/optional/ehcache )

Spring2.5 安裝包所用的jar 包:
dist/spring.jar
lib/aspectj/aspectjweaver.jar
、aspectjrt.jar

lib/cglib/cgligb-nodep-2.1_3.jar
lib/j2ee/common-annotations.jar

lib/jakarta-commons/commons-logging.jar commons-dbcp.jar 、commons-pool.jar

dist/modules/spring-webmvc-struts.jar

lib/log4j/log4j-1.2.15.jar   供srping 與hibernate 使用的日誌記錄jar 包

lib/slf4j/ slf4j-log4j12-1.5.0.jar  日誌轉換jar 包,實現把log4j.jar 包適配到slf4j 標準。

struts1.10 所用到的jar

導入lib 目錄下的所有jar 包。但爲了避免jar 包衝突,不要導入antlr.jar (hibernate 已經導入)
jar 包說明 綠色字樣是框架所需的一些基本包hibernate 的開發jar 包主要集中在required 目錄和核心jar 包,還需要特別說明的是它的日誌包問題:hibernate 使用了slf4j 來記錄日誌,但是slf4j 只是一個日誌記錄標準,需要具體的實現纔可以進行日誌記錄,我們可以使用它本身的實現(如slf4j-simple-1.5.8.jar ),也可以使用其它的實現,如log4j 來記錄日誌,但是使用log4j 需要導入slf4j-log4j12-1.5.8.jar 來進行適配。在這裏爲了和spring 的日誌記錄結合,我們使用了log4j 的實現。而要使用log4j 日誌記錄,可以在spring 的lib 子目錄中找到。Spring 框架中除了常用的jar 包外,還增加了dbcp 連接池 相關的包,需要說明的是連接池的基本包爲:dbcp 、pool 、collections (在hibernate 中已導入)。爲了與strus 相結合,還增加了一個spring-webmvc-struts.jar 包。
經驗:spring 的作用是來集成其它的框架,所以對於許多集成所要用到的jar 包,在spring 中都能找到,因而對於項目中出現的類裝載相關的錯誤,應首先查看jar 包是否衝突,是否導入了需要的包,建議集成時,其它框架只需導入它們自己所需的基本包,而對於集成所用的包都可以從spring 提供的lib 中查詢。

2. 集成spring+hibernate

對於框架的集成最好的方式是分佈集成,比如這裏先集成spring+hibernate 並測試,測試通過後再來集成struts 。
步驟一、建立接口UserServeice ,代碼如下:

package com.asm.service;

public interface UserService {

    public abstract void save(User user);

    public abstract void delete(Integer id);

    public abstract void update(User user);

    public abstract User getUser(Integer id);

    @SuppressWarnings ( "unchecked" )

    public abstract List<User> getUsers();

}

步驟二、對應的實現類 UserServiceBean ,代碼如下:
package com.asm.service.impl;

public class UserServiceBean implements UserService {

    @Resource

    private SessionFactory sf ;

    @Transactional

    public void save(User user) {

       sf .getCurrentSession().persist(user);

    }

    ... 其它實現方法省略
}

注意事項 (1) 在每個方法上都要加上事務註解,特別是 save update 方法一定要加上事務註解,因爲把 sessionFactory 對象是通過 spring 注入 ( 註解方式注入 ) ,而 spring 中配置的 sessionFactory 對象又納入了 spring 容器的事務管理,所以要加上事務標記。 (2) 一定要通過 getCurrentSession 方法得到 session 對象,因爲使用 openSession 方式得到的 Session 對象不受 spring 容器管理。
實體類 User 及相應的 User.hbm.xml 配置在此省略。

步驟三、在 beans.xml 中進行配置,內容如下:
< bean id = "theDatasource"

       class = "org.apache.commons.dbcp.BasicDataSource" >

    < property name = "driverClassName" value = "com.mysql.jdbc.Driver" />

    < property name = "url" value = "jdbc:mysql://localhost:3306/ssh" />

    < property name = "username" value = "root" />

    < property name = "password" value = "123456" />

       ... 數據源配置,省略其它,可參看“與 jdbc 的集成”。

</ bean >

 

< bean id = "txManager"

class = "org.springframework.orm.hibernate3.HibernateTransactionManager" >

       < property name = "sessionFactory" ref = "sessionFactory" />

</ bean >

< tx:annotation-driven transaction-manager = "txManager" />

 

< bean id = "sessionFactory"

class = "org.springframework.orm.hibernate3.LocalSessionFactoryBean" >

       < property name = "dataSource" ref = "theDatasource" />

       < property name = "mappingResources" >

           < list >

              < value > com/asm/entity/User.hbm.xml </ value >

           </ list >

       </ property >

       < property name = "hibernateProperties" >

           < value >

              hibernate.dialect=org.hibernate.dialect.MySQLDialect

              hibernate.hbm2ddl.auto=update

               hibernate.show_sql=true

              hibernate.format_sql=true             

           </ value >

       </ property >

<!—

如果想就把此 bean 對應的屬性配置放到 hibernate.cfg.xml 中去配置,可以在 hibernate.cfg.xml 中配好後,再使用下面的配置加載 hibernate.cfg.xml 配置文件:

<property name="configLocation" value="hibernate.cfg.xml" />

       -->

    </ bean >

   

< bean id = "userServiceBean" class = "com.asm.service.impl.UserServiceBean" />

說明:這裏配置的 sessionFactory 實質就是對 hibernate sessionFactory 進行了一次包裝,它的配置依賴於一個數據源實例 bean ,最後再把 sessionFactory 納入 spring 容器的事務管理,但是要注意的是這時使用的是事務管理器類爲
HibernateTransactionManager
配置簡圖:

步驟四、建立 junit 單元進行測試,測試代碼如下:
package junit.test;

public class UserServiceTest {

    private static UserService us ;

    @BeforeClass

    public static void setUpBeforeClass() throws Exception {

       ApplicationContext ctx= new ClassPathXmlApplicationContext( "beans.xml" );

       us =(UserService) ctx.getBean( "userServiceBean" );

    }

    @Test

    public void testSave() {

       us .save( new User( " 李某某 " ));

    }

    ... 其它測試方法省略。

}
測試通過,完成了 MVC M 層業務邏輯層的開發。

3. 集成struts 框架

首先準備好struts 框架的基本配置環境:(1 )在web.xml 中配置struts 的核心控制器Actionservlet (配置代碼省略)。 (2 )在struts-config.xml 中建立strus-config.xml 配置文件,它的配置內容如下:
<? xml version = "1.0" encoding = "ISO-8859-1" ?>

<! DOCTYPE struts-config PUBLIC

         "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"

         "http://struts.apache.org/dtds/struts-config_1_3.dtd" >

< struts-config >

< action-mappings >

    < action path = "/list" type = "com.asm.action.UserAction" >

       < forward name = "success" path = "/WEB-INF/page/showUser.jsp" ></ forward >

    </ action >

</ action-mappings >

</ struts-config >

(3 )上面所用到的UserAction 的代碼如下:
package com.asm.action;

public class UserAction extends Action {

    @Override

    public ActionForward execute(ActionMapping mapping, ActionForm form,

           HttpServletRequest request, HttpServletResponse response) throws Exception {

         WebApplicationContext ctx = WebApplicationContextUtils

         .getWebApplicationContext ( this . servlet .getServletContext());

         UserService us = (UserService) ctx.getBean( "userServiceBean" );

         List<User> users = us.getUsers();

       request.setAttribute( "users" ,users);

       return mapping.findForward( "success" );

    }

}

說明:要想此實例真正可用,還需在web.xml 中增加一個監聽器的配置,內容如下:

< context-param >

    < param-name > contextConfigLocation </ param-name >

       < param-value > classpath:beans.xml </ param-value >

</ context-param >

< listener >

    < listener-class >

           org.springframework.web.context.ContextLoaderListener

    </ listener-class >

</ listener >

此監聽器的作用就是在啓動時把spring 容器進行實例化,並把spring 容器實例 WebApplicationContext 對象 放到application 作用域中,大致進行類似這樣的操作:servletContext.setAttribute( WebApplicationContext. ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE ,spring 容器實例WebApplicationContext 對象) 。因此我們也可以通過保存的名字來獲取 WebApplicationContext 對象,但是爲了方便, spring 提供一個工具類 WebApplicationContextUtils 來獲取此對象,關於這個工具類的使用,在 UserAction 就是使用了此工具類來獲取 spring 容器實例。保存一個 List<User> 對象後,在 showUser.jsp 遍歷 List 對象,代碼如下:

< c:forEach items = "${users} "var = "user" >

       ${user.id } -- ${user.name } < br />

</ c:forEach >

4. 改進集成struts 框架

上面的代碼其實並非整合struts 的最佳方式,整合struts 的優雅方式應該是把Action 也納入spring 容器管理。具體操作如下:

步驟一 、修改UserAction 類,修改後的代碼如下:

@Resource

private UserService userService ;

@Override

public ActionForward execute(ActionMapping mapping, ActionForm form,

    HttpServletRequest request, HttpServletResponse response) throws Exception {

    request.setAttribute( "users" , userService .getUsers());

    return mapping.findForward( "success" );

}

說明 :上面的userService 屬性通過spring 的註解方式注入,這樣可以避免獲取UserService 對象的繁瑣過程。
步驟二 、在beans.xml 配置UserAction ,納入spring 容器管理,配置內容如下:

< bean name = "/list" class = "com.asm.action.UserAction" />

說明 :此實例 bean 的配置使用了 name 屬性,而未使用 id 屬性,因爲 id 不支持“ / ”這樣的特殊字符。還需要說明的是這裏的 name 之所以取值爲“ /list ”,是因爲納入 spring 容器管理的 Action 的取名必須保證與 action 的 path 屬性值相同(爲什麼如此做,後面有解釋)。
步驟三 、添加一個 ActionServet 的處理器類。 Struts1.x 中的 ActionServlet 最終其實是把請求派發給 RequestProcessor 類來處理,而如果想自己編寫處理請求類,可以在 struts-config.xml 中配置一個 <controller> 來處理這些請求。 Spring 提供了一個 DelegatingRequestProcessor 類,它繼承自 RequestProcessor 類,它除了具備父類的功能,還主要完成一件重要的工作:把 ActionServlet 派發過來的請求交給在 spring 中配置的 Action bean 實例來處理(由於最終它是把請求交給了 spring 中配置的 action bean 實例處理,所以在 struts-config.xml 中關於此 action 的 type 屬性可以不寫)。因此我們還應 struts-config.xml 的中增加如下配置代碼:
< controller >

< set-property property = "processorClass"

value = "org.springframework.web.struts.DelegatingRequestProcessor" />

</ controller >

關於這個處理器也可如下配置:

< controller processorClass =
"org.springframework.web.struts.DelegatingRequestProcessor" />

說明了爲什麼要在 beans.xml 配置的 UserAction 的名字爲“ /list ”

 

5. 使用二級緩存

步驟一、在使用hibernate 框架時,可以爲hibenate 配置一個第三方緩存。通常的做法是在hibernate 的主配置文件中開啓緩存相關的功能。在此項目中,由於是集成了spring 框架,所以我們可以在spring 容器的beans.xml 中的sessionFactory bean 實例的 hibernateProperties 屬性中增加如下配置:
cache.use_second_level_cache=true         --> 開啓二級緩存

hibernate.cache.use_query_cache=false    --> 不使用查詢緩存

hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider

                                         --> 第三方緩存實現類           

步驟二、第三方緩存EhCache 的配置,在src 目錄下編寫ehcache.xml 配置文件,內容如下:

<? xml version = "1.0" encoding = "UTF-8" ?>

<!--

    defaultCache 節點爲缺省的緩存策略

     maxElementsInMemory 內存中最大允許存在的對象數量

     eternal 設置緩存中的對象是否永遠不過期

     overflowToDisk 把溢出的對象存放到硬盤上

     timeToIdleSeconds 指定緩存對象空閒多長時間就過期 , 過期的對象會被清除掉

     timeToLiveSeconds 指定緩存對象總的存活時間

     diskPersistent jvm 結束是是否持久化對象

     diskExpiryThreadIntervalSeconds 指定專門用於清除過期對象的監聽線程的輪詢時間

  -->

< ehcache >

    < diskStore path = "C:/cache" />

    < defaultCache  maxElementsInMemory = "1000" eternal = "false" overflowToDisk = "true"

        timeToIdleSeconds = "120"

        timeToLiveSeconds = "180"

        diskPersistent = "false"

        diskExpiryThreadIntervalSeconds = "60" />

    < cache name = "com.asm.entity.User" maxElementsInMemory = "100" eternal = "false"

    overflowToDisk = "true" timeToIdleSeconds = "300" timeToLiveSeconds = "600" diskPersistent = "false" />

</ ehcache >

步驟三、在User.htm.xml 中的<class> 目錄下增加如下配置:

< cache usage = "read-write" region = "com.asm.entity.User" />

說明 :這裏的region 的值和ehcache.xml 配置下的<cache> 中的name 屬性值相同。結合echcache.xml 作如下說明: 通常情況下,實體類的二級緩存配置通常使用默認的<defaultCache> 這個配置,但是如果想使用特有的緩存配置,可以用實體類的<cacche  regin> 來和ehcache.xml 中的<cache name> 關聯。

步驟四、測試二級緩存。在 UserServiceTest 中增加如下測試代碼:

    public void testGetUser() {

       System. out .println( us .getUser(1).getName());

       System. out .println( " 關閉數據庫服務 " );

       try {

           Thread.sleep (1000*40);

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

       System. out .println( " 關閉數據庫準備從緩存中獲取 " );

       System. out .println( us .getUser(1).getName());

    }

說明:當執行到控制檯顯示“關閉數據庫服務”時,我們手工停掉數據庫服務,休眠 40 秒後,仍能獲取數據,證明是從緩存中取得的數據。

小結使用緩存步驟 :開啓支持緩存的配置、對第三方緩存類進行配置、在實體類配置文件中標記使用二級緩存。

6. 亂碼問題

當前臺傳遞的數據包含中文時,獲取這些中文數據時會出現亂碼。大致原因 如下:在struts 框架中,我們把配置的*.do 的請求會被ActionServlet 攔截到,而訪問ActionServlet 時用到的默認編碼爲ISO8859-1 , 即是說設置了 request.setCharacterEncoding (“ ISO8859-1 ”) 。這時ActionServlet 在獲取參數時就會得到亂碼,ActionServlet 填充ActionForm 時就把亂碼填充進ActionForm 。當填充完畢後會把*.do 的請求派發給相應的Action, 而Action 從ActionForm 中獲取數據時自然就出現了亂碼。解決此問題很簡單,只需把spring 提供的一個過濾器類配置到web.xml 中,即增加如下代碼:

< filter >

    < filter-name > encodingFilter </ filter-name >

    < filter-class >

       org.springframework.web.filter.CharacterEncodingFilter 

    </ filter-class >

    < init-param >

       < param-name > encoding </ param-name >

       < param-value > UTF-8 </ param-value >

    </ init-param >

</ filter >

< filter-mapping >

    < filter-name > encodingFilter </ filter-name >

    < url-pattern > /* </ url-pattern >

</ filter-mapping >

在以前我們是自己編寫此filter 類,而spring 爲我們提供了CharacterEncodingFilter 類,我們就自需配置即可。另要注意在配置時一定要指定一個encoding 參數名及值. 補充 :關於這裏的亂碼問題可參“struts1.x 深入學習筆記 - 四、2. 步驟五. 問題 ”。

7.OSIV

Spring 對於OpenSessionInView 也提供一個filter 類來解決此問題:

org.springframework.orm.hibernate3.support.OpenSessionInViewFilter

 

八、 SSJ 集成實例

1. 搭建環境

hibernate 核心包

/lib/bytecode/cglib/Hibernate-cglib-repack.jar

hibernate 註解包

hibernate-annotations.jar

Lib/Ejb3-persistence.jar 、hibernate-commons-annotations.jar

Hibernate 針對JPA 的實現包

Hibernate-entitymanager.jar

上面列舉的jar 包(基於hibernate 實現的JPA 包)+SSH 集成時的所有jar 包

2. 集成spring+JPA

建立spring_07_SSJ 項目 並導入相關ja 包,此項目基本參照上一個項目,只是把JPA 的實現加了進去。

步驟一、搭建JPA 開發環境

在src 下建立META-INF/persistence.xml 文件,此文件的主要內容如下:
< persistence xmlns = "http://java.sun.com/xml/ns/persistence"

    xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"

    xsi:schemaLocation = "http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"

    version = "1.0" >

    < persistence-unit name = "jpaDemo"

       transaction-type = "RESOURCE_LOCAL" >

       < provider > org.hibernate.ejb.HibernatePersistence </ provider >

       < properties >

           < property name = "hibernate.dialect"

              value = "org.hibernate.dialect.MySQL5Dialect" />

           < property name = "hibernate.hbm2ddl.auto" value = "update" />

           < property name = "hibernate.show_sql" value = "true" />

           < property name = "hibernate.format_sql" value = "true" />

           <!-- 配置要連接的數據庫信息: -->

       < property name = "hibernate.connection.driver_class"

              value = "com.mysql.jdbc.Driver" />

        < property name = "hibernate.connection.url"

              value = "jdbc:mysql://localhost:3306/ssj" />

       < property name = "hibernate.connection.username" value = "root" />

< property name = "hibernate.connection.password" value = "123456" />

    </ properties >

    </ persistence-unit >

</ persistence >

對應的持久化 User 的代碼如下:

package com.asm.entity;

@Entity

public class User {

    @Id

    @GeneratedValue

    private Integer id ;

    @Column (name= "u_name" )

    private String name ;

 

    public User() {

    }

 

    public User(String name) {

       this . name = name;

    }

    ... 省略 get/set 方法,特別注意要在實體在打上實體相關的標記: @Entity     @Id

    @GeneratedValue @Column

}

步驟二、把JPA 納入spring 容器管理
在beans.xml 中的配置如下:

< context:annotation-config />

< bean id = "localEMF"

class = "org.springframework.orm.jpa.LocalEntityManagerFactoryBean" >

    < property name = "persistenceUnitName" value = "jpaDemo" />

</ bean >

< bean id = "txManager"

    class = "org.springframework.orm.jpa.JpaTransactionManager" >

    < property name = "entityManagerFactory" ref = "localEMF" />

</ bean >

< tx:annotation-driven transaction-manager = "txManager" />

說明:localEMF 實例bean 的使用是把JPA 的persistence.xml 配置的jpaDemo 持久化單元加進JPA 實體工廠管理( LocalEntityManagerFactoryBean 包裝了JPA 所使用的 EntityManager 對象的工廠類 EntityManagerFactoryBean )。再把工廠類加進事務管理器。

步驟三、UserServiceBean 的代碼如下:

package com.asm.service.impl;

public class UserServiceBean implements UserService {

    @PersistenceContext

    private EntityManager em ;

    @Transactional

    public void save(User user) {

       em .persist(user);

    }

    ... 省略其它的實現方法

}

說明: em 屬性依賴於 spring 容器注入。只要把此類交給 spring 容器管理, spring 容器會根據 @PersistenceContext 註解,來用配置的產生 EntityManager 對象的 JPA 工廠類來完成此對象的注入。 強調 的是在 save update 方法上一定要打上事務註解標記。

步驟四、測試UserServiceBean 的業務方法

測試代碼同上一個項目的測試代碼

3. 集成struts :參上一個項目

4.OSIV 問題

在集成hibernate 時,OSIV 指的是sesion 關閉後的的解決方案。而在使用JPA 時,取代session 的是 EntityManager , 所以要解決因 EntityManager 關閉的OSIV 問題,應使用spring 提供的 org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter

 

九、SSH2 集成實例

1. 搭建環境並集成hibenate

基本環境SSH 集成相同,只是要導入開發struts2 的開發包(在2.1.8 需要導入6 個基本包), 與hibenate 的集成可以前面的項目說明。

2. 集成strust2

步驟一、建立UserAction ,代碼如下:

package com.asm.action;

public class UserAction extends ActionSupport {

    @Resource

    private UserService userService ;

    private String username ;

    @Override

    public String execute() throws Exception {

       User user= new User( username );

       userService .save(user);

       return "success" ;

    }

... 省略 username 的 get/set 方法

}

說明: userService 屬性通過 spring 注入。在 bean.xml 配置 UserAction 如下 :

< bean id = "userAction" class = "com.asm.action.UserAction" />

配置此action ,在src 建立struts.xml 文件,配置如下:

<? xml version = "1.0" encoding = "UTF-8" ?>

<! DOCTYPE struts PUBLIC

    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

    "http://struts.apache.org/dtds/struts-2.0.dtd" >

< struts >   

    < constant name = "struts.devMode" value = "true" />

    < package name = "baseSSH" namespace = "/" extends = "struts-default" >

       < action name = "add" class = "com.asm.action.UserAction" >

           < result name = "success" > /success.jsp </ result >

       </ action >

    </ package >

</ struts >

步驟二、配置一個監聽器,實例化spring 容器,和集成struts1 相同。即在web.xml 中增加如下代碼:

< context-param >

    < param-name > contextConfigLocation </ param-name >

    < param-value > classpath:beans.xml </ param-value >

</ context-param >

< listener >

    < listener-class >

       org.springframework.web.context.ContextLoaderListener

    </ listener-class >

</ listener >

步驟三、前臺index.jsp 頁面,代碼如下:

< form action = "<%= request.getContextPath() %> /add.action ">

    用戶名: < input type = "text" name = "username" >

    < input type = "submit" value = " 提交 " >    

</ form >

步驟四、發佈測試。

十、整合總結:

 

十一、錯誤總結:

1. 使用new ClassPathXmlApplicationContext 對象時未傳遞 beans.xml 配置文件出錯: BeanFactory not initialized or already closed

另外如果 ctx 對象關閉後,再刷新也可以重新使用 ctx 對象來獲取 bean 實例

OpenSession 得到的session 不受Spring 容器管理

JPA 實體一定要有entity 註解標誌— 涉及到保存或更新操作時,一定要打上事務標記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章