Spring學習筆記html
Java相關課程系列筆記之十五前端
筆記內容說明java
Spring(梁建全老師主講,佔筆記內容100%);node
目 錄web
1、 Spring概述 1spring
1.3 Spring框架的容器 1express
2、 Spring容器的基本應用 2apache
2.6案例:Spring框架的使用以及2.1節-2.5節整合測試 3
3.5案例:不用JDBC訪問數據庫,而是採用Hibernate訪問 6
5.4案例:AOP的使用,模擬某些組件須要記錄日誌的功能 11
5.7案例:環繞通知,修改5.4案例使之動態顯示所執行的操做 12
5.8案例:利用AOP實現異常處理,將異常信息寫入文件 13
8.6 Spring框架如何使用Hibernate技術 22
8.7 Spring+Hibernate如何使用Session、Query等對象 25
9、 整合開發包struts-spring-plugin.jar 31
9.2 struts-spring-pligin.jar建立對象的方式 31
9.3 struts-spring-plugin.jar的內部實現 31
10.2編程式事務管理(基於Java編程實現事務控制),不推薦用! 34
11.4案例:修改11.3案例(基於註解配置,推薦使用) 37
咱們學習Spring框架的最終目的是用它整合Struts二、Hibernate框架(SSH)。
Spring框架主要負責技術整合(能夠整合不少技術),該框架提供IoC和AOP機制,基於這些特性整合,能夠下降系統組件之間的耦合度,便於系統組件的維護、擴展和替換。
其實與Spring框架的做用相同:
在SSH中,主要是利用Spring容器管理咱們程序中的Action、DAO等組件,經過容器的IoC機制,能夠下降Action、DAO之間的耦合度(關聯度),利用AOP進行事務管理等共通部分的處理。
在SSH中,Struts2主要是利用它的控制器,而不是標籤、表達式;Hibernate主要利用它的數據庫訪問;Spring主要是利用它的整合。
Spring框架的核心是提供了一個容器(是咱們抽象出來的,代指後面的類型)。該容器類型是BeanFactory或ApplicationContext(建議用這個類型,它是BeanFactory的子類,功能更多)。
該容器具備如下功能:
1)容器能夠建立和銷燬組件對象,等價於原來「工廠」類的做用。
2)容器能夠採用不一樣的模式建立對象,如單例模式建立對象。
3)容器具備IoC機制實現。
4)容器具備AOP機制實現。
1)Bean組件其實就是個普通的Java類!
2)方法:在applicationContext.xml中添加如下定義,見2.6案例中step4。
<bean id="標識符" class="Bean組件類型"></bean>
1)實例化容器:
ApplicationContext ac=new ClassPathXmlApplicationContext("/applicationContext.xml");
//FileSystemXmlApplicationContext("");//去指定的磁盤目錄找,上面的爲去Class路徑找
2)利用getBean("標識符")方法獲取容器中的Bean對象。見2.6案例中step5。
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模式。
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>
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。
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默認爲單例模式 }
1)Inverse of Controller被稱爲控制反轉或反向控制,其實真正體現的是「控制轉移」。
2)所謂的控制指的是負責對象關係的指定、對象建立、初始化和銷燬等邏輯。
3)IoC指的是將控制邏輯交給第三方框架或容器負責(即把Action中的控制邏輯提出來,交給第三方負責),當兩個組件關係發生改變時,只須要修改框架或容器的配置便可。
4)IoC主要解決的是兩個組件對象調用問題,能夠以低耦合方式創建使用關係。
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>
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技術實現保存資費記錄。
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.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技術實現保存資費記錄
Spring能夠爲對象注入如下類型的數據。
<property name="屬性名" ref="要注入的Bean對象的id值"></property>
1)字符串、數字
<property name="屬性名" value="要注入的值"></property>
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>
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(); }
Aspect Oriented Programming,被稱爲面向方面編程。對單個對象(一對一)的解耦用IOC,而當有個共通組件,它對應多個其餘組件(一對多),則解耦用AOP。如,攔截器。這也是爲什麼在程序中大量的用IoC,而AOP卻用的不多,由於程序中不可能有不少的共通部分。
OOP是面向對象編程,AOP是以OOP爲基礎的。
OOP主要關注的是對象,如何抽象和封裝對象。
AOP主要關注的是方面,方面組件能夠以低耦合的方式切入到(做用到)其餘某一批目標對象方法中(相似於Struts2中的攔截器)。
AOP主要解決共通處理和目標組件之間解耦。
1)方面(Aspect):指的是封裝了共通處理的功能組件。該組件能夠做用到某一批目標組件的方法上。
2)鏈接點(JoinPoint):指的是方面組件和具體的哪個目標組件的方法有關係。
3)切入點(Pointcut):用於指定目標組件的表達式。指的是方面組件和哪一批目標組件方法有關係。多個鏈接點組成的集合就是切入點。如:a、b爲切入點,一、2爲鏈接點。
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 代理類();//代理類去實現了原目標接口,因此沒有原來類的方法
接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
通知決定方面組件和目標組件做用的關係。主要有如下幾種類型通知:
1)前置通知:方面組件在目標方法以前執行。
2)後置通知:方面組件在目標方法以後執行,目標方法沒有拋出異常才執行方面組件。
3)最終通知:方面組件在目標方法以後執行,目標方法有沒有異常都會執行方面組件。
4)異常通知:方面組件在目標方法拋出異常後才執行。
5)環繞通知:方面組件在目標方法以前和以後執行。
try{ //前置通知執行時機<aop:before>
//執行目標方法
//後置通知執行時機<aop:after-returning>
}catch(Exception e){//異常通知執行時機<aop:after-throwing>
}finally{ //最終通知執行時機<aop:after>
}//環繞通知等價於前置+後置<aop:around>
切入點用於指定目標組件和方法,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 注意事項:上述表達式可使用&&、| | 運算符鏈接使用。
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
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
Log4j主要用於日誌信息的輸出。能夠將信息分級別(錯誤、嚴重、警告、調式信息)按不一樣方式(控制檯、文件、數據庫)和格式輸出。
Log4j主要有如下3部分組件構成:
1)日誌器(Logger):負責消息輸出,提供了各類不一樣級別的輸出方法。
2)輸出器(Appender):負責控制消息輸出的方式,例如輸出到控制檯、文件輸出等。
3)佈局器(格式器,Layout):負責控制消息的輸出格式。
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信息。
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顯示結果:
註解技術從JDK5.0推出,以後不少框架開始提供註解配置形式。Spring框架從2.5版本開始支持註解配置。註解配置的優勢:簡單、快捷。
Spring能夠按指定的包路徑掃描內部的組件,當發現組件類定義前有一下的註解標記,會將該組件歸入Spring容器中。
1)@Component(其餘組件)
2)@Controller(Action組件,負責調Service)
3)@Service(Service組件,負責調DAO,處理一些額外邏輯)
4)@Repository(DAO組件,負責訪問數據庫)
u 注意事項:
v 括號中的爲推薦用法,上述4個註解任意用也能夠,但不符合規範。
v 註解只能用在類定義前、方法定義前、成員變量定義前!
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爲類名首字母小寫!且默認單例模式 }
若是容器中兩個符合要求可被注入同一個組件的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
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文件。
1)SQLException是JDBC拋的異常。
2)org.hibernate.XXXException是Hibernate拋出的異常。
3)Spring提供的異常根類:DataAccessException,可是不少異常都被try-catch了,因此出錯後看不到提示,所以要用Log4j。
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類型的參數。
基於AOP配置實現,不須要寫Java代碼,加註解標籤便可。詳情見第十章。
之前的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{ …… }
1)加強數據訪問的穩定性。
2)鏈接池能夠將鏈接數控制在安全的範圍內。
3)鏈接池中的鏈接對象始終與數據庫保持聯通狀態,它的close方法被重寫,不是真正的關閉,而是把鏈接又放回池中,避免了重複的新建鏈接和釋放鏈接過程。
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()); } }
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
}
}
);
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進行測試
修改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)。
Struts2-->ObjectFactory-->StrutsObjectFactory
在它的包中有個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" />
經過它的內部代碼,能夠發現該整合開發包獲取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節!
}
第一種方式:建立Action由Spring容器負責。
第二種方式:建立Action由插件負責,與第一種相比Action脫離了Spring容器,因此不能用AOP機制了!也不能用事務管理了!因此,爲了使Action只負責調用而不涉及業務邏輯,開發中通常會有個Service組件,把業務邏輯、AOP機制、事務管理都給Service組件。
9.3節中catch塊的注入規則:
1)組件中,不加@Resource(默認)是按名稱匹配,即屬性名和id值一致才能夠。
2)組件中,若是添加了@Resource,則按類型匹配。
Spring提供如下兩種方式管理事務。
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方式生成代理類。
主要是利用transactionTemplate的execute()方法,以回調方式將多個操做封裝在一個事務中。
1)REQUIRED:支持當前事務,若是當前沒有事務,就新建一個事務。這是最多見的選擇,也是默認值。
2)SUPPORTS:支持當前事務,若是當前沒有事務,就以非事務方式執行。
3)MANDATORY:支持當前事務,若是當前沒有事務,就拋出異常。
4)REQUIRES_NEW:新建事務,若是當前存在事務,把當前事務掛起。
5)NOT_SUPPORTED:以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。
6)NEVER:以非事務方式執行,若是當前存在事務,則拋出異常。
7)NESTED:若是當前存在事務,則在嵌套事務內執行。若是當前沒有事務,則進行與REQUIRED相似的操做。擁有多個能夠回滾的保存點,內部回滾不會對外部事務產生影響。只對DataSourceTransactionManager有效。
1)控制器(兩種):①DispatcherServlet(等價於Struts2中的Filter)
②Controller(等價於Struts2中的Action)
2)映射處理器:HandlerMapping(完成請求和Controller之間的調用,等價於Struts2中的ActionMapping)
3)模型視圖組件:ModelAndView(封裝了模型數據和視圖標識)
4)視圖解析器:ViewResolver(等價於Struts2中的Result)
5)視圖組件:主要用JSP
1)客戶端發送請求,請求到達DispatcherServlet主控制器。
2)DispatcherServlet控制器調用HandlerMapping處理。
3)HandlerMapping負責維護請求和Controller組件對應關係。HandlerMapping根據請求調用對應的Controller組件處理。
4)執行Controller組件的業務處理,須要訪問數據庫,能夠調用DAO等組件。
5)Controller業務方法處理完畢後,會返回一個ModelAndView對象。該組件封裝了模型數據和視圖標識。
6)Servlet主控制器調用ViewResolver組件,根據ModelAndView信息處理。定位視圖資源,生成視圖響應信息。
7)控制器將響應信息給用戶輸出。
因爲此案例沒有用到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:部署,測試
因爲此案例沒有用到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:部署,測試
例如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">
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。