Spring學習筆記

Spring學習筆記html

Java相關課程系列筆記之十五前端

筆記內容說明java

Spring(梁建全老師主講,佔筆記內容100%);node

目  錄web

1、 Spring概述 1spring

1.1 Spring框架的做用 1sql

1.2 Spring框架的優勢 1數據庫

1.3 Spring框架的容器 1express

2、 Spring容器的基本應用 2apache

2.1如何將一個Bean組件交給Spring容器 2

2.2如何獲取Spring容器對象和Bean對象 2

2.3如何控制對象建立的模式 2

2.4 Bean對象建立的時機 2

2.5爲Bean對象執行初始化和銷燬方法 2

2.6案例:Spring框架的使用以及2.1節-2.5節整合測試 3

3、 Spring框架IoC特性 5

3.1 IoC概念 5

3.2 DI概念 5

3.3案例:測試IoC(set注入) 5

3.4案例:測試IoC(構造注入) 6

3.5案例:不用JDBC訪問數據庫,而是採用Hibernate訪問 6

4、 Spring中各類類型的數據注入 7

4.1 Bean對象注入 7

4.2基本數據的注入 7

4.3集合的注入 7

4.4案例:各種數據注入 7

5、 AOP概念 10

5.1什麼是AOP 10

5.2 AOP和OOP的區別 10

5.3 AOP相關術語 10

5.4案例:AOP的使用,模擬某些組件須要記錄日誌的功能 11

5.5通知類型 11

5.6切入點 12

5.7案例:環繞通知,修改5.4案例使之動態顯示所執行的操做 12

5.8案例:利用AOP實現異常處理,將異常信息寫入文件 13

6、 Log4j日誌記錄工具 14

6.1 Log4j介紹 14

6.2 Log4j的使用 14

6.3案例:修改5.8案例,使用Log4j記錄日誌 14

7、 Spring註解配置 16

7.1組件掃描功能 16

7.2組件掃描的使用方法 16

7.3注入註解標記使用方法 17

7.4 AOP註解標記使用方法 17

8、 Spring對數據訪問技術的支持 19

8.1 Spring提供了統一的異常處理類型 19

8.2 Spring提供了編寫DAO的支持類 19

8.3 Spring提供了聲明式事務管理方法 19

8.4 Spring框架如何使用JDBC技術 19

8.5鏈接池優勢 22

8.6 Spring框架如何使用Hibernate技術 22

8.7 Spring+Hibernate如何使用Session、Query等對象 25

8.8 Spring框架和Struts2整合應用 25

8.9案例:採用SSH結構重構資費管理模塊 27

9、 整合開發包struts-spring-plugin.jar 31

9.1 Struts2建立對象的方式 31

9.2 struts-spring-pligin.jar建立對象的方式 31

9.3 struts-spring-plugin.jar的內部實現 31

9.4原理圖1 31

9.5原理圖2 32

9.6注意事項 32

9.7注入規則 32

10、 Spring的事務管理 33

10.1聲明式事務管理(基於配置方式實現事務控制) 33

10.2編程式事務管理(基於Java編程實現事務控制),不推薦用! 34

10.3 Spring中經常使用的事務類型 34

11、 Spring的MVC 35

11.1 Spring MVC的體系結構 35

11.2 Spring MVC的工做流程 35

11.3案例:簡易登陸(基於XML配置,不推薦使用) 35

11.4案例:修改11.3案例(基於註解配置,推薦使用) 37

12、 其餘注意事項 39

12.1 Spring的核心模塊 39

12.2表單中action屬性的相對、絕對路徑問題 39

12.3用SSH重構NetCTOSS項目模塊的步驟 39

1、Spring概述

咱們學習Spring框架的最終目的是用它整合Struts二、Hibernate框架(SSH)。

1.1 Spring框架的做用

Spring框架主要負責技術整合(能夠整合不少技術),該框架提供IoC和AOP機制,基於這些特性整合,能夠下降系統組件之間的耦合度,便於系統組件的維護、擴展和替換。

1.2 Spring框架的優勢

其實與Spring框架的做用相同:

在SSH中,主要是利用Spring容器管理咱們程序中的Action、DAO等組件,經過容器的IoC機制,能夠下降Action、DAO之間的耦合度(關聯度),利用AOP進行事務管理等共通部分的處理。

在SSH中,Struts2主要是利用它的控制器,而不是標籤、表達式;Hibernate主要利用它的數據庫訪問;Spring主要是利用它的整合。

1.3 Spring框架的容器

Spring框架的核心是提供了一個容器(是咱們抽象出來的,代指後面的類型)。該容器類型是BeanFactory或ApplicationContext(建議用這個類型,它是BeanFactory的子類,功能更多)。

該容器具備如下功能:

1)容器能夠建立和銷燬組件對象,等價於原來「工廠」類的做用。

2)容器能夠採用不一樣的模式建立對象,如單例模式建立對象。

3)容器具備IoC機制實現。

4)容器具備AOP機制實現。

2、
Spring容器的基本應用

2.1如何將一個Bean組件交給Spring容器

1)Bean組件其實就是個普通的Java類!

2)方法:在applicationContext.xml中添加如下定義,見2.6案例中step4。

<bean id="標識符" class="Bean組件類型"></bean>

2.2如何獲取Spring容器對象和Bean對象

1)實例化容器:

ApplicationContext ac=new ClassPathXmlApplicationContext("/applicationContext.xml");

//FileSystemXmlApplicationContext("");//去指定的磁盤目錄找,上面的爲去Class路徑找

2)利用getBean("標識符")方法獲取容器中的Bean對象。見2.6案例中step5。

2.3如何控制對象建立的模式

Spring支持singleton(單例)和prototype(原型,非單例)兩種模式。

默認是singleton模式,能夠經過<bean>的scope屬性修改成prototype模式。之後在Web程序中,經過擴展可使用request、session等值。見2.6案例中step四、step7。

例如:<bean id="標識符" scope="prototype" class="Bean組件類型"></bean>

u 注意事項:對於NetCTOSS項目,一個請求建立一個Action,因此用Spring時必須指明prototype,不然默認使用singleton會出問題。而DAO則可用singleton模式。

2.4 Bean對象建立的時機

1)singleton模式的Bean組件是在容器實例化時建立。

2)prototype模式是在調用getBean()方法時建立。

3)singleton模式可使用<bean>元素的lazy-init="true"屬性將對象的建立時機推遲到調用getBean()方法。也能夠在<beans>(根元素)中使用default-lazy-init="false"推遲全部單例Bean組件的建立時機。見2.6案例中step三、step4。

例如:<bean id="標識符" scope="singleton" lazy-init="true" class="Bean組件類型"></bean>

<beans ...... default-lazy-init="false"></beans>

2.5爲Bean對象執行初始化和銷燬方法

1)初始化:①能夠利用<bean>元素的init-method="方法名"屬性指定初始化方法。

②指定的初始化方法是在構造方法調用後自動執行。若非單例模式,則每建立一個對象,則執行一次初始化方法(單例、非單例模式均可)。見2.6案例中step三、step4。

u 注意事項:

v 初始化的三種方式:寫構造方法中;或寫{ }中(代碼塊);Spring框架中<bean>元素寫init-method="方法名"屬性。

v 初始化不能用static{ },它是類加載調用,比建立對象要早。

2)銷燬:①能夠利用<bean>元素的destroy-method="方法名"屬性執行銷燬方法。

②指定的銷燬方法是在容器關閉時觸發,並且只適用於singleton模式的組件(只能爲單例模式)。見2.6案例中step三、step四、step6。

2.6案例:Spring框架的使用以及2.1節-2.5節整合測試

step1:導入Spring開發包:spring.jar、commons-logging.jar和配置文件:applicationContext.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:tx="http://www.springframework.org/schema/tx"

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

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

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

xsi:schemaLocation="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/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/jee   http://www.springframework.org/schema/jee/spring-jee-2.5.xsd">

</beans>

step2:在org.tarena.dao包下,建立接口CostDAO,添加兩個方法

public void delete(); public void save();

step3:在org.tarena.dao包下,建立JdbcCostDAO類,並實現CostDAO接口

public JdbcCostDAO(){ System.out.println("建立CostDAO對象"); }

public void myinit(){ System.out.println("初始化CostDAO對象"); }

public void mydestroy(){ System.out.println("銷燬CostDAO對象"); }

public void delete() { System.out.println("利用JDBC技術實現刪除資費記錄"); }

public void save() { System.out.println("利用JDBC技術實現保存資費記錄"); }

step4:在applicationContext.xml配置文件中,將Bean組件(Java類)交給Spring容器

<bean id="jdbcCostDAO" scope="singleton" lazy-init="true" init-method="myinit"

destroy-method="mydestroy" class="org.tarena.dao.JdbcCostDAO">

</bean>

step5:在org.tarena.test包下,建立TestApplicationContext類,獲取Spring容器對象,並測試

@Test

public void test1(){ String conf="/applicationContext.xml";

ApplicationContext ac=new ClassPathXmlApplicationContext(conf);//實例化容器

CostDAO costDAO=(CostDAO)ac.getBean("jdbcCostDAO");//獲取Bean對象

costDAO.save(); costDAO.delete(); }

step6:在TestApplicationContext類中添加方法,測試銷燬對象

@Test

/**關閉容器纔會觸發銷燬,但關閉容器方法封裝在AbstractApplicationContext類中 */

public void test2(){ String conf="/applicationContext.xml";

ApplicationContext ac=new ClassPathXmlApplicationContext(conf);

AbstractApplicationContext ac=new ClassPathXmlApplicationContext(conf);

CostDAO costDAO=(CostDAO)ac.getBean("jdbcCostDAO"); ac.close(); }

step7:在TestApplicationContext類中添加方法,測試單例

@Test

public void test3(){ String conf="/applicationContext.xml";

ApplicationContext ac= new ClassPathXmlApplicationContext(conf);

CostDAO costDAO1=(CostDAO)ac.getBean("jdbcCostDAO");

CostDAO costDAO2=(CostDAO)ac.getBean("jdbcCostDAO");

System.out.println(costDAO1==costDAO2);//true,因此Spring默認爲單例模式 }

3、
Spring框架IoC特性

3.1 IoC概念

1)Inverse of Controller被稱爲控制反轉或反向控制,其實真正體現的是「控制轉移」。

2)所謂的控制指的是負責對象關係的指定、對象建立、初始化和銷燬等邏輯。

3)IoC指的是將控制邏輯交給第三方框架或容器負責(即把Action中的控制邏輯提出來,交給第三方負責),當兩個組件關係發生改變時,只須要修改框架或容器的配置便可。

4)IoC主要解決的是兩個組件對象調用問題,能夠以低耦合方式創建使用關係。

3.2 DI概念

1)Dependency Injection依賴注入。

2)Spring框架採用DI技術實現了IoC控制思想。

3)Spring提供了兩種形式的注入方法:

①setter方式注入(經常使用):依靠set方法,將組件對象傳入(可注入多個對象)。

A.首先添加屬性變量和set方法。

B.在該組件的<bean>定義中採用下面的描述方式:

  <property name="屬性名" ref="要注入的Bean對象的id值"></property>

u 注意事項:例如CostAction中有costDAO屬性,而它的標準set方法名爲setCostDAO,那麼配置文件中的name就應該寫costDAO(去掉set,首字母小寫)。若是set方法名爲setCost,那麼name就應該寫cost(去掉set,首字母小寫)!確切的說,name不是看定義的屬性名,而是set方法名。

②構造方式注入(用的少):依靠構造方法,將組件對象傳入。

A.在須要注入的組件中,添加帶參數的構造方法。

B.在該組件的<bean>定義中,使用下面格式描述:

  <constructor-arg index="參數索引" ref="要注入的Bean對象的id值"></constructor-arg>

3.3案例:測試IoC(set注入)

step1:接2.6案例,在org.tarena.action包下,建立CostAction類,調用save方法

private CostDAO costDAO;//利用Spring的IOC機制使用CostDAO組件對象,set注入

public void setCostDAO(CostDAO costDAO) {   this.costDAO = costDAO;  }

public String execute(){ System.out.println("處理資費添加操做");

costDAO.save();//調用CostDAO中的save方法 return "success"; }

step2:在applicationContext.xml配置文件中,將CostAction組件交給Spring容器

<bean id="costAction" scope="prototype" class="org.tarena.action.CostAction">

<!-- 利用setCostDAO方法接收jdbcCostDAO對象 -->

<property name="costDAO" ref="jdbcCostDAO"></property>

<!-- name:與CostAction中對應的set方法匹配的名。ref:指明哪一個對象 -->

</bean><!--此處用到了2.6案例中step3描述的組件JdbcCostDAo-->

step3:在org.tarena.test包下,建立TestIoc類,用於測試IOC機制

@Test //測試set注入

public void test1(){ String conf="/applicationContext.xml";

ApplicationContext ac=new ClassPathXmlApplicationContext(conf);

CostAction action=(CostAction)ac.getBean("costAction");//得到CostAction類的對象

action.execute(); }

step4:測試結果爲:

建立CostDAO對象 初始化CostDAO對象    處理資費添加操做

利用JDBC技術實現保存資費記錄。

3.4案例:測試IoC(構造注入)

step1:接3.3案例,在org.tarena.action包下,建立DeleteAction類,調用delete方法

private CostDAO costDAO;

public DeleteAction(CostDAO costDAO){ this.costDAO=costDAO;  }//構造注入

public String execute() {   System.out.println("處理資費刪除操做");

costDAO.delete();//調用CostDAO中的delete方法   return "success"; }

step2:在applicationContext.xml配置文件中,將DeleteAction組件交給Spring容器

<bean id="deleteAction" scope="prototype" class="org.tarena.action.DeleteAction">

<!-- 索引0:給構造方法中第一個參數注入一個jdbcCostDAO對象。

  若多個參數則重複追加constructor-arg元素便可 -->

<constructor-arg index="0" ref="jdbcCostDAO"></constructor-arg><!-- 構造注入 -->

</bean>

step3:在TestIoc類中添加方法,測試構造注入

@Test //測試構造注入

public void test2(){ String conf="/applicationContext.xml";

ApplicationContext ac=new ClassPathXmlApplicationContext(conf);

DeleteAction action=(DeleteAction)ac.getBean("deleteAction");

action.execute(); }

step4:測試結果爲:

建立CostDAO對象 初始化CostDAO對象    處理資費刪除操做

利用JDBC技術實現刪除資費記錄。

3.5案例:不用JDBC訪問數據庫,而是採用Hibernate訪問

接3.3案例,若是不用JDBC訪問數據庫,而是採用Hibernate訪問,則替換組件過程爲:

step1:建立HibernateCostDAO類,並實現CostDAO接口

public void delete() { System.out.println("利用Hibernate技術實現刪除資費記錄"); }

public void save() {  System.out.println("利用Hibernate技術實現保存資費記錄"); }

step2:在applicationContext.xml配置文件中,將HibernateCostDAO組件交給Spring容器

<bean id="hibernateCostDAO" class="org.tarena.dao.HibernateCostDAO"></bean>

step3:修改3.3案例中step2中CostAction組件的描述

<bean id="costAction" scope="prototype" class="org.tarena.action.CostAction">

<!-- 修改ref屬性的指引 -->

<property name="costDAO" ref="hibernateCostDAO"></property>

<!-- name:與CostAction中添加的屬性名相同。ref:指明哪一個對象 -->

</bean>

step4:再次執行3.3案例step3中test1方法,測試結果爲:

處理資費添加操做    利用Hibernate技術實現保存資費記錄

4、
Spring中各類類型的數據注入

Spring能夠爲對象注入如下類型的數據。

4.1 Bean對象注入

<property name="屬性名" ref="要注入的Bean對象的id值"></property>

4.2基本數據的注入

1)字符串、數字

<property name="屬性名" value="要注入的值"></property>

4.3集合的注入

1)List、Set

<property name="集合屬性名">

<list><!-- 普通值用<value>標籤,對象用<bean>標籤 -->

<value>集合中的值1</value><value>集合中的值2</value> …………

</list>

</property>

2)Map

<property name="集合屬性名">

<map><!-- 普通值用<value>標籤,對象用<bean>標籤 -->

<entry key="鍵1" value="值1"></entry>

<entry key="鍵2" value="值2"></entry>  …………

</map>

</property>

3)Properties

<property name="集合屬性名">

<props>

<prop key="鍵1">值1</prop>

<prop key="鍵2">值2</prop> …………

</props>

</property>

4)特殊用法:set方法接收字符串,內部進行處理(如分割),再存入集合屬性

<property name="集合屬性名" value="字符串"></property>

4.4案例:各種數據注入

step1:對象注入參考3.三、3.4案例

step2:建立MessageBean類,並定義不一樣類型的數據以及對應的set方法

private String username;//用戶名 private String fileDir;//上傳路徑

private List<String> hbms; private Set<String> cities;

private Map<Integer, String> books; private Properties props;

private Set<String> types;//容許上傳類型

/** 注意set方法的名字!不是看屬性名,而是看set方法名去掉set,首字母大寫的名 */

public void setName(String username) { this.username = username; }//手動更改過名字

public void setDir(String fileDir) { this.fileDir = fileDir; }//手動更改過名字

……其餘屬性名字沒改,其餘屬性代碼略

public void setTypes(String str) {//特殊用法:注入一個字符串,分析以後給set集合賦值

String[] arr=str.split(","); types=new HashSet<String>();

for(String s:arr){ types.add(s); } }

public void show(){

System.out.println("用戶名:"+username); System.out.println("上傳路徑:"+fileDir);

System.out.println("--hbms文件以下--");

for(String s:hbms){ System.out.println(s); }

System.out.println("--city城市以下--");

for(String c:cities){ System.out.println(c); }

System.out.println("--book圖書信息--");

Set<Entry<Integer,String>> ens=books.entrySet();

for(Entry en:ens){  System.out.println(en.getKey()+" "+en.getValue());  }

System.out.println("--props參數以下--");

Set keys=props.keySet();//另外一種方式遍歷

for(Object key:keys){ System.out.println(key+" "+

props.getProperty(key.toString())); }

System.out.println("--容許上傳類型以下--");//特殊用法

for(String type:types){   System.out.println(type); } }

step3:applicationContext.xml配置文件中

<!-- 各類數據類型的注入 -->

<bean id="messageBean" class="org.tarena.dao.MessageBean">

<!-- 注意名字 name指的是set方法名去掉set,首字母大寫的名,不看屬性名! -->

<property name="name" value="root"></property><!--手動更改過set方法名 -->

<property name="dir" value="D:\\images"></property><!--手動更改過set方法名 -->

<property name="hbms">

<list><value>/org/tarena/entity/Cost.hbm.xml</value>

<value>/org/tarena/entity/Admin.hbm.xml</value></list></property>

<property name="cities">

<set><value>北京</value><value>上海</value></set></property>

<property name="books">

<map><entry key="1" value="Java語言基礎"></entry>

  <entry key="2" value="Java Web入門"></entry></map></property>

<property name="props">

<props><prop key="hibernate.show_sql">true</prop>

<prop key="hibernate.dialect_sql">org.hibernate.dialect.OracleDialect</prop>

</props>

</property>

<!-- 特殊用法,set方法傳入字符串,內部進行處理,再存入集合 -->

<property name="types" value="jpg,gif,jpeg"></property>

</bean>

step4:建立TestInjection類用於測試各種數據的注入

@Test

public void test1(){

String conf="/applicationContext.xml";

ApplicationContext ac=new ClassPathXmlApplicationContext(conf);

MessageBean bean=(MessageBean)ac.getBean("messageBean");

bean.show(); }

5、
AOP概念

5.1什麼是AOP

Aspect Oriented Programming,被稱爲面向方面編程。對單個對象(一對一)的解耦用IOC,而當有個共通組件,它對應多個其餘組件(一對多),則解耦用AOP。如,攔截器。這也是爲什麼在程序中大量的用IoC,而AOP卻用的不多,由於程序中不可能有不少的共通部分。

5.2 AOP和OOP的區別

OOP是面向對象編程,AOP是以OOP爲基礎的。

OOP主要關注的是對象,如何抽象和封裝對象。

AOP主要關注的是方面,方面組件能夠以低耦合的方式切入到(做用到)其餘某一批目標對象方法中(相似於Struts2中的攔截器)。

AOP主要解決共通處理和目標組件之間解耦。

5.3 AOP相關術語

1)方面(Aspect):指的是封裝了共通處理的功能組件。該組件能夠做用到某一批目標組件的方法上。
2)鏈接點(JoinPoint):指的是方面組件和具體的哪個目標組件的方法有關係。

3)切入點(Pointcut):用於指定目標組件的表達式。指的是方面組件和哪一批目標組件方法有關係。多個鏈接點組成的集合就是切入點。如:a、b爲切入點,一、2爲鏈接點。

wps_clip_image-23792

wps_clip_image-19666

wps_clip_image-26213

wps_clip_image-25910

4)通知(Advice):用於指定方面組件和目標組件方法之間的做用時機。例如:先執行方面組件再執行目標方法;或先執行目標方法再執行方面組件。

5)目標(Target):利用切入點指定的組件和方法。

6)動態代理(AutoProxy):Spring一樣採用了動態代理技術實現了AOP機制。當使用AOP以後,從容器getBean()獲取的目標組件,返回的是一個動態生成的代理類。而後經過代理類執行業務方法,代理類負責調用方面組件功能和原目標組件功能。

Spring提供了下面兩種動態代理技術實現:

1)採用CGLIB技術實現(目標組件沒有接口採用此方法)

例如:public class 代理類 extends 原目標類型 { }

CostAction action=new 代理類();//代理類中有原來類的方法

2)採用JDK Proxy API實現(目標組件有接口採用此方法,即實現了某個接口)

例如:Public class 代理類 implements 原目標接口 { }

CostDAO costDAO=new 代理類();//代理類去實現了原目標接口,因此沒有原來類的方法

5.4案例:AOP的使用,模擬某些組件須要記錄日誌的功能

接3.三、3.4案例,想讓全部的操做進行日誌記錄,那麼按之前的方式就須要給全部Action或DAO中添加記錄日誌的代碼,若是Action或DAO不少,那麼不便於維護。而使用AOP機制,則能夠很方便的實現上述功能:

step1:導入AOP須要的包:aopalliance.jar、aspectjrt.jar、aspectjweaver.jar、cglib-nodep-2.1_3.jar

step2:在org.tarena.aop包下新建LoggerBean類,並添加logger方法用於模擬記錄日誌功能

public void logger(){ System.out.println("記錄了用戶的操做日誌"); }

step3:在applicationContext.xml配置文件中,添加AOP機制

<bean id="loggerBean" class="org.tarena.aop.LoggerBean"></bean>

<aop:config>

<!--定義切入點,指定目標組件和方法。id:可任意起個名字。expression:指定哪些組件是目標,並做用在這些目標的方法上。下面表示全部Action中的全部方法爲切入點-->

<aop:pointcut id="actionPointcut" expression="within(org.tarena.action.*)" />

<!--定義方面,將loggerBean對象指定爲方面組件,loggerBean從普通Bean組件升級爲了方面組件-->

<aop:aspect id="loggerAspect" ref="loggerBean">

<!-- aop:before在操做前執行 aop:after操做後執行 -->

<!-- 定義通知,aop:before:指定先執行方面組件的logger方法,再執行切入點指定的目標方法。aop:after:與aop:before相反 -->

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

</aop:aspect>

</aop:config>

step4:執行3.3案例step3,則發現添加操做已有了記錄日誌功能

建立CostDAO對象 初始化CostDAO對象   記錄了用戶的操做日誌

處理資費添加操做 利用JDBC技術實現保存資費記錄

step5:執行3.4案例step3,則發現刪除操做已有了記錄日誌功能,記得加無參構造方法!

記錄了用戶的操做日誌 處理資費刪除操做

利用Hibernate技術實現刪除資費記錄

u 注意事項:DeleteAction用的是構造注入,因此此處要把無參構造器再加上!由於AOP底層調用了DeleteAction的無參構造方法。不加則報錯:Superclass has no null constructors but no arguments were given

5.5通知類型

通知決定方面組件和目標組件做用的關係。主要有如下幾種類型通知:

1)前置通知:方面組件在目標方法以前執行。

2)後置通知:方面組件在目標方法以後執行,目標方法沒有拋出異常才執行方面組件。

3)最終通知:方面組件在目標方法以後執行,目標方法有沒有異常都會執行方面組件。

4)異常通知:方面組件在目標方法拋出異常後才執行。

5)環繞通知:方面組件在目標方法以前和以後執行。

try{ //前置通知執行時機<aop:before>

//執行目標方法

//後置通知執行時機<aop:after-returning>

}catch(Exception e){//異常通知執行時機<aop:after-throwing>

}finally{ //最終通知執行時機<aop:after>

}//環繞通知等價於前置+後置<aop:around>

5.6切入點

切入點用於指定目標組件和方法,Spring提供了多種表達式寫法:

1)方法限定表達式:指定哪些方法啓用方面組件。

①形式:execution(修飾符? 返回類型 方法名(參數列表) throws 異常?)

②示例:

execution(public * * (..)),匹配容器中,全部修飾符是public(不寫則是無要求的),返回類型、方法名都沒要求,參數列表也不要求的方法。

execution(* set*(..)),匹配容器中,方法以set開頭的全部方法。

execution(* org.tarena.CostDAO.*(..)),匹配CostDAO類中的全部方法。

execution(* org.tarena.dao.*.*(..)),匹配dao包下全部類全部方法。

execution(* org.tarena.dao..*.*(..)),匹配dao包及其子包中全部類全部方法。

2)類型限定表達式:指定哪些類型的組件的全部方法啓用方面組件(默認就是全部方法都啓用,且知道類型,不到方法)。

①形式:within(類型) ②示例:

within(com.xyz.service.*),匹配service包下的全部類全部方法

within(com.xyz.service..*),匹配service包及其子包中的全部類全部方法

within(org.tarena.dao.CostDAO),匹配CostDAO全部方法

u 注意事項:within(com.xyz.service..*.*),爲錯誤的,就到方法名!

3)Bean名稱限定:按<bean>元素的id值進行匹配。

①形式:Bean(id值) ②示例:

bean(costDAO),匹配id=costDAO的bean對象。

bean(*DAO),匹配全部id值以DAO結尾的bean對象。

4)args參數限定表達式:按方法參數類型限定匹配。

①形式:args(類型) ②示例:

args(java.io.Serializable),匹配方法只有一個參數,而且類型符合Serializable的方法,public void f1(String s)、public void f2(int i)都能匹配。

u 注意事項:上述表達式可使用&&、| | 運算符鏈接使用。

5.7案例:環繞通知,修改5.4案例使之動態顯示所執行的操做

step1:新建opt.properties文件,自定義格式:包名.類名.方法名=操做名。在高版本MyEclipse中,切換到Properties界面,點擊Add直接輸入鍵和值,則中文會自動轉爲ASCII碼。低版本的則須要使用JDK自帶的轉換工具:native2ascii.exe

#第一個爲資費添加,第二個爲資費刪除

org.tarena.action.CostAction.execute=\u8D44\u8D39\u6DFB\u52A0

org.tarena.action.DeleteAction=\u8D44\u8D39\u5220\u9664

step2:新建PropertiesUtil工具類,用於解析.properties文件

private static Properties props = new Properties();

static{ try{ props.load(PropertiesUtil.class.getClassLoader()

.getResourceAsStream("opt.properties"));

}catch(Exception ex){ ex.printStackTrace(); } }

public static String getValue(String key){

String value =  props.getProperty(key);

if(value == null){ return "";  }else{ return value;  } }

step3:使用環繞通知,將5.4案例step3中的<aop:before />標籤換爲<aop:around />

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

step4:修改5.4案例step2中的LoggerBean類

public Object logger(ProceedingJoinPoint pjp) throws Throwable{//採用環繞通知,加參數

//前置邏輯

String className=pjp.getTarget().getClass().getName();//獲取要執行的目標組件類名

String methodName=pjp.getSignature().getName();//獲取要執行的方法名

//根據類名和方法名,給用戶提示具體操做信息

String key=className+"."+methodName; System.out.println(key);

//解析opt.properties,根據key獲取value

String value=PropertiesUtil.getValue(key);

//XXX用戶名能夠經過ActionContext.getSession獲取

System.out.println("XXX執行了"+value+"操做!操做時間:"+

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(

new Date(System.currentTimeMillis())));

Object obj=pjp.proceed();//執行目標方法

//後置邏輯

return obj; }

step5:分別執行3.3案例step3和3.4案例step3,執行結果動態顯示所執行的操做及時間

XXX執行了資費添加操做!操做時間:2013-08-19 20:14:47

XXX執行了資費刪除操做!操做時間:2013-08-19 20:15:45

5.8案例:利用AOP實現異常處理,將異常信息寫入文件

1)分析:方面:將異常寫入文件。切入點:做用到全部Action業務方法上

within(org.tarena.action..*)。通知:異常通知<aop:after-throwing>。

2)實現:step1:在org.tarena.aop包中建立ExceptionBean類

public class ExceptionBean {//模擬,將異常信息寫入文件

public void exec(Exception ex){//ex表明目標方法拋出的異常

System.out.println("將異常記錄文件"+ex); //記錄異常信息 } }

step2:在applicationContext.xml配置文件中進行配置

<bean id="exceptionBean" class="org.tarena.aop.ExceptionBean"></bean>

<aop:pointcut id="actionPointcut" expression="within(org.tarena.action.*)"/>

<!-- 定義方面組件,將exceptionBean指定爲方面 -->

<aop:aspect id="exceptionAspect" ref="exceptionBean">

<!-- throwing:和自定的方法中的參數名相同。必定要把異常拋出來才行!

try-catch了則不行! -->

<aop:after-throwing pointcut-ref="actionPointcut" method="exec" throwing="ex"/>

</aop:aspect>

step3:在DeleteAction的execute方法中添加異常

String str=null; str.length();

step4:執行3.3案例step3則添加操做執行正常;執行3.4案例step3則刪除操做報空指針異常!顯示結果:將異常記錄文件java.lang.NullPointerException

6、Log4j日誌記錄工具

6.1 Log4j介紹

Log4j主要用於日誌信息的輸出。能夠將信息分級別(錯誤、嚴重、警告、調式信息)按不一樣方式(控制檯、文件、數據庫)和格式輸出。

Log4j主要有如下3部分組件構成:

1)日誌器(Logger):負責消息輸出,提供了各類不一樣級別的輸出方法。

2)輸出器(Appender):負責控制消息輸出的方式,例如輸出到控制檯、文件輸出等。

3)佈局器(格式器,Layout):負責控制消息的輸出格式。

6.2 Log4j的使用

step1:引入log4j.jar

step2:在src下添加log4j.properties(定義了消息輸出級別、採用哪一種輸出器、採用哪一種佈局器)

#level:大小寫均可,myconsole是本身隨便起的appender名字,能夠寫多個appender

log4j.rootLogger=debug,myconsole,myfile

#appender:可在org.apache.log4j中找自帶的類

log4j.appender.myconsole=org.apache.log4j.ConsoleAppender

log4j.appender.myfile=org.apache.log4j.FileAppender

#log4j.appender.myfile.File=D:\\error.txt

log4j.appender.myfile.File=D:\\error.html

#layout:可在org.apache.log4j中找自帶的類

log4j.appender.myconsole.layout=org.apache.log4j.SimpleLayout

log4j.appender.myfile.layout=org.apache.log4j.HTMLLayout

u 注意事項:級別從小到大爲:debug、info、warn、error、fatal

step3:建立TestLog4j類,測試利用日誌器不一樣的方法輸出消息。

public class TestLog4j {

public static Logger logger=Logger.getLogger(TestLog4j.class);

public static void main(String[] args) {

//能顯示就顯示,不顯示也不會影響主程序後面的運行,僅是個輔助工具

logger.debug("調試信息"); logger.info("普通訊息");

logger.warn("警告信息"); logger.error("錯誤信息");

logger.fatal("致命信息"); } }

u 注意事項:

v 導包爲org.apache.log4j.Logger。

v 若在log4j.properties中指定的級別爲debug,則五種信息都會顯示;若指定的級別爲error,則只顯示error和fatal信息。

6.3案例:修改5.8案例,使用Log4j記錄日誌

step1:繼續使用6.2節step1和step2

step2:修改5.8案例step1

public class ExceptionBean {//將異常信息寫入文件

Logger logger=Logger.getLogger(Exception.class);

public void exec(Exception ex){//ex表明目標方法拋出的異常

logger.error("====異常信息====");//記錄異常信息

logger.error("異常類型"+ex);

StackTraceElement[] els=ex.getStackTrace();

for(StackTraceElement el:els){ logger.error(el); } } }

step3:執行3.4案例step3則刪除操做報空指針異常(前提:已進行了5.8案例step3操做)!因爲log4j.properties配置了兩種輸出方式,因此兩種方式都有效。

控制檯的顯示結果:

XXX執行了資費刪除操做!操做時間:2013-08-20 12:47:54

ERROR - ====異常信息====

ERROR - 異常類型java.lang.NullPointerException

…… …… …… ……

HTML顯示結果:

wps_clip_image-12442

7、
Spring註解配置

註解技術從JDK5.0推出,以後不少框架開始提供註解配置形式。Spring框架從2.5版本開始支持註解配置。註解配置的優勢:簡單、快捷。

7.1組件掃描功能

Spring能夠按指定的包路徑掃描內部的組件,當發現組件類定義前有一下的註解標記,會將該組件歸入Spring容器中。

1)@Component(其餘組件)

2)@Controller(Action組件,負責調Service)

3)@Service(Service組件,負責調DAO,處理一些額外邏輯)

4)@Repository(DAO組件,負責訪問數據庫)

u 注意事項:

v 括號中的爲推薦用法,上述4個註解任意用也能夠,但不符合規範。

v 註解只能用在類定義前、方法定義前、成員變量定義前!

7.2組件掃描的使用方法

step1:在applicationContext.xml配置文件中開啓組件掃描配置

<!-- 開啓組件掃描,base-package指定掃描包路徑。使用前提:要有xmlns:context命名空間的引入。base-package="org.tarena"這麼寫,則dao和action都能被掃描-->

<context:component-scan base-package="org.tarena" />

step2:在要掃描的組件的類定義前使用上述註解標記便可。例如在JdbcCostDAO類前使用

@Repository

public class JdbcCostDAO implements CostDAO {

public JdbcCostDAO(){ System.out.println("建立CostDAO對象"); }

@PostConstruct//等價於設置了init-method="方法名"屬性

public void myinit(){ System.out.println("初始化CostDAO對象"); }

@PreDestroy//等價於設置了destroy-method="方法名"屬性

public void mydestroy(){ System.out.println("銷燬CostDAO對象"); }

…… ……

@Repository等價於原來配置文件中的:

<bean id="jdbcCostDAO" class="org.tarena.dao.JdbcCostDAO"></bean>

加上@Scope("prototype")等價於原配置文件中的:

<bean id="jdbcCostDAO" scope="prototype" class="org.tarena.dao.JdbcCostDAO"></bean>

u 注意事項:

v 上述標記將組件掃描到容器後,id屬性默認是類名首字母小寫。若是須要自定義id值,可使用@Repository("自定義id值"),其餘註解也同理。

v 類的命名和變量的命名要規範!首字母大寫,第二個字母要小寫!不然在使用框架時會有衝突或沒法識別,如類名爲JDBCCostDAO時沒法識別它的id值:jDBCCostDAO,此時以它的類名做爲id值卻可識別:JDBCCostDAO。

v 默認採用singleton模式建立Bean對象,若是須要改變,可使用

@Scope("prototype")定義。

v lazy-init="true"屬性只能在<beans>根元素定義了,沒有對應的註解。

step3:建立TestAnnotation類,用於測試註解

@Test //組件掃描

public void test1(){ String conf="/applicationContext.xml";

ApplicationContext ac=new ClassPathXmlApplicationContext(conf);

//獲取掃描到容器的Bean對象

CostDAO costDAO=(CostDAO)ac.getBean("jdbcCostDao");

costDAO.delete();//組件掃描,默認的id爲類名首字母小寫!且默認單例模式 }

7.3注入註解標記使用方法

若是容器中兩個符合要求可被注入同一個組件的Bean對象,能夠採用下面註解標記:

1)@Resource,默認按類型匹配注入(JDK自帶的)。如有多個符合要求的類型,則報錯:匹配不惟一,那麼就須要採起按名稱注入的方式,它的使用格式爲:

@Resource(name="須要注入的Bean對象id值")。

2)@Autowired,默認按類型匹配注入(Spring提供的)。如有多個符合要求的類型,則採起按名稱注入的方式,它的使用格式爲:

@Autowired

@Qualifier("須要注入的Bean對象id值")

u 注意事項:注入標記在成員變量定義前,但@Resource也能夠在set方法前使用!

3)案例:id爲hibernateCostDao的Bean對象和id爲costDao的Bean對象,都符合CostDAO接口,在CostAction組件中注入,那麼此時將會報錯:匹配不惟一。解決以下:

step1:修改CostActin,添加註入標記

@Controller("costAction")

@Scope("prototype")

public class CostAction {

//@Resource//將costDao注入,按類型匹配注入,JDK自帶的

//@Autowired//將costDao注入,按類型匹配注入,Spring提供的

//@Resource(name="hibernateCostDao")//當有多個符合要求的類型,則按名稱注入

@Autowired

@Qualifier("hibernateCostDao")//當有多個符合要求的類型,則按名稱注入

private CostDAO costDAO;

public void setCostDAO(CostDAO costDAO) { this.costDAO = costDAO; }

…… …… }

step2:在TestAnnotation類,添加方法測試注入標記

@Test //注入標記測試

public void test2(){ String conf="/applicationContext.xml";

ApplicationContext ac=new ClassPathXmlApplicationContext(conf);

CostAction costAction=(CostAction)ac.getBean("costAction");

costAction.execute(); }

step3:可正常執行,若是沒寫注入標記則報錯:NullPointerException

7.4 AOP註解標記使用方法

step1:在applicationContext.xml配置文件中開啓AOP註解

<aop:aspectj-autoproxy /><!--以前的配置可都刪除,只留根元素-->

step2:在方面組件中,使用下面註解標記:

1)首先使用@Component將組件掃描到Spring容器。

2)而後使用@Aspect將組件定義爲方面組件。

3)以後定義一個空方法(方法名隨便起)在方法前使用@Pointcut定義切入點表達式。

4)最後在方面組件的處理方法前使用@Around、@Before、@AfterReturning、@AfterThrowing、@After

例如:修改5.7案例step4中的LoggerBean類

@Component//將組件掃描到Spring容器

@Aspect//將該組件定義爲方面組件

public class LoggerBean {

//定義切入點

@Pointcut("within(org.tarena.action..*)")

public void mypoint(){}//主要目的是使用@Pointcut標記,id則爲它的方法名mypoint

//採用環繞通知

@Around("mypoint()")//方法即爲下面的方法名

public Object logger(ProceedingJoinPoint pjp) throws Throwable{

//……方法體內容沒變…… } }

u 注意事項:@Pointcut註解在JDK1.7中不能識別,只能把切入點表達式寫在通知中:

@Around("within(org.tarena.action..*)")。而此用法JDK1.6也支持。

step3:再次執行3.3案例step3,則也可正常執行

step4:把6.3案例step2中的ExceptionBean修改成使用AOP註解

@Component//將組件掃描到Spring容器

@Aspect//將該組件定義爲方面組件

public class ExceptionBean {

Logger logger=Logger.getLogger(Exception.class);

@Pointcut("within(org.tarena.action..*)")

public void mypoint(){}

@AfterThrowing(pointcut="mypoint()",throwing="ex")//方法名即爲下面方法的名字

public void exec(Exception ex){ //……方法體內容沒變…… } }

step5:執行6.3案例step3,則正常執行,控制檯顯示空指針異常,異常信息也被寫入HTML文件。

8、
Spring對數據訪問技術的支持

8.1 Spring提供了統一的異常處理類型

1)SQLException是JDBC拋的異常。

2)org.hibernate.XXXException是Hibernate拋出的異常。

3)Spring提供的異常根類:DataAccessException,可是不少異常都被try-catch了,因此出錯後看不到提示,所以要用Log4j。

8.2 Spring提供了編寫DAO的支持類

1)DaoSupport類:JdbcDaoSupport、HibernateDaoSupport,本身寫的DAO按使用的訪問技術,有選擇的繼承它們(相似於之前寫的BaseDAO類)。

2)Template類:JdbcTemplate、HibernateTemplate,封裝了通用操做,如:增刪改查。特殊操做,如:分頁查詢,則仍須要使用Hibernate原來的方式,詳見8.6節。

3)繼承DaoSupport類後,就可經過getJdbcTemplate()、getHibernateTemplate()方法得到對應的Template類對象,便可進行通用操做:

①update():實現增刪改查。 ②query():實現查詢多行記錄。

③queryForObject():實現查詢單行記錄。 ④queryForInt():實現查詢單個int值。

4)將一條記錄轉換成一個實體對象,須要實現Spring提供的RowMapper接口(將實體與記錄間的轉換寫在它的實現類中),由於Spring提供的Template對象中的查詢方法query()有RowMapper類型的參數。

8.3 Spring提供了聲明式事務管理方法

基於AOP配置實現,不須要寫Java代碼,加註解標籤便可。詳情見第十章。

8.4 Spring框架如何使用JDBC技術

之前的DBUtil則不須要寫了,交給Spring配置。此例包含註解配置和xml配置。

step1:新建一個工程,引入Spring開發包(IoC和AOP開發包)和配置文件

spring.jar/commons-logging.jar/aopalliance.jar/aspectjrt.jar/aspectjweaver.jar/cglib-nodep-2.1_3.jar

step2:引入JDBC技術相關的開發包(驅動包)

step3:根據要操做的表,編寫對應的實體類。此處用Hibernate筆記5.16案例中Cost實體

step4:編寫DAO接口CostDAO和實現類JdbcCostDAO(實現類繼承JdbcDaoSupport,並使用其提供的getJdbcTemplate對象實現增刪改查操做)

1)CostDAO接口

public Cost findById(int id);   public void save(Cost cost); public void delete(int id);

public void update(Cost cost);  public int count();         public List<Cost> findAll();

2)JdbcCostDAO實現類

public class JdbcCostDAO extends JdbcDaoSupport implements CostDAO{

public void delete(int id) {  String sql="delete from COST_CHANG where ID=?";

Object[] params={id}; getJdbcTemplate().update(sql,params); }

public List<Cost> findAll() { String sql="select * from COST_CHANG";

/** 將一條記錄轉換成一個Cost對象,須要實現Spring提供的RowMapper接口  */

RowMapper mapper=new CostRowMapper();

List<Cost> list=getJdbcTemplate().query(sql,mapper); return list; }

public int count(){    String sql="select count(*) from COST_CHANG";

int size=getJdbcTemplate().queryForInt(sql); return size; }

public Cost findById(int id) {  String sql="select * from COST_CHANG where ID=?";

Object[] params={id}; RowMapper mapper=new CostRowMapper();

Cost cost=(Cost)getJdbcTemplate().queryForObject(sql, params,mapper);

return cost; }

public void save(Cost cost) {

String sql="insert into COST_CHANG(ID,NAME,BASE_DURATION," +

"BASE_COST,UNIX_COST,STATUS,DESCR," +

"STARTTIME) values(COST_SEQ_CHANG.nextval,?,?,?,?,?,?,?)";

Object[] params={ cost.getName(),  cost.getBaseDuration(),  cost.getBaseCost(),

cost.getUnitCost(),  cost.getStatus(), cost.getDescr(), 

cost.getStartTime() };

getJdbcTemplate().update(sql,params); }

public void update(Cost cost) {

String sql="update COST_CHANG set NAME=?,BASE_DURATION=?," +

BASE_COST=?,UNIX_COST=?,STATUS=?,DESCR=?," +

"STARTTIME=? where ID=?";

Object[] params={ cost.getName(),  cost.getBaseDuration(),  cost.getBaseCost(),

cost.getUnitCost(),  cost.getStatus(),  cost.getDescr(),  cost.getStartTime(),

cost.getId()  };

getJdbcTemplate().update(sql,params); } }

3)在org.tarena.entity包中建立CostRowMapper類

public class CostRowMapper implements RowMapper{//實現RowMapper接口

/** rs:結果集。index:當前記錄的索引。Object:返回實體對象。 */

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

Cost cost = new Cost(); cost.setId(rs.getInt("ID"));

cost.setName(rs.getString("NAME"));

cost.setBaseDuration(rs.getInt("BASE_DURATION"));

cost.setBaseCost(rs.getFloat("BASE_COST"));

cost.setUnitCost(rs.getFloat("UNIT_COST"));

cost.setStartTime(rs.getDate("STARTIME"));

cost.setStatus(rs.getString("STATUS")); return cost; } }

step5:將DAO組件交給Spring容器,在applicationContext.xml中進行相關配置

1)定義DAO組件的<bean>元素

<bean id="costDao" class="org.tarena.dao.impl.JdbcCostDAO">

<!-- 此處須要注入鏈接資源給DaoSupport,用於實例化Template對象,不然沒有與數據庫的鏈接!dataSource:表明鏈接池對象。此處實際是給JdbcDaoSupport注入鏈接,用於實例化JdbcTemplate對象。 -->

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

</bean>

2)須要DAO的Bean注入一個dataSource對象。dataSource對象採用一個鏈接池構建(此處使用dbcp鏈接池),先引入dbcp鏈接池開發包(commons-pool.jar、commons-dbcp-1.2.2.jar、commons-collections-3.1.jar),再定義dataSource對象的<bean>元素。

<!-- 定義鏈接池Bean對象 -->

<bean id="MyDataSource" class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

   <!-- 注入數據庫鏈接參數 -->

   <property name="url" value="jdbc:oracle:thin:@localhost:1521:dbchang"></property>

   <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>

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

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

   <property name="maxActive" value="20"></property><!-- 設置鏈接最大數 -->

   <!-- 鏈接池實例化時初始建立的鏈接數 -->

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

</bean>

step6:建立TestJdbcCostDAO類,用於測試xml配置,可正常執行

@Test

public void testFindAll() {//測試基於xml配置方法

String conf="/applicationContext.xml";

ApplicationContext ac = new ClassPathXmlApplicationContext(conf);

CostDAO costDAO=(CostDAO)ac.getBean("costDao");

List<Cost> list=costDAO.findAll(); System.out.println("總數:"+costDAO.count());

for(Cost c:list){ System.out.println(c.getId()+" "+c.getName()); }

System.out.println("findById:"+costDAO.findById(1).getName()); }

step7:使用註解配置,保留step5中的鏈接資源MyDataSource,而Bean組件costDao刪除,並添加開啓組件掃描的配置

<context:component-scan base-package="org.tarena" /><!-- 開啓組件掃描 -->

step8:在JdbcCostDAO中添加註解

@Repository   //id值默認爲類名首字母小寫

@Scope("prototype") //設置非單例模式

public class JdbcCostDAO extends JdbcDaoSupport implements CostDAO{

@Resource //將容器中的myDataSource按類型匹配注入

/** 雖然繼承了JdbcDaoSupport,但咱們沒法在別人的代碼中添加註解,因此咱們添加一個set方法,注意名字別用setDataSource!由於JdbcDaoSupport 有同名的set方法,且是final修飾的,因此須要稍微改變一下。 */

public void setMyDataSource(DataSource ds){

super.setDataSource(ds);//將注入的dataSource給DaoSupport注入 }

…… ……

step9:在TestJdbcCostDAO類添加方法,用於測試註解配置,可正常執行

@Test

public void testFindAllByAnnotation() {//測試基於註解配置方法

String conf="/applicationContext.xml";

ApplicationContext ac = new ClassPathXmlApplicationContext(conf);

CostDAO costDAO=(CostDAO)ac.getBean("jdbcCostDAO");

List<Cost> list=costDAO.findAll(); System.out.println("總數:"+costDAO.count());

for(Cost c:list){ System.out.println(c.getId()+" "+c.getName());    }

System.out.println("findById:"+costDAO.findById(1).getName()); }

u 注意事項:此時因爲一開始定義的id爲costDao的Bean已被刪除,因此TestJdbcCostDAO類中的testFindAll方法已沒法執行。在測試註解配置時,能夠新建applicationContext-annotation.xml文件,把註解開啓、鏈接資源寫這裏,以後在測試方法中,指定它的名字便可!這樣兩個方法均可執行。

step10:優化,當大量Bean組件須要採起註解配置時,每一個Bean都要寫set方法注入dataSource鏈接資源,因此比較麻煩,可採起繼承方式:

1)新建JdbcBaseDAO類,並繼承JdbcDaoSupport,把set方法寫入

public class JdbcBaseDAO extends JdbcDaoSupport {

@Resource //將容器中的myDataSource按類型匹配注入

public void setMyDataSource(DataSource ds){//注意名字,詳見step8

super.setDataSource(ds); }  }

2)刪除step8中JdbcCostDAOset的set方法及其前面的註解,並繼承JdbcBaseDAO

public class JdbcCostDAO extends JdbcBaseDAO implements CostDAO{ ……  }

8.5鏈接池優勢

1)加強數據訪問的穩定性。

2)鏈接池能夠將鏈接數控制在安全的範圍內。

3)鏈接池中的鏈接對象始終與數據庫保持聯通狀態,它的close方法被重寫,不是真正的關閉,而是把鏈接又放回池中,避免了重複的新建鏈接和釋放鏈接過程。

8.6 Spring框架如何使用Hibernate技術

Hibernate的主配置也不須要了,也交給Spring配置。此例包含註解配置和xml配置。

step1:新建一個工程,引入Spring開發包(IoC和AOP開發包)和配置文件

spring.jar/commons-logging.jar/aopalliance.jar/aspectjrt.jar/aspectjweaver.jar/cglib-nodep-2.1_3.jar

step2:引入Hibernate相關的開發包(Hibernate開發包+驅動)

step3:編寫實體類和hbm.xml映射描述文件,此處用Hibernate筆記5.16案例中Cost實體及其Cost.hbm.xml映射文件

step4:編寫DAO接口和HibernateCostDAO實現類(實現類繼承HibernateDaoSupport,並使用其提供的HibernateTemplate對象實現增刪改查操做)

1)CostDAO接口,與8.4節step4中1)基本相同,但額外添加一個方法

public List<Cost> findPage(int page,int pageSize);//分頁查詢

2)HibernateCostDAO實現類

public class HibernateCostDAO extends HibernateDaoSupport implements CostDAO {

public int count() { String hql="select count(*) from Cost";

List list=getHibernateTemplate().find(hql);

int size=Integer.parseInt(list.get(0).toString());

/** 最好先變成string再轉成int。不要強轉,Spring的不一樣版本返回值多是long、Integer類型 */ return size;  }

public void delete(int id) { Cost cost=findById(id);

getHibernateTemplate().delete(cost); }

public List<Cost> findAll() { String hql="from Cost";

List<Cost> list=getHibernateTemplate().find(hql); return list; }

public Cost findById(int id) {

Cost cost=(Cost)getHibernateTemplate().get(Cost.class, id);  return cost; }

public void save(Cost cost) { getHibernateTemplate().save(cost); }

public void update(Cost cost) { getHibernateTemplate().update(cost); }

public List<Cost> findPage(final int page,final int pageSize) {//分頁查詢,參數爲final

List<Cost> list=(List<Cost>)getHibernateTemplate().execute(

new HibernateCallback(){//回調函數及下面的Session如何使用詳見8.7節

public Object doInHibernate(Session session)//記得改參數名

throws HibernateException, SQLException {

String hql="from Cost";

Query query=session.createQuery(hql);//使用session對象

int begin=(page-1)*pageSize;

query.setFirstResult(begin);

query.setMaxResults(pageSize);

return query.list();

}

}

);

return list; } }

step5:將DAO組件交給Spring容器管理,在applicationContext.xml中進行相關配置

<bean id="hibernateCostDao" scope="prototype"

class="org.tarena.dao.impl.HibernateCostDAO">

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

</bean><!-- name:表明Hibernate的鏈接資源,該資源要麼是hibernateTemplate類型,要麼是sessionFactory類型(按Alt+/就會顯示),咱們用sessionFactory類型。ref:名字任意起,是咱們配置的sessionFactory鏈接資源,有了該資源,getHibernateTemplate()方法才能執行 -->

step6:在applicationContext.xml中配置sessionFactory資源(至關於Hibernate的主配置)

<bean id="MySessionFactory"

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

<!-- LocalSessionFactoryBean是Hibernate中SessionFactory的子類,咱們不用寫 -->

<!-- 注入數據庫鏈接信息,此處要再配置一下dataSource數據庫鏈接資源 -->

<property name="dataSource" ref="MyDataSource"></property><!--ref:名字任意起-->

<!-- 注入Hibernate配置參數 -->

<property name="hibernateProperties">

<props><!-- 放Spring中屬性要加個前綴hibernate -->

<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>

<prop key="hibernate.show_sql">true</prop>

<prop key="hibernate.format_sql">true</prop>

</props>

</property>

<!-- 注入映射描述 -->

<property name="mappingResources">

<list><value>org/tarena/entity/Cost.hbm.xml</value></list>

</property>

</bean>

step7:在applicationContext.xml中配置dataSource數據庫鏈接資源,與8.4案例step5中2)相同,dataSource對象採用一個鏈接池構建(此處使用dbcp鏈接池),先引入dbcp鏈接池開發包(commons-pool.jar、commons-dbcp-1.2.2.jar、commons-collections-3.1.jar)

<!-- 定義鏈接池Bean對象 -->

<bean id="MyDataSource" class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

   <!-- 注入數據庫鏈接參數 -->

   <property name="url" value="jdbc:oracle:thin:@localhost:1521:dbchang"></property>

   <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>

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

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

   <property name="maxActive" value="20"></property><!-- 設置鏈接最大數 -->

   <!-- 鏈接池實例化時初始建立的鏈接數 -->

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

</bean>

u 注意事項:此案例的step5-7的注入關係爲:dataSource對象(dbcp鏈接池構建的)注入給sessionFactory對象,sessionFactory對象注入給DAO(Bean)對象。

step8:建立TestHibernateCostDAO類,用於測試xml配置,可正常執行

@Test //測試基於xml配置方法

public void testFindAll() { String conf="/applicationContext.xml";

ApplicationContext ac = new ClassPathXmlApplicationContext(conf);

CostDAO costDAO=(CostDAO)ac.getBean("hibernateCostDao");

List<Cost> list=costDAO.findAll(); System.out.println("總數:"+costDAO.count());

for(Cost c:list){ System.out.println(c.getId()+" "+c.getName()); }

System.out.println("findById:"+costDAO.findById(1).getName()); }

step9:使用註解配置,新建applicationContext-annotation.xml配置文件(此處使用的爲8.4案例step9中注意事項說的方式),在其中添加step6中的sessionFactory資源,添加step7中的dataSource數據庫鏈接資源(不要step5中的Bean組件hibernateCostDao),並添加開啓組件掃描的配置

<context:component-scan base-package="org.tarena" /><!-- 開啓組件掃描 -->

step10:在HibernateCostDAO中添加註解

@Repository("hibernateCostDao")

@Scope("prototype")

public class HibernateCostDAO extends HibernateDaoSupport implements CostDAO {

@Resource //setSessionFactory名字用不了是final的,同理說明見8.4案例step8

public void setMySessionFactory(SessionFactory sf){

super.setSessionFactory(sf);//將注入的sessionFactory給HibernateDaoSupport傳入

}

…… …… ……

step11:在TestHibernateCostDAO類添加方法,用於測試註解配置,可正常執行

@Test //測試基於註解配置方法

public void testFindAllByAnnotation() { String conf="/applicationContext-annotation.xml";

ApplicationContext ac = new ClassPathXmlApplicationContext(conf);

CostDAO costDAO=(CostDAO)ac.getBean("hibernateCostDao");

List<Cost> list=costDAO.findAll(); System.out.println("總數:"+costDAO.count());

for(Cost c:list){ System.out.println(c.getId()+" "+c.getName()); }

System.out.println("findById:"+costDAO.findById(1).getName()); }

step12:在TestHibernateCostDAO類添加方法,用於測試分頁查詢

@Test //分頁查詢

public void testFindByPage() { String conf="/applicationContext-annotation.xml";

ApplicationContext ac = new ClassPathXmlApplicationContext(conf);

CostDAO costDAO=(CostDAO)ac.getBean("hibernateCostDao");

List<Cost> list=costDAO.findPage(2, 4);System.out.println("總數:"+costDAO.count());

for(Cost c:list){ System.out.println(c.getId()+" "+c.getName()); } }

8.7 Spring+Hibernate如何使用Session、Query等對象

1)方式一:利用HibernateDaoSupport提供的getSession()方法:

沒用到延遲加載的API,那麼用這個方式簡單些。可是有延遲加載的API,則會出現問題:session關閉早了,頁面可能獲取不到數據;不關閉吧,一但出了方法體則關不上了!而屢次訪問數據庫後,就發現沒結果,由於鏈接數用完了。

Session session=getSession();

……利用session進行一些操做……

session.close();//注意,必定要釋放!

u 注意事項:getHibernateTemplate中的API都有釋放操做,因此本身不用再寫。

2)方式二:利用HibernateTemplate.execute()方法,以回調函數方式使用。這種方式不用擔憂方式一出現的問題,session的關閉由HibernateTemplate統一管理。

getHibernateTemplate().execute(

new HibernateCallback(){//實現該接口的doInHibernate方法

public Object doInHibernate(Session session)

throws HibernateException, SQLException{

//回調函數中使用session,可見8.6案例step4

}

}

);

8.8 Spring框架和Struts2整合應用

step1:新建一個工程,引入Spring開發包(IoC和AOP開發包)、配置文件和Struts2的五個基本核心包

spring.jar/commons-logging.jar/aopalliance.jar/aspectjrt.jar/aspectjweaver.jar/cglib-nodep-2.1_3.jar/xwork-core.jar/struts-core.jar/ognl.jar/freemarker.jar/commons-fileupload.jar

step2:在org.tarena.action包中新建HelloAction

private String name;//output

public String getName() { return name; }

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

public String  execute(){ name="Chang"; return "success"; }

step3:新建applicationContext.xml文件並配置,將Action或DAO等組件交給Spring容器

<bean id="helloAction" scope="prototype" class="org.tarena.action.HelloAction"></bean>

step4:引入Struts2和Spring整合的開發包:struts-spring-plugin.jar。該開發包的做用爲:當Struts2請求過來時,Action對象將交給整合包去Spring容器獲取,即Struts2再也不產生Action對象,詳解介紹可看第九章。

step5:新建struts.xml文件,並配置<action>,將class屬性與Spring容器中<bean>元素的id屬性保持一致。(整合包利用class值看成id標識去Spring容器獲取Bean對象)

<struts>

<package name="spring05" extends="struts-default">

<!--關鍵:利用struts-spring-plugin.jar去Spring容器尋找Bean對象

利用class屬性看成id值去Spring容器獲取,詳細介紹看第九章 -->

<!-- 原來爲class="org.tarena.action.HelloAction",如今改成id值:helloAction -->

<action name="hello" class="helloAction"><result>/hello.jsp</result></action>

</package>

</struts>

step6:在WebRoot目錄中,新建hello.jsp和index.jsp

1)hello.jsp

<body style="font-size:30px;font-style:italic;">${name }你好! </body>

2)index.jsp

<a href="hello.action">Hello示例</a>

step7:在web.xml中添加前端控制器

<filter>

<filter-name>StrutsFilter</filter-name>

<filter-class>

org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter

</filter-class>

</filter><!-- 前端控制器 -->

<filter-mapping>

<filter-name>StrutsFilter</filter-name>

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

</filter-mapping>

step8:部署,啓動服務器,準備測試。可是發現報錯:

You might need to add the following to web.xml:

    <listener>

        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

    </listener>

step9:在web.xml中添加ContextLoaderListener組件配置(能夠在啓動服務器時,實例化Spring容器),但它的默認查找路徑爲:/WEB-INF/applicationContext.xml,因此要指定路徑(src中),不然仍是會報錯:java.io.FileNotFoundException

<!-- 指定Spring配置文件位置和名稱 -->

<context-param><!-- 下面的名字是ContextLoaderListener里約定好的,必須這麼寫 -->

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

    <param-value>classpath:applicationContext.xml</param-value><!--classpath表明src-->

</context-param>

<!-- 在服務器啓動時,實例化Spring容器,在中間過程沒法插入實例化代碼,由於都是自動的,因此沒地方寫!所以要配置listener,啓動服務器則建立Spring容器。 -->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

u 注意事項:在前端控制器前添加!!不然沒法生效。

step10:在瀏覽器中輸入http://localhost:8080/Spring05_Struts2t/index.jsp進行測試

8.9案例:採用SSH結構重構資費管理模塊

修改Hibernate筆記5.16案例中的資費管理模塊(原功能採用Struts2+Hibernate結構)。

採用SSH結構須要追加如下步驟:

step1:引入Spring開發包(IoC和AOP)和applicationContext.xml配置文件

spring.jar/commons-logging.jar/aopalliance.jar/aspectjrt.jar/aspectjweaver.jar/cglib-nodep-2.1_3.jar

====================重構Spring+Hibernate====================

step2:編寫DAO組件,採用Spring+Hibernate方式實現,如:新建SpringHibernateCostDAOImpl

public class SpringHibernateCostDAOImpl extends HibernateDaoSupport implements CostDAO{ public void delete(int id) throws DAOException { Cost cost=findById(id);

getHibernateTemplate().delete(cost); }

public List<Cost> findAll() throws DAOException { String hql="from Cost";

List<Cost> list=getHibernateTemplate().find(hql); return list; }

public List<Cost> findAll(final int page,final int rowsPerPage) throws DAOException {

List list=(List)getHibernateTemplate().execute(

new HibernateCallback(){ @Override

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

  String hql="from Cost";

  Query query=session.createQuery(hql);

  int start=(page-1)*rowsPerPage;//設置分頁查詢參數

  query.setFirstResult(start);//設置抓取記錄的起點,從0開始(第一條記錄)

  query.setMaxResults(rowsPerPage);//設置抓取多少條記錄

  return query.list();//按分頁參數查詢 } }

);

return list; }

public Cost findById(Integer id) throws DAOException {

Cost cost=(Cost)getHibernateTemplate().load(Cost.class, id);

return cost;//有延遲問題!要配置web.xml ,詳見step10 }

public Cost findByName(String name) throws DAOException {

String hql = "from Cost where name=?"; Object[] params = {name};

List<Cost> list = getHibernateTemplate().find(hql,params);

if(!list.isEmpty()){ return list.get(0);

}else{ return null; } }

public int getTotalPages(int rowsPerPage) throws DAOException {

String hql="select count(*) from Cost";//類名

List list=getHibernateTemplate().find(hql);

int totalRows=Integer.parseInt(list.get(0).toString());

if(totalRows%rowsPerPage==0){ return totalRows/rowsPerPage;

}else { return (totalRows/rowsPerPage)+1; } }

public void save(Cost cost) throws DAOException { cost.setStatus("1");

cost.setCreaTime(new Date(System.currentTimeMillis()));

getHibernateTemplate().save(cost); }

public void update(Cost cost) throws DAOException {

getHibernateTemplate().update(cost); } }

step3:在Spring容器的配置文件中定義配置

  1)將DAO掃描到Spring容器

<context:component-scan base-package="com.tarena.netctoss" /><!--開啓組件掃描-->

  2)配置sessionFactory

<!-- 注意:這裏的id值不能自定義了!必須寫sessionFactory,由於配置了

OpenSessionInViewFilter後(session關閉延遲到JSP解析後,詳見step10),默認找的就是這個名字 -->

<bean id="sessionFactory"

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

<!-- LocalSessionFactoryBean是Hibernate中SessionFactory的子類,咱們不用寫-->

<!-- 注入數據庫鏈接信息 -->

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

<!-- 注入Hibernate配置參數 -->

<property name="hibernateProperties">

<props>

<prop key="hibernate.dialect"><!-- 放Spring中屬性加個前綴hibernate -->

org.hibernate.dialect.OracleDialect

</prop>

<prop key="hibernate.show_sql">true</prop>

<prop key="hibernate.format_sql">true</prop>

</props>

</property>

<!-- 注入映射描述 -->

<property name="mappingResources">

<list>

<value>com/tarena/netctoss/entity/Cost.hbm.xml</value>

<value>com/tarena/netctoss/entity/Admin.hbm.xml</value>

</list>

</property>

</bean>

3)引入dbcp開發包(鏈接池),定義DataSource

<!-- 定義鏈接池Bean對象 -->

<bean id="MyDataSource" class="org.apache.commons.dbcp.BasicDataSource"

destroy-method="close">

<!-- 注入數據庫鏈接參數 -->

   <property name="url" value="jdbc:oracle:thin:@localhost:1521:dbchang"></property>

   <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>

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

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

<!-- 設置鏈接最大數 -->

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

<!-- 設置鏈接池實例化時初始建立的鏈接數 -->

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

</bean>

4)將sessionFactory給DAO注入

@Repository @Scope("prototype")//排版須要,這裏寫一行了

public class SpringHibernateCostDAOImpl extends HibernateDaoSupport implements CostDAO{

@Resource

public void setMySessionFactory(SessionFactory sf){//注意名字問題

super.setSessionFactory(sf); }

…… …… ……

step4:測試DAO組件,在TestCostDAO中添加方法,可正常執行

@Test //Spring+Hibernate

public void testFindAllByPageBySpring() throws Exception{

String conf="applicationContext.xml";

ApplicationContext ac = new ClassPathXmlApplicationContext(conf);

CostDAO costDao = (CostDAO)ac.getBean("springHibernateCostDAOImpl");

List<Cost> list = costDao.findAll(1,5);

for(Cost c : list){ System.out.println(c.getId()+" "+c.getName()); } }

====================重構Spring+Struts2====================

step5:(*)將資費模塊中全部Action定義到Spring容器(step3已開啓組件掃描),若是使用了DAO,採用注入方式使用(此處是新建了ListCostActionSpring類,與原來的ListCostAction相同)

@Controller @Scope("prototype")//排版須要,這裏寫一行了

public class ListCostActionSpring { @Resource

private CostDAO costDAO; … … }

step6:引入struts-spring-plugin.jar插件包

step7:(*)修改Action的struts配置,將class屬性修改成容器中Action組件的id值

<action name="list" class="listCostActionSpring">

<result name="success">/WEB-INF/jsp/cost/cost_list.jsp</result>

</action><!-- class可寫包名.類名,即和之前的方式相同,詳情見9.3節-->

step8:在web.xml中添加ContextLoaderListener配置,用於實例化Spring容器,詳細說明見8.8案例step9

<!-- 指定Spring配置文件位置和名稱 -->

<context-param><!--默認在WEB-INF下查找applicationContext.xml的,不指定則報錯-->

<param-name>contextConfigLocation</param-name><!--名字必須這麼寫 -->

<param-value>classpath:applicationContext.xml</param-value><!--classpath表示src-->

</context-param>

<!-- 在服務器啓動時,實例化Spring容器對象 -->

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

step9:測試SSH,發現修改資費時,沒法回顯數據!這是由於Hibernate中的load方法爲延遲加載,而session被早關閉,因此JSP頁面獲取不到數據。爲了支持Hibernate延遲加載的使用,在web.xml中能夠配置Spring提供的OpenSessoinInViewFilter。將Session關閉動做推遲到JSP解析以後。

step10:在web.xml中添加OpenSessoinInViewFilter,利用它控制Session關閉

<!-- 追加OpenSessionInViewFilter,不須要本身寫了,Spring提供了!做用:將Template中的session關閉動做推遲到jsp解析以後 -->

<filter><!--注意:配置的Filter順序,要在Struts控制器Filter以前配置才能生效!-->

<filter-name>openSessionInViewFilter</filter-name>

<filter-class>

org.springframework.orm.hibernate3.support.OpenSessionInViewFilter

</filter-class>

</filter>

<filter-mapping>

<filter-name>openSessionInViewFilter</filter-name>

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

</filter-mapping>

step11:測試SSH,發現增、刪、改會報錯,這是因爲配置OpenSessionInViewInterceptor後產生的(查詢正常),詳細說明見下面的注意事項

org.springframework.dao.InvalidDataAccessApiUsageException:

Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL):

Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from   transaction definition.

step12:在applicationContext.xml配置文件中添加AOP事務控制,使用註解的方式,代碼及詳情見10.3節2)

step13:分別給Action添加事務註解(可類定義前,也可execute或自定義方法前)

1)AddCostAction、DeleteCostAction、UpdateCostAction中添加,事務類型詳見10.3節

@Transactional(propagation=Propagation.REQUIRED)

2)DetailCostAction、ListCostActionSpring、ValidateCostNameAction中添加

@Transactional(readOnly=true,propagation=Propagation.REQUIRED)

u 注意事項:

v 注意配置的Filter順序!該Filter須要在Struts控制器Filter以前配置才能生效!!

v 當配置OpenSessionInViewFilter後,Spring容器中SessionFactory組件的id值必須爲sessionFactory,不能再隨便起了。

v 當配置OpenSessionInViewFilter後,會默認將session操做置成readOnly狀態,此時須要添加AOP事務才能執行增、刪、改,不然報錯(查詢沒問題)。

v 因爲Hibernate筆記5.16案例中用到了5.17節4)step2的自定義攔截器OpenSessionInViewInterceptor,並按step3修改了struts-cost.xml,此時會有衝突:資費模塊的權限驗證會失效(無權限的也能進入)。當項目爲SSH結構時,用Spring提供的OpenSessionInViewFilter就好了,以前定義的OpenSessionInViewInterceptor不用了!把struts-cost.xml中自定義的攔截器的聲明、引用、默認全局都刪掉!包cost繼承netctoss-default,權限驗證便可生效。其餘配置也都繼承netctoss-default(以前爲了方便測試因此都繼承了json-default)。

9、
整合開發包struts-spring-plugin.jar

9.1 Struts2建立對象的方式

Struts2-->ObjectFactory-->StrutsObjectFactory

9.2 struts-spring-pligin.jar建立對象的方式

在它的包中有個StrutsSpringObjectFactory,而它的配置文件struts-pligin.xml,則把Struts2中的ObjectFactory指定成了StrutsSpringObjectFactory,這樣一來,當Struts2請求過來時,Action對象將交給整合包去Spring容器獲取,即Struts2再也不產生Action對象,可在該包的struts-plugin.xml中查看。

<bean type="com.opensymphony.xwork2.ObjectFactory" name="spring"

class="org.apache.struts2.spring.StrutsSpringObjectFactory" />

    <constant name="struts.objectFactory" value="spring" />

9.3 struts-spring-plugin.jar的內部實現

經過它的內部代碼,能夠發現該整合開發包獲取Bean對象時,有兩種方式:

try{//第一種 見原理圖1

Object action = ac.getBean(class屬性);//id值

}catch(){//第二種  見原理圖2

Class class = Class.forName(class屬性);//step1:包名.類名,利用反射機制生成Action

Object action = class.newInstance();

//step2:而後自動注入,注入規則見9.7節!

}

9.4原理圖1

wps_clip_image-14228

9.5原理圖2

wps_clip_image-25890

9.6注意事項

第一種方式:建立Action由Spring容器負責。

第二種方式:建立Action由插件負責,與第一種相比Action脫離了Spring容器,因此不能用AOP機制了!也不能用事務管理了!因此,爲了使Action只負責調用而不涉及業務邏輯,開發中通常會有個Service組件,把業務邏輯、AOP機制、事務管理都給Service組件。

9.7注入規則

9.3節中catch塊的注入規則:

1)組件中,不加@Resource(默認)是按名稱匹配,即屬性名和id值一致才能夠。

2)組件中,若是添加了@Resource,則按類型匹配。

10、
Spring的事務管理

Spring提供如下兩種方式管理事務。

10.1聲明式事務管理(基於配置方式實現事務控制)

1)以8.9案例爲例,在applicationContext.xml配置文件中使用xml方式配置事務:

<!--事務管理配置-->

<!--定義事務管理Bean(用於管理事務),不用咱們寫了,直接用Spring提供的類-->

<bean id="txManager"

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

<!-- 注入session資源 -->

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

</bean>

<!-- 定義方面和通知,直接使用Spring提供的命名空間:xmlns:tx=http://.../schema/tx -->

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

<!--能夠指定目標對象中不一樣方法採用不一樣的事務管理機制。注意:name要根據目標對象中的方法名寫。read-only="true":只讀事務,只能查詢。propagation="REQUIRED"爲默認值其餘可取的值見10.3節 -->

<tx:attributes> <!--註釋中的是給DAO添加事務,但很差!沒註釋的是給Action加-->

<!-- <tx:method name="save" propagation="REQUIRED"/>

<tx:method name="update" propagation="REQUIRED"/>

<tx:method name="delete" propagation="REQUIRED"/>

    <tx:method name="find*" read-only="true" propagation="REQUIRED"/>

<tx:method name="get*" read-only="true" propagation="REQUIRED"/>

<tx:method name="*" propagation="REQUIRED"/>  -->

<!-- action中execute方法都採起REQUIRED,而REQUIRED把默認的只讀操做模式改了,此時可增、刪、改、查。因爲NetCTOSS項目沒加Service組件,因此把事務加到Action上,不然就是給Service加的。而不加在DAO中是由於有些操做可能要執行多個DAO共同完成一項功能,若是加在DAO中,那麼裏面的一個方法就是一個事務(會提交),那麼當該功能中途出現問題,則以前的操做將沒法回滾了! -->

<tx:method name="execute" propagation="REQUIRED"/>

<!--其餘沒有考慮到的按默認的事務管理機制值-->

<tx:method name="*" propagation="REQUIRED"/>

</tx:attributes>

</tx:advice>

<!--定義切入點,AOP切入--><!--proxy-target-class="true":表示強制採用CGLIB方式生成代理類,若不寫則容器會根據是否有接口,而去選擇用哪一種技術產生代理類,但這樣就會有問題!如:Action繼承BaseAction,而BaseAction實現某個接口,那麼容器選擇的代理類技術而產生的代理Action是沒有原來Action中的方法的!如果做用在DAO上,則可不寫 -->

<aop:config proxy-target-class="true"><!-- 類型匹配 -->

<aop:pointcut id="actionPointcut" expression="within(com.tarena.netctoss.action..*)" />

<!-- 將切入點和通知結合 -->

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

</aop:config>

2)以8.9案例爲例,在applicationContext.xml配置文件中使用註解方式配置:

step1:定義HibernateTransactionManager(事務管理)Bean組件

<bean id="txManager"

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

<!-- 注入session資源 -->

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

</bean>

step2:開啓事務的註解配置

<!-- 開啓事務註解配置 --> <!-- 指明讓txManager來管理事務 -->

<tx:annotation-driven proxy-target-class="true" transaction-manager="txManager"/>

step3:而後在業務組件的類定義前或方法中使用@Transactional註解便可,例如:

①AddCostAction,在類定義前使用(對類中除了get/set方法外的全部方法,都使用相同的事務管理)

@Transactional(propagation=Propagation.REQUIRED)

public class AddCostAction extends BaseAction{  ……  }

②DeleteCostAction,在方法前使用

@Transactional(propagation=Propagation.REQUIRED)

public String execute() throws DAOException{ ……  }

③UpdateCostAction,在類定義前使用

@Transactional(propagation=Propagation.REQUIRED)

public class UpdateCostAction extends BaseAction { ……  }

④ListCostAction,在方法前使用

@Transactional(readOnly=true,propagation=Propagation.REQUIRED)

public String execute() throws Exception { ……  }

u 注意事項:若是將Action看成目標,須要在<tx:annotation-driven>添加proxy-target-class="true"屬性,表示無論有沒有接口,都採用CGLIB方式生成代理類。

10.2編程式事務管理(基於Java編程實現事務控制),不推薦用!

主要是利用transactionTemplate的execute()方法,以回調方式將多個操做封裝在一個事務中。

10.3 Spring中經常使用的事務類型

1)REQUIRED:支持當前事務,若是當前沒有事務,就新建一個事務。這是最多見的選擇,也是默認值。

2)SUPPORTS:支持當前事務,若是當前沒有事務,就以非事務方式執行。

3)MANDATORY:支持當前事務,若是當前沒有事務,就拋出異常。

4)REQUIRES_NEW:新建事務,若是當前存在事務,把當前事務掛起。

5)NOT_SUPPORTED:以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。

6)NEVER:以非事務方式執行,若是當前存在事務,則拋出異常。

7)NESTED:若是當前存在事務,則在嵌套事務內執行。若是當前沒有事務,則進行與REQUIRED相似的操做。擁有多個能夠回滾的保存點,內部回滾不會對外部事務產生影響。只對DataSourceTransactionManager有效。

11、
Spring的MVC

11.1 Spring MVC的體系結構

1)控制器(兩種):①DispatcherServlet(等價於Struts2中的Filter)

  ②Controller(等價於Struts2中的Action)

2)映射處理器:HandlerMapping(完成請求和Controller之間的調用,等價於Struts2中的ActionMapping)

3)模型視圖組件:ModelAndView(封裝了模型數據和視圖標識)

4)視圖解析器:ViewResolver(等價於Struts2中的Result)

5)視圖組件:主要用JSP

wps_clip_image-23349

11.2 Spring MVC的工做流程

1)客戶端發送請求,請求到達DispatcherServlet主控制器。

2)DispatcherServlet控制器調用HandlerMapping處理。

3)HandlerMapping負責維護請求和Controller組件對應關係。HandlerMapping根據請求調用對應的Controller組件處理。

4)執行Controller組件的業務處理,須要訪問數據庫,能夠調用DAO等組件。

5)Controller業務方法處理完畢後,會返回一個ModelAndView對象。該組件封裝了模型數據和視圖標識。

6)Servlet主控制器調用ViewResolver組件,根據ModelAndView信息處理。定位視圖資源,生成視圖響應信息。

7)控制器將響應信息給用戶輸出。

11.3案例:簡易登陸(基於XML配置,不推薦使用)

因爲此案例沒有用到JDBC、Hibernate等訪問數據庫的技術,因此AOP包能夠不用導入!

step1:導入spring-webmvc.jar包

step2:導入Spring的IoC開發包(spring.jar、commons-logging.jar)

step3:配置web.xml

<servlet>

<servlet-name>springmvc</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

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

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

</init-param><!-- 讓容器從指定的src目錄下查找applicationContext.xml文件-->

</servlet>

<servlet-mapping>

<servlet-name>springmvc</servlet-name>

<url-pattern>*.do</url-pattern><!-- 不能寫/*了,影響太廣,自定義一種請求形式 -->

</servlet-mapping>

step4:在/WEB-INF/jsp中新建login.jsp和ok.jsp

1)login.jsp

<h1>Spring MVC 登陸</h1><font size="5" color="red">${error }</font>

<form action="login.do" method="post">

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

密碼:<input type="password" name="password" /><br/>

<input type="submit" value="登陸"/></form>

2)ok.jsp

<h1>歡迎${user },登陸成功!</h1>

step5:在WebRoot下新建index.jsp

<a href="toLogin.do">spring mvc(xml)</a>

step6:在org.tarena.controller包中新建ToLoginController類,並實現Controller接口

public class ToLoginController implements Controller {//必須實現Controller,並重寫方法

@Override //記得改改參數名字

public ModelAndView handleRequest(HttpServletRequest request,

HttpServletResponse response) throws Exception {//默認執行的業務方法

ModelAndView mv=new ModelAndView("login");//調用login.jsp,指定視圖名稱

return mv; } }

step7:在org.tarena.controller包中新建LoginController類,並實現Controller接口

public class LoginController implements Controller  {//必須實現Controller,並重寫方法

@Override //記得改改參數名字

public ModelAndView handleRequest(HttpServletRequest request,

HttpServletResponse response) throws Exception {//默認執行的業務方法

String name=request.getParameter("username");

String pwd=request.getParameter("password");

Map<String,Object> map=new HashMap<String, Object>();

if("chang".equals(name) && "123".equals(pwd)){//簡單模擬,不訪問數據庫了

map.put("user", name);   return new ModelAndView("ok",map); }

map.put("error", "用戶名或密碼錯誤");

return new ModelAndView("login",map);//指定視圖名稱 } }

step8:新建applicationContext.xml文件,並進行配置

<!-- 定義handlermapping,即定義請求和Controller的映射信息 -->

<bean id="handlerMapping"

class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="toLogin.do">toLoginController</prop>

<prop key="login.do">loginController</prop>

</props>

</property>

</bean>

<!-- 定義視圖解析器,負責根據ModelAndView信息調用View組件 -->

<bean id="viewResolver"

class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"></property><!-- 聲明前綴 -->

<!-- 因返回的ModelAndView對象僅有個名字,因此要定義先後綴 -->

<property name="suffix" value=".jsp"></property><!-- 聲明後綴 -->

</bean>

<!-- 定義Controller -->

<bean id="toLoginController" class="org.tarena.controller.ToLoginController"></bean>

<bean id="loginController" class="org.tarena.controller.LoginController"></bean>

step9:部署,測試

11.4案例:修改11.3案例(基於註解配置,推薦使用)

因爲此案例沒有用到JDBC、Hibernate等訪問數據庫的技術,因此AOP包能夠不用導入!

step1:導入spring-webmvc.jar包

step2:導入Spring的IoC開發包(spring.jar、commons-logging.jar)

step3:配置web.xml,與9.3案例step3相同

step4:在/WEB-INF/jsp中新建login.jsp和ok.jsp

1)login.jsp

<h1>Spring MVC 登陸</h1><font size="5" color="red">${error }</font>

<!-- 模擬請求有多級,即user/login.do,action寫user/login.do爲相對路徑,出現疊加問題;寫絕對路徑須要加「/」,同時也要寫應用名。詳細說明見10.2節-->

<form action="/Spring06_MVC2/user/login.do" method="post">

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

密碼:<input type="password" name="password" /><br/>

<input type="submit" value="登陸"/></form>

2)ok.jsp,與9.3案例step4中2)相同

step5:在WebRoot下新建index.jsp

<a href="toLogin.do">spring mvc(annotation)</a>

step6:新建applicationContext.xml文件,並進行配置

<!-- 開啓組件掃描 -->

<context:component-scan base-package="org.tarena" />

<!-- 定義映射處理器,採用註解AnnotationMethodHandlerAdapter指定映射 -->

<bean id="annotationMapping"

class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">

</bean>

<!-- 定義視圖解析器,負責根據ModelAndView信息調用View組件(JSP)-->

<bean id="viewResolver"

class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/jsp/"></property><!-- 聲明一個前綴 -->

<property name="suffix" value=".jsp"></property><!-- 聲明一個後綴 -->

</bean>

step7:在org.tarena.controller包中新建ToLoginController類,並使用註解

@Controller //將組件掃描到Spring容器

@Scope("prototype")

public class ToLoginController {//不用再實現接口了,就是寫個普通的類

@RequestMapping(value="/toLogin.do",method=RequestMethod.GET)

public String execute(){//寫個最簡單的方法,返回類型也可寫ModelAndView

return "login";//返回視圖名稱 } }

step8:在org.tarena.controller包中新建LoginController類,並使用註解

@Controller //將組件掃描到Spring容器

@Scope("prototype")

@RequestMapping("/user/*")//當請求有多級,即有共同前綴,可寫類定義前

public class LoginController { //不用去實現Controller接口了,就是寫個普通的類

//@RequestMapping(value="/login.do",method=RequestMethod.POST)//沒有共同前綴

@RequestMapping(value="login.do",method=RequestMethod.POST)//有共同前綴

public String execute(User user,Model model){//Model是能夠傳到下一個頁面的

Map<String,Object> map=new HashMap<String, Object>();

if("chang".equals(user.getUsername()) && "123".equals(user.getPassword())){

map.put("user", user.getUsername());

model.addAttribute("user",user.getUsername()); return "ok"; }

model.addAttribute("error", "用戶名或密碼錯誤");

return "login"; } }

u 注意事項:

v @RequestMapping註解:value屬性指定請求;method屬性指定請求提交方式。

v Controller中業務方法能夠定義成如下格式:

1)public String f1(){}

2)public ModelAndView f1(){}

3)public String f1(HttpServletRequest request){}//須要request就加上,不須要可不寫

4)public String f1(HttpServletRequest request,HttpServletResponse response){}

5)public String f1(User user){} //自定義實體類,屬性與表單中的提交名一致
6)public String f1(Model model){} //org.springframework.ui.Model中的

7)public String f1(User user,Model model){}

step9:step8中execute方法用到了實體User,因此在org.tarena.entity包下建立User實體

private String username;   private String password;//和表單提交名同樣  ……get/set方法

step10:部署,測試

12、
其餘注意事項

12.1 Spring的核心模塊

wps_clip_image-23796

12.2表單中action屬性的相對、絕對路徑問題

例如9.4案例step4中的login.jsp,當請求有多級時(項目中不須要建立什麼user目錄,僅是經過請求實現的分類而已):

<form>標籤中的action屬性,若是寫「user/login.do」,即相對路徑,會出現疊加問題:

<form action="user/login.do" method="post">

由於它相對的是當前請求地址,好比當前請求頁面地址爲:

http://localhost:8080/Spring06_MVC2/user/login.do

那麼,第一次向user/login.do發請求沒問題,但第二次發請求則出現疊加問題!(點兩次登陸就能出現該問題)

http://localhost:8080/Spring06_MVC2/user/user/login.do

即,第二次請求把後面的login.do又替換爲了user/login.do,而以前的user仍在,則出現疊加問題。

解決方式:寫絕對路徑,但要加「/」,同時也要寫應用名,即:

<form action="/Spring06_MVC2/user/login.do" method="post">

12.3用SSH重構NetCTOSS項目模塊的步驟

1)瞭解原功能的處理流程,例如資費模塊:/cost/add.action-->AddCostAction.execute

-->CostDAO.save-->list.action

2)重構CostDAO(Spring+Hibernate)

①追加Spring開發包和配置文件。

②追加Cost類(已存在)和Cost.hbm.xml。

③在Spring的sessionFactory中加載hbm.xml。

④基於HibernateDaoSupport和HibernateTemplate編寫CostDAO實現組件。

3)CostDAO的Spring配置

①將CostDAO掃描到Spring容器。

②將容器中dataSource注入到sessionFactory,而後sessionFactory注入到DAO組件。

③測試DAO。

4)修改AddCostAction

①將Action掃描到Spring容器。

②採用注入方式使用DAO組件對象。

③在業務方法上定義@Transactional事務註解。

5)修改AddCostAction的struts配置

①將class屬性改爲與掃描到Spring容器後的Action組件id值。

6)檢查共通的操做是否完成

①是否引入struts-spring-plugin.jar。

②是否在web.xml中添加ContextLoaderListener。

③是否開啓了組件掃描配置。

④是否開啓了註解事務配置。

⑤是否配置dataSource、sessionFactory。

相關文章
相關標籤/搜索