一、理論知識
1. 依賴注入、控制反轉
依賴注入:在運行期,由外部容器動態地將依賴對象注入到組件中
控制反轉:應用本身不負責依賴對象的創建及維護,依賴對象的創建及維護是由外部窗口負責得。這樣控制權就由應用轉移到了外部容器,控制權的轉移就是所謂的反轉。
2.spring 的主要特性。
(1 )降低組件之間的耦合度,實現軟件各層之間的解耦。
(2 )可以使用容器提供的衆多服務,如:事務管理服務、消息服務、JMS 服務、持久化服務等等。
(3 )容器提供單例模式支持,開發人員不再需要自己編寫實現代碼。
(4 )容器提供了AOP 技術,利用它很容易實現如權限攔截,運行期監控等功能。
(5 )容器提供的衆多輔作類,使用這些類能夠加快應用的開發,如:JdbcTemplate 、HibernateTemplate.
(6 )對主流的應用框架提供了集成支持。
3. 常用技術
控制反轉/ 依賴注入--- 面向切入編程--- 與主流框架的整合、管理---
二、基本實例
1. 準備搭建環境
dist/spring.jar
lib/jakata-commons/commons-loggin.jar
如果使用了切面編程,還需下列jar 文件:
lib/aspectj/aspectjweaver.jar 和aspectjrt.jar
lib/cglib/cglib-nodep-2.1.3.jar
如果使用了jsr-250 中的註解,還需要下列jar 文件:
lib/j2ee/common-annotations.jar
2. 搭建並測試環境
建立名爲spring_01_base
項目 ,根據需求導入jar 包。建立一個Junit 測試單元SpringEnvTest ,測試代碼如下:
@Test
public void testEnv() {
ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );
}
beans.xml 配置文件在此省略(見下)。運行此測試如無錯,則說明環境搭建成功。
說明: beans.xml
可以在類路徑下進行配置,也可以在具體的目錄下配置。可以是一個配置文件,也可以是多個配置文件組成
String 數組傳入。
3. 實例
作如下準備工作:(1 )建立UseDao 接口,代碼如下:
package com.asm.dao;
public interface UserDao {
void save();
}
(2 )建立UserDao 接口的實現類,UserDaoImpl
package com.asm.dao.impl;
import com.asm.dao.UserDao;
public class UserDaoImpl implements UserDao{
public void save() {
System. out .println( " 執行 save 方法 ..." );
}
}
(3 )在src 目錄下配置此beans.xml ,它的配置如下:
<? xml version
= "1.0" encoding
= "UTF-8"
?>
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" >
< bean id = "userDaoImpl" class = "com.asm.dao.impl.UserDaoImpl" />
</ beans
>
說明:bean 代表一個實質的java 類,通過它的id 可以獲取一個此類的一個對象。
補充 :讓xml 配置文件在編譯時提示
[windows][preferences][myeclipse][files and editors][xml][xml catalog] 點add ,在出現窗口的location 中選“file system ”, 然後在spring 解壓目錄的dist/resources 目錄中選擇“spring-beans-2.5.xsd ”, 並將key Type 值改爲“Schema Location ”,key 值爲:http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
(4 )Junit 測試單元SpringEnvTest 中增加如下代碼測試:
@Test
public void base() {
ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );
UserDao userDao = (UserDao) ctx.getBean( "userDaoImpl" );
userDao.save();
}
以上的代碼就是通過配置文件 beans.xml
獲取所需要的實例對象。
4. 三種bean 的生成方式
除了上面使用的類直接生成方式,還有bean 靜態工廠及bean 實例工廠。
bean 靜態工廠的配置如下:
< bean id
= "userDaoImpl2"
class =
"com.asm.dao.impl.UserDaoImplFactory" factory-method
= "getUserDaoImpl"
/>
相應的工廠類代碼如下:
package com.asm.dao.impl;
public class UserDaoImplFactory {
public static UserDaoImpl getUserDaoImpl(){
return new UserDaoImpl();
}
}
bean 實例工廠的配置如下:
< bean id
= "factory" class
= "com.asm.dao.impl.UserDaoImplFactory2"
/>
< bean id = "userDaoImpl3" factory-bean = "factory" factory-method = "getUserDaoImpl" />
相應的工廠類的代碼如下:
package com.asm.dao.impl;
public class UserDaoImplFactory2 {
public UserDaoImpl getUserDaoImpl() {
return new UserDaoImpl();
}
}
5.bean 的作用域
singleton: 返回bean 的同一個實例,也是默認的作用域(無狀態bean 使用此作用域)
prototype: 每次請求都會創建一個實例(有狀態bean 使用此作用域)
request 、session 、global session 這三個作用域主要用在web 應用中
6.bean 的生命週期
(1) 什麼時候初始化bean 實例
當scope =singleton ,即默認情況,會在裝載配置文件時實例化。如果希望在調用getBean 時才初始化,可以使用lazy-init="true" 補充:如果希望希望該配置文件中的所有bean 都延遲初始化,則應在beans 根結點中使用lazy-init="true" 。
當scope=prototype 時 ,在調用getBean() 方法時纔會初始化。
(2 )生命週期:
構造器、init 方法、獲取bean 後的操作、destroy 方法(ctx.close 、注意 如果bean 的scope 設爲prototype 時,當ctx.close 時,destroy 方法不會被調用)
7. 屬性注入Setter 方式
(1 )簡單屬性(如String ):
< bean id
= "userServiceBean"
class =
"com.asm.service.UserServiceBean" >
< property name = "id" value = "10" ></ property >
< property name = "username" value = " 張某某 " ></ property >
</ bean
>
(2) 對象屬性- 外部bean 注入:在上面的<bean> 中增加如下配置:
< property
name =
"userDao" ref
= "userDaoImpl"
/>
對象屬性- 內部bean 注入:在上面的<bean> 中增加如下配置:
< property name
= "userDao"
>
< bean id = "temp" class = "com.asm.dao.impl.UserDaoImpl" />
</ property >
(3) 集合屬性注入:
<
property name
= "list" >
< list >
< value > List 值一 </ value >
< value > List 值二 </ value >
< value > List 值三 </ value >
</ list >
</ property >
< property name = "set" >
< set >
< value > Set 值二 </ value >
< value > Set 值一 </ value >
< value > Set 值三 </ value >
</ set >
</ property >
< property name = "map" >
< map >
< entry key = "one" value = " 一 " />
< entry key = "two" value = " 二 " />
< entry key = "three" value = " 三 " />
</ map >
</
property >
< property name = "pro" >
< props >
< prop key = "p1" > first </ prop >
< prop key = "p2" > second </ prop >
< prop key = "p3" > third </ prop >
</ props >
</ property >
注意: 在相應的字段上一定要有setter 方法, 才能注入。
補充 :使用繼承。在beans.xml 中的配置如下:
< bean abstract = "true" id = "parent" >
< property name = "username" value = " 張某某 " ></ property >
</ bean >
< bean id = "XXX" class = "com.asm.vo.User" parent = "parent" >
< property name = "password" value = "123456" ></ property >
</ bean >
相當於在 XXX bean 實例中也有 username 屬性設置。
8. 屬性注入構造器方式
< bean id = "userServiceBean2" class = "com.asm.service.UserServiceBean" >
< constructor-arg index = "0" value = " 李某某 " />
< constructor-arg index = "1" ref = "userDaoImpl" />
< constructor-arg index = "2" >
< list >
< value > List 值一 </ value >
< value > List 值二 </ value >
< value > List 值三 </ value >
</ list >
</ constructor-arg >
</ bean >
UserServiceBean 對應的構造方法代碼如下:
public UserServiceBean(String username, UserDao userDao, Set<String> set) {
this . username =username;
this . userDao =userDao;
this . set =set;
}
注意 :此方法會覆蓋掉默認的構造方法,導致要依賴默認構造方法的配置不可用,因此我們還應爲此類提供一個默認的構造器。
三、使用註解方式注入
1. 準備
註解方式的注入主要針對對象屬性的注入。
使用註解功能要引用註解包,另beans.xml 的配置模板如下:
<? xml version
= "1.0" encoding
= "UTF-8"
?>
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context = "http://www.springframework.org/schema/context"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" >
< context:annotation-config /> <!-- 開啓註解功能 -->
</ beans >
2.Resource 註解實例
拷貝上一個項目爲spring_02_annotation 項目 ,修改UserServiceBean 爲如下形式:
package com.asm.service;
public class UserServiceBean {
@Resource (name = "userDaoImpl" )
private UserDao userDao ;
private UserDao userDao2 ;
@Resource
public void setUserDao2(UserDao userDao2) {
this . userDao2 = userDao2;
}
public void test() {
userDao .save();
userDao2 .save();
}
}
然後在bean.xml 中的配置如下:
< bean id = "userDaoImpl" class = "com.asm.dao.impl.UserDaoImpl" />
< bean
id =
"userServiceBean" class
= "com.asm.service.UserServiceBean" />
簡要說明 :Resouce 註解可以在字段上標記,也可以在對應的setter 方法上標記。此註解可以不使用name 屬性,它會自動去查找匹配的類型(先以字段名稱爲name 的值查找,如找不到會再根據依賴對象的類型查找)。但只要使用了name 屬性,就應確保name 的值在xml 中有相應的bean Id 對應。它是屬於java 本身的註解,
Resource
默認按屬性名稱裝配
3.Autowired 註解實例
@Autowired (required= false )
@Qualifier ( "userDaoImplXXX" )
private UserDao
userDao3 ;
說明 :Autowired 默認是按照類型來查找對應的bean 實例注入,如果想注入指定名稱的bean 實例,可以使用Qualifier 註解來指定名字。Required 屬性設爲true 時,如果不能成功注入則會報告異常,如果爲設爲false 而不能成功注入,則會將userDao3 設爲null 。同樣地,它也實用於setter
方法。它屬於spring 特有的註解,Autowired 默認按類型裝配。
4. 自動裝配
自動裝配(瞭解,不建議使用):除了要設置字段的setter 方法外,還應在beans.xml 配置文件中設置如下內容:
< bean id
= "userServiceBean2"
class
= "com.asm.service.UserServiceBean2"
autowire =
"byType" />
說明:除了byType 外,autowire 的可選屬性如下:
byName :根據類中的字段名來查找對應的bean, 如不能成功注入,則字段設爲null.
byType :根據類型裝配,如果發現多個類型都能夠匹配,則拋出異常。
Consturctor :也byType 相似,不同之處在於它應用於構造器的參數,如果容器中沒有找到與構造器參數類型一致的bean, 則拋出異常。
Autodetect :通過bean 類的自省機制來決定是使用consturctor 還是byType 方式進行自動裝配。如果發現默認的構造器,那麼將使用byType 方式。
四、自動掃描管理 bean
1. 準備工作
在前面使用註解的時候,除了<beans> 結點配置增加了名稱空間說明,另還增加了 < context:annotation-config /> 配置,它的作用就是註冊一個處理器。通常情況下,我們要使用某個bean 實例,總會配置<bean> 相關內容。Spring 最新版本可以簡化這一操作,即是說我們只要在配置文件作如下設置:
< context:component-scan
base-package
= " 包名
" /> 便可以自動管理指定包名及子包下標住了@service (業務層組件)、@controller (控制層組件)、@repository (數據訪問組件)或@component (泛指組件)的類,並把它們作一個實例bean, 相當於在beans.xml 中配置了<bean> 元素。需要說明的是,使用了此配置同時意味着還註冊了註解配置的處理器,所以在這些類中用到了註解配置時並不需要再配置
< context:annotation-config
/> 。
爲什麼提出自動掃描管理:在一些比較大的項目中,涉及到的bean 實例會有很多,如果依次對每個bean 實例進行配置,不但配置內容繁瑣,而且配置文件也會顯得雜亂。因此spring 提出了自動掃描bean ,它依據在xml 文件中指定的包名和類中標記的component 系列註解。
2. 實例
建立spring_03_autoscan
項目 ,內容基本和前面兩個項目一樣,只是在要納入spring 管理的類前增加了component 這樣的註解。Beans.xml 的配置如下:
<? xml version
= "1.0" encoding
= "UTF-8"
?>
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context = "http://www.springframework.org/schema/context"
xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" >
< context:component-scan base-package = "com.asm.service" />
< context:component-scan base-package = "com.asm.dao.impl" />
</ beans >
說明:以上的配置會自動管理service 包和impl 包及它們子包下的帶有component 標記的類,上面兩行配置代碼等價於
< context:component-scan
base-package
= "com.asm" />
下面以UserServiceBean 爲例進行說明,代碼如下:
package com.asm.service;
@Service ( "usb" ) @Scope ( "singleton" )
public class UserServiceBean {
@Resource (name = "userDaoImpl" )
private UserDao userDao ;
private UserDao userDao2 ;
@Autowired (required = true )
@Qualifier ( "userDaoImpl" )
private UserDao userDao3 ;
@Resource
public void setUserDao2(UserDao userDao2) {
this . userDao2 = userDao2;
}
public UserServiceBean() {
}
@PostConstruct
public void init() {
System. out .println( "init method is called" );
}
public void test() {
System. out .println( "********************************" );
userDao .save();
userDao2 .save();
System. out .println( userDao3 );
// userDao3.save();
System. out .println( "********************************" );
}
}
說明 :如果使用 Service 這些註解時不指定名稱,這些實例bean 的名稱就是類名(但首字母小寫),也可以指定實例bean 的名字,比如這裏指定其名字爲“usb ”,scope 註解配置了bean 的作用範圍,
PostConstruct 註解指定了bean 的init 方法。關於其它的一些註解配置參文檔(3.11-3.12 )。
它的junit 測試代碼如下:
public class
AutoScanTest {
@Test
public void base () {
ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );
UserServiceBean udb = (UserServiceBean) ctx.getBean( "usb" );
udb.test();
}
}
小結 使用自動掃描管理的核心:配置掃描的包、類前的component 標記、瞭解常用註解。
五、 AOP 技術
1. 引出問題
建立spring_04_aop
項目 ,在該項目下有一個UserDao 接口,代碼如下:
package com.asm.dao;
public interface UserDao {
void save();
void update();
}
該接口的實現類UseDaoImp ,代碼如下:
package com.asm.dao.impl;
import com.asm.dao.UserDao;
public class UserDaoImp implements UserDao {
private String username ;
public UserDaoImp() {
}
public UserDaoImp(String username) {
this . username = username;
}
public String getUsername() {
return username ;
}
@Override
public void save() {
System. out .println( "save method is called : " + username );
}
@Override
public void update() {
System. out .println( "update method is called" + username );
}
}
需求如下:如果實現類的username!=null, 纔可以調用save 與update 方法,爲null 則不能調用。當然要解決此問題,可以在save 與update 方法內部進行判斷,但是如果在方法內部進行判斷,代碼則失去了靈活性,如果以後的需求改變,比如變成username.equals 時,則又要在update/save 方法中重新進行一次判斷。如果save/update 這樣的方法很多,這樣就會很麻煩。其實要解決此問題,可以通過動態代理技術實現。這裏的需求其實就是根據要求來攔截一些業務方法,這種編程問題稱之爲橫切性關注點 。
代理的目標對象必須實現了一個接口--- 橫切關注點
2. 動態代理實現[JDK]
建立代理工廠 ProxyFactory ,代碼如下:
package com.asm.dao.impl.factory;
public class ProxyFactory implements InvocationHandler {
private Object target ;
public Object createUserDaoImp(Object target) {
this . target = target;
return Proxy.newProxyInstance ( this . target .getClass().getClassLoader(), this . target .getClass().getInterfaces(), this );
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
UserDaoImp udi = (UserDaoImp) target ;
Object result = null ;
if (udi.getUsername() != null ) {
result = method.invoke( target , args);
}
return result;
}
}
簡析動態代理:此類根據傳遞的target 對象來生成此目標對象的代理對象,需要強調的是動態代理技術所要代理的對象必須實現一個接口 。 newProxyInstance 參數說明:第一個參數是目標對象的類裝載器,第二個參數是目標對象的接口,第三個參數是回調對象,生成的代理對象要執行方法時就是依靠這個回調對象的invoke 方法來進行目標對象的方法回調。關於動態代理的其它細節不在此討論。
建立junit 測試代碼,內容如下:
public class AopProxyTest {
@Test // 用戶名爲空,不執行方法
public void testProxy(){
ProxyFactory pf= new ProxyFactory();
UserDao ud=(UserDao) pf.createUserDaoImp( new UserDaoImp());
ud.save();
}
@Test // 用戶名爲不爲空,才執行 save 方法
public void testProxy2(){
ProxyFactory pf = new ProxyFactory();
UserDao ud=(UserDao) pf .createUserDaoImp( new UserDaoImp( " 張某 " ));
ud.save();
}
}
3.cglib 實現代理
JDK 的Proxy 實現代理要求被代理的目標對象必須實現一個接口,而如果目標對象沒有實現接口則不能使用Proxy 來代理。其實也可以藉助cglib 來實現代理。操作步驟如下
步驟一、建立 UserDaoImp2 類,它與
UserDaoImp 的唯一區別就是沒有實現任何接口。
步驟二、導入cglib.jar 包,創建cglib 代理工廠,代碼如下:
package com.asm.dao.impl.factory;
public class CglibFactory implements MethodInterceptor {
private Object target ;
public Object createUserDaoImp2(Object target) {
this . target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(
this .
target .getClass());
// cglib 創建的代理對象,其實就是繼承了要代理的目標類,然後對目標類中所有非
final 方法進行覆蓋,但在覆蓋方法時會添加一些攔截代碼。
enhancer.setCallback( this ); // 註冊回調器
return enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
UserDaoImp2 udi = (UserDaoImp2) target ;
Object result = null ;
if (udi.getUsername() != null ) {
// 前置通知
try {
result = methodProxy.invoke( target , args);
// 後置通知
} catch (Exception e) {
e.printStackTrace();
// 例外通知
} finally {
// 最終通知
}
}
return result;
}
}
說明:注意註釋的通知,通知就是攔截到方法後執行的一些代碼,比如前置通知,就是說在回調目標方法時執行的一些操作。
步驟三、建立測試代碼(省略,和五.2 的測試代碼相似)
4.aop 理論知識
橫切性關注點:對哪些方法攔截,攔截後怎麼處理,這些關注就稱之爲橫切性關注點
切面(aspect ):類是對物體特徵的抽象,而切面是指對橫切性關注點的抽象。
連接點(joinpoint ): 被攔截到的點,因爲spring 只支持方法類型的連接點,所以在spring 中連接點指的就是被攔截到的方法。實際上連接點還可以是字段或構造器。
切入點(pointcut ):對連接點進行攔截的定義。
通知(advice ):所謂通知就是指攔截到連接點之後的要執行的代碼。通知分爲前置、後置、異常、最終。環繞通知五類。
目標對象:代理的目標對象。
織入(weave ):將切面應用到目標對象並導致代理對象創建的過程
引入(introduction ):在不修改代碼的前提下,引入可以在運行期 爲類動態地添加一些方法或字段。
5. 基於spring 的AOP 實現
步驟一、導入spring 開發的基本包(包括切面及註解包)
步驟二、編寫切面類TheInterceptor ,代碼如下:
package com.asm.dao.impl.factory;
@Aspect
public class TheInterceptor {
@Pointcut ( "execution (* com.asm.dao.impl.UserDaoImp.*(..))" )
// 聲明一個切入點 ( 第一個 * 後要留一個空格 )
private void anyMethod() {
}
@Before ( "anyMethod()" ) // 前置通知
public void before() {
System. out .println( " 前置通知 " );
}
@AfterReturning ( "anyMethod()" ) // 後置通知
public void afterReturning(){
System. out .println( " 後置通知 " );
}
}
簡析:Aspect 註解聲明此類爲一個切面類,Pointcut 註解用來聲明一個切入點,括號中的參數是切入點的表達式,這裏的表達式的意思是對UserDaoImp 類的所有方法進行攔截。關於切入點表達式後面會有詳細的說明。anyMethod 是爲切入點起一個名字,後面的“通知”都要依賴這個名字。
步驟三、beans.xml 配置文件的內容如下:
<? xml version = "1.0" encoding = "UTF-8" ?>
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context = "http://www.springframework.org/schema/context"
xmlns:aop = "http://www.springframework.org/schema/aop"
xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd" >
< aop:aspectj-autoproxy /> <!-- 開啓切面編程功能 -->
< bean id = "userDaoImp" class = "com.asm.dao.impl.UserDaoImp" />
< bean id = "theInterceptor"
class = "com.asm.dao.impl.factory.TheInterceptor" />
</ beans >
說明:要想切面類起作用,首先要把切面類納入spring 容器管理。
步驟四、編寫junit 測試單元
@Test
public void base() {
ApplicationContext ctx = new ClassPathXmlApplicationContext ( "beans.xml" );
// UserDaoImp udii=(UserDaoImp) ctx.getBean("userDaoImp");
UserDao ud = (UserDao) ctx.getBean( "userDaoImp" );
System. out .println(ud.getClass().getName());
ud.save();
}
說明:由於開啓了切面編程功能,所以當我們獲取一個被切面類監控管理的
bean 對象 —UserDaoImp
時,它實際上獲取的是此對象的一個代理對象,而在
spring 中對代理對象的處理有如下
原則 :( 1
)如果要代理的對象實現了接口,則會按照 Proxy
的方式來產生代理對象,這即是說產生的代理對象只能是接口類型,比如起用上面注掉的代碼就會報錯,而且通過下面的打印語句我們也可以看出產生的是一個代理對象。(
2 )要代理的對象未實現接口,則按
cglib 方式來產生代理對象。
另還要注意:要想
spring 的切面技術起作用,被管理的
bean 對象只能是通過 spring
容器獲取的對象。比如這裏如果直接 new
出 UseDaoImp
對象,則 new 出的對象是不能被
spring 的切面類監控管理。
補充 :測試被代理對象未實現接口時,spring 切面技術的應用。
步驟一、修改切面類 TheInterceptor
切入點爲如下內容:
@Pointcut (
"execution (* com.asm.dao.impl.*.*(..))"
)
說明:攔截 impl
包下的所有類所有方法
步驟二、在beans.xml 中增加如下內容:
< bean id
= "userDaoImp2"
class =
"com.asm.dao.impl.UserDaoImp2" />
步驟三、junit 測試代碼如下:
public void
base2() {
ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );
UserDaoImp2 udi2 = (UserDaoImp2) ctx.getBean( "userDaoImp2" );
System. out .println(udi2.getClass().getName());
System. out .println(udi2.getClass().getSuperclass ().getName());
udi2.save();
}
說明: UseDaoImp2
未實現任何接口,因此在 spring
中利用切面技術來管理此類使用的動態代理技術實質是
cglib 的動態代理方式,所以產生的代理對象實質是被代理對象的一個子類,通過上面的控制檯打印語句可以看出。
小結:( 1
)聲明 aspect 的切面類要納入
spring 容器管理才能起作用。(
2 )被管理的
bean 實例要通過容器的 getBeans
方法獲取。 (
3 )依據被管理的 bean
是否實現接口, spring
採取兩種方式來產生代理對象。( 4
)在 xml 文件中啓用 <aop:aspectj-autoproxy/>
6. 通知應用實例(基於註解)
在前一節,我們應用了前置通知和後置通知,除了這兩個通知外,下面接着演示其它通知的應用。
(1 )最終通知
在切面類 TheInterceptor 中增加如下代碼即可:略去測試。
@After (
"anyMethod()" )
// 最終通知
public void after() {
System. out .println( " 最終通知 " );
}
(2 )異常通知
爲了演示此實例,我們在UseDaoImp 中增加如下代碼以拋出異常:
int i=1/0; 在然後在切面類
TheInterceptor 中增加如下代碼:
@AfterThrowing (
"anyMethod()" )
// 例外通知
public void AfterThrowing() {
System. out .println( " 例外通知 " );
}
當獲取代理對象並調用save 方法時會拋出異常,例外通知便會得以執行。
(3 )環繞通知
@Around ( "anyMethod()" ) // 環繞通知
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System. out .println( " 進入環繞 " );
//if(){ // 進行一些判斷,再執行環繞
Object result = pjp.proceed();
//}
System. out .println( " 退出環繞 " );
return result;
}
注意的是方法的參數及拋出異常類型的固定寫法(方法名可以是任意得),另在該方法中必須執行
pjp.proceed() 才能讓環繞通知中的兩處打印代碼得以執行。即是說要想環繞通知的攔截處理代碼起作用必須調用
pjp.proceed 方法。
補充 :環繞通知通常可以用來測試方法的執行時間,在
pjp.proceed 前獲取一個時間,在
pjp.proceed 方法後再獲取一個時間。最後兩個時間相減即可得方法執行時間。
(4 )傳遞參數給通知
首先在UseDao 接口中增加如下代碼:
String add(String name);
然後再在UserDaoImp 中實現此方法,代碼如下:
public String add(String name) {
System. out .println( "add method is called [ " + name+ " ]" );
return " 添加成功 " ;
}
需求:獲取調用add 方法傳遞的參數。操作步驟如下:在切面類增加如下代碼:
@Before (
"anyMethod() && args(name)" )
// 前置通知 ,
只針對 UseDaoImp
的 add
方法
public void beforeAdd(String name) {
System. out .println( " 前置通知 :" + name);
}
說明:在前置通知的方法中有一個參數,然後再把此參數作爲攔截條件(即是說攔截帶有一個
String 類型參數的方法)。
args 的名字和
beforeAdd 方法參數名字相同。
測試代碼:
public
void advieeTest () {
ApplicationContext ctx = new ClassPathXmlApplicationContext( "beans.xml" );
UserDao ud=(UserDao) ctx.getBean( "userDaoImp" );
ud.add( "xxx" );
}
(5 )獲取方法的返回值
我們知道add 方法有一個返回值,我們對此方法進行攔截並獲取返回值,在切面類中增加如下代碼:
@AfterReturning (pointcut =
"anyMethod()"
, returning = "result"
)
// 後置通知 , 監聽返回結果,針對 UserDaoImp 的 getUsername() 方法
public void afterReturningRes(String result) {
System. out .println( " 後置通知 , 返回結果 :" + result);
}
說明: afterReturningRes
方法的參數就是要返回的參數類型, returning
標記的就是的結果,它的取值與該方法參數名相同。
測試代碼同( 4
)。
(6 )獲取拋出的異常
切面類的增加如下代碼:
@AfterThrowing (pointcut= "anyMethod" ,throwing= "e" )
public void catchException(Exception e){
System. out .println( " 獲取拋出的異常: " +e);
}
throwing 的取值和方法的參數名相同,測試代碼省略。
7. 通知應用實例(基於XML )
步驟一、複製 TheInterceptorX 類爲 TheInterceptorXML ,並去掉所有註解。
步驟二、建立beansXML.xml 配置文件,內容如下:
< aop:aspectj-autoproxy
/> <!--
開啓切面編程功能 -->
< bean id = "userDaoImp" class = "com.asm.dao.impl.UserDaoImp" />
< bean id = "aspectBean"
class = "com.asm.dao.impl.factory.TheInterceptorXML" />
< aop:config >
< aop:aspect id = "asp" ref = "aspectBean" > -- 聲明一個切面類
< aop:pointcut id = "thecut" -- 聲明一個切入點
expression = "execution(* com.asm.dao.impl.UserDaoImp.*(..))" />
< aop:after-returning pointcut-ref = "thecut" method = "afterReturningRes" returning = "result" />
< aop:around pointcut-ref = "thecut" method = "around" />
< aop:after-throwing pointcut-ref = "thecut" method = "catchException" throwing = "e" />
< aop:after pointcut-ref = "thecut" method = "after" />
< aop:before pointcut-ref = "thecut" method = "before" />
</ aop:aspect >
</ aop:config >
測試代碼如下:
public void
advieeTest() {
ApplicationContext ctx = new ClassPathXmlApplicationContext( "beansXML.xml " );
UserDao ud=(UserDao) ctx.getBean( "userDaoImp" );
ud.add( "xxx" );
}
未解決問題:不能成功傳參給前置通知。
8. 解析切入點表達式
1. 格式: execution( 返回值 空格 方法選擇) 。兩部分組成,中間一定要有空格 。
返回值:可以是* ,說明攔截任何方法。Java.lang.String( 全名) ,攔截返回值爲String 類型的方法。 常用的實例如下:
方法選擇:包名[ 類名].* ()。設定要攔截的方法簽名。
表達式(省略 execution ) |
說明 |
(java.lang.String 方法選擇略 ) |
攔截返回值爲 String 類型的方法 |
(!void 方法選擇略 ) |
攔截返回值非空的方法 |
(* com.asm..*.*(..)) |
攔截 com.asm 包及子包下每個類的全部方法 |
(* com.asm.*.*(..)) |
攔截 com.asm 包下每個類的全部方法 |
(* com.asm.User.*(..)) |
攔截 asm 包下 User 類的所有方法 |
(* com.asm.User.* |
攔截 User 類中第一個參數爲 String, 後面參數任一的方法 |
待增加 |
|
待增加 |
|
9. 總結
面向切面的常見應用(如權限攔截)、spring 的aop 依賴兩種方式實現代理(依被代理的對象是否實現接口而定)、通知概念、基於註解與基於XML 兩種方式來配置切面、基本步驟(依要攔截的方法來設定切入點,依據業務需求實現攔截通知代碼,切面納入spring 容器管理,要被監控的類只能是通過Spring 容器獲取)、切入點的格式。
六、與 JDBC 集成
1. 搭建環境
建立spring_05_integrationJdbc 項目 ,此項目使用dbcp 作爲數據源來整合JDBC 技術。因此除了spring 所需的jar 包,還應導入dbcp 的jar 包:commons-dbcp-1.2.2.jar 及此 jar 所依賴的兩個apache 開源jar 包:commons-collections-3.1.jar 、commons-pool.jar 。由於涉及到數據庫操作,還應導入數據庫驅動包,這裏選擇的是mySQL 驅動。
2. 基於註解的事務管理
步驟一、配置事務管理器。
建立beans.xml 文件,它的內容如下:
<? xml version
= "1.0" encoding
= "UTF-8"
?>
< beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop = "http://www.springframework.org/schema/aop"
xmlns:tx = "http://www.springframework.org/schema/tx"
xmlns:context = "http://www.springframework.org/schema/context"
xsi:schemaLocation = "
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd " >
< bean id = "theDatasource"
class = "org.apache.commons.dbcp.BasicDataSource" >
< property name = "driverClassName" value = "com.mysql.jdbc.Driver" />
< property name = "url" value = "jdbc:mysql://localhost:3306/sjdbc" />
< property name = "username" value = "root" />
< property name = "password" value = "123456" />
< property name = "initialSize" value = "2" />
< property name = "maxActive" value = "100" />
< property name = "maxIdle" value = "2" />
< property name = "minIdle" value = "1" />
</ bean >
< bean id = "txManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< property name = "dataSource" ref = "theDatasource" />
</ bean >
< tx:annotation-driven transaction-manager = "txManager" />
說明:首先是配置了一個數據源,以供數據源事務管理器 配置引用。接着配置了數據源事務管理器,隨後開啓了基於 @Transactional 註解的事務管理器,開啓後,只要是被 spring 管理的 bean 且打有 @Transactional 註解的 bean 都會受配置的事務管理。關於這裏的配置可參看 spring 文檔 9.5.6
步驟二、準備使用環境。
建立 UserDao 接口,代碼如下:
package com.asm.dao;
public interface UserDao {
void save(User user);
void delete (User user);
void update(User user);
User get( int id);
List<User> getUsers();
}
建立 UserDaoImp 類實現 UseDao 接口,代碼如下:
package com.asm.dao.impl;
@Transactional
public class UserDaoImp implements UserDao {
private JdbcTemplate jdbcTemplate ;
public void setDatasouce(DataSource datasource) {
jdbcTemplate = new JdbcTemplate(datasource);
}
public void delete(User user) {
jdbcTemplate .update( "delete from user where id=?" , new Object[] { user.getId() },
new int [] { java.sql.Types. INTEGER });
}
public User get( int id) {
return (User) jdbcTemplate .queryForObject( "select * from user where id=?" ,
new Object[] { id }, new int [] { java.sql.Types. INTEGER }, new RowMapper() {
public Object mapRow(ResultSet rs, int arg1) throws SQLException {
User user = new User();
user.setId(rs.getInt( "id" ));
user.setName(rs.getString( "name" ));
return user;
}
});
}
@SuppressWarnings ( "unchecked" )
public List<User> getUsers() {
return (List<User>) jdbcTemplate .query( "select * from user" , new RowMapper() {
public Object mapRow(ResultSet rs, int arg1) throws SQLException {
User user = new User();
user.setId(rs.getInt( "id" ));
user.setName(rs.getString( "name" ));
return user;
}
});
}
public void save(User user) {
jdbcTemplate .update( "insert into user(name) values(?)" , new Object[] { user.getName() },
new int [] { java.sql.Types. VARCHAR });
}
public void update(User user) {
jdbcTemplate .update( "update user set name=? where id=?" , new Object[] { user.getName(),
user.getId() }, new int [] { java.sql.Types. VARCHAR , java.sql.Types. INTEGER });
}
}
步驟三、把UserDaoImp 納入spring 容器管理。
在beans.xml 中增加對應的配置內容如下:
< bean id = "userDaoImp" class = "com.asm.dao.impl.UserDaoImp" >
< property name = "datasouce" ref = "theDatasource" />
</ bean
>
結合配置文件解析UserDaoImp 實現類:(1 )此類作爲一個bean 實例納入spring 容器管理,使用setter 注入方式完成對datasource 的注入,實質是完成的
JdbcTemplate 對象的初始化。(2 )該類CRUD 方法都使用了Spring 容器提供的
JdbcTemplate 對象來簡化了CRUD 操作,在spring 文檔的 11.2.1 . 節 對JdbcTemplate 類作了較詳細的介紹。(3 )此類打上了
@Transactional 註解,表示此類中的業務方法都會受beans.xml 配置的
< tx:annotation-driven transaction-manager
= "txManager"
/> 管理
步驟四、編寫測試類
package com.asm.test;
public class TestSJ {
private UserDao ud = (UserDao) new ClassPathXmlApplicationContext( "beans.xml" ).getBean( "userDaoImp" );
public static void main(String[] args) {
TestSJ sj = new TestSJ();
sj.save();
sj.delete();
}
public void save() {
User user = new User();
user.setName( " 張某某 " );
ud .save(user);
}
public void delete() {
User user = new User();
user.setId(1);
ud .delete(user);
}
// 其它測試方法省略 ...
}
說明 : 注意這裏通過 getBean 獲取的是 UserDao 接口對象,而非 UserDao 接口的實現類 UserDaoImp 對象,因爲 spring 的事務管理也是利用了 aop 技術,所以必須要面向接口,如果想通過 getBean 獲取它的實現類對象將會報錯。
步驟五、感知事務管理。
在UserDaoImp 的delete 方法中增加如下代碼:
jdbcTemplate .update( "delete from user where id=?" , new Object[] { user.getId() }, new int [] { java.sql.Types. INTEGER });
int i=5/0;
jdbcTemplate .update( "delete from user where id=2" );
spring默認的事務管理方式:運行期異常進行事務回滾,非運行期異常不進行事務回滾。因此增加上面的代碼後,會出現ArithmeticException運行期異常。所以當出現此異常時,delete方法中的數據庫操作都會進行回滾,因而id=1和2這兩條記錄都不會被刪除。 如果把UseDaoImp類前標記@Transactional的註解去掉,id=1的記錄會被刪除,因爲失去了spring容器的事務管理。
小結spring事務:
(1)在beans.xml中配置事務管理器,可以是依賴於數據源的事務管理器,也可以其它的事務管理器(比如和JPA集成的事務管理器等),這些事務管理器類都繼承自AbstractPlatformTransactionManager抽象類。(2)事務管理器配置後,要想讓配置的事務管理器對某些類的事務管理起作用,可以有兩種方式配置:一種是聲明式配置(兩種:基於註解或基於XML),一種是編程是配置。(3)上面的一些操作,都是基於註解的聲明式事務配置:關鍵兩點:開啓基於事務的註解支持(<tx:annotation-driven transaction-manager="txManager" />);被管理的類打上事務註解標記。(4)除了可以在類前聲明事務標記,也可以在類的方法中具體聲明詳細的事務標記。(5)事務標記具有多個可選屬性,具體可參文檔9.5.6.1。
3. 簡析事務註解屬性
@Transactional 註解具有大屬性:
(1 )傳播屬性 propagation
:
可選屬性 |
說明 |
REQUIRED |
業務方法需要在一個事務中運行,如果方法運行時,已經處在一個事務中,那麼加入到該事務,否則自己創建一個新的事務。 |
REQUIRESNEW |
不管是否存在事務,業務方法總會爲自己發起一個新的事務,如果方法已經運行在一個事務中,則原有事務會被掛起,新的事務會被創建,直到方法執行結束,新事務纔算結束,原先的事務才恢復執行。 |
SUPPORTS |
如果業務方法在某個事務範圍內被調用,則業務方法成爲該事務的一部分。如果業務方法在事務範圍外被調用,則方法在沒有事務的環境下執行 |
NOT_SUPPORTED |
聲明業務方法不需要事務,如果方法沒有關聯到一個事務,容器不會爲它開啓事務,如果方法在一個事務中被調用,則該事務會被掛起,在該方法調用結束後,原先的事務恢復執行 |
NEVER |
指定業務方法絕對不能在事務範圍中執行,如果業務方法在某個事務中執行,容器會拋出例外;只有業務方法沒有關聯到任何事務,才能正常執行。 |
MANDATORY |
指定業務方法只能在一個已經存在的事務中執行,業務方法不能發起自己的事務。如果業務方法在沒有事務的環境下調用,容器就會拋出例外 |
NESTED |
如果一個活動的事務存在,它會自己產生一個單獨的而且擁有多個可以回滾的保存點的事務,然後嵌套運行在活動的事務中,實質它可以看成是一個內部事務且具有回滾保存點,所以內部自身的事務回滾並不會引起外部活動事務的回滾,它只是回滾到內部事務的保存點。 如果沒有活動事務,則按REQIIRED 屬性執行。需要注意的是此配置只對DataSourceTransactionManager 事務管理器生效。 |
說明:上面多次提到業務方法,它實質就是UserDaoImp 中save 等這樣的方法。但是這些方法前會有一個設定了詳細屬性的
@Transactional 註解。比如:
@Transactional (propagation=
"" ,isolation=
"" ,noRollbackFor=
"" )
(2 )隔離級別屬性 isolation
:這個是依賴數據庫系統而言,數據庫系統提供四種事務隔離級別。(具體的事務隔離級別分析在些略過,可以參看網絡資源及相關文檔)
(3 )只讀屬性 readOnly
:false-- 讀寫性、true-- 只讀性事務.
(4 )超時屬性 timeout
:int 型,以秒爲單位。
(5 )回滾屬性:根據拋出的異常決定是否回滾事務,它包括四種可選屬性。參文檔 9.5.6 .1 的表 9.3
4. 擴展:抽取dpcp 配置文件。
如果希望把dbcp 的配置單獨放到一個properties 配置文件中去,可以使用如下做法。操作步驟:在src 的路徑下建立dbcp.properties 文件,內容如下:
driverClassName= com.mysql.jdbc.Driver
url= jdbc : mysql://localhost:3306/sjdbc
username= root
password= 123456
initialSize= 2
maxActive= 100
maxIdle= 2
minIdle=
1
然後把此配置讀取數據源bean 中去,形式如下:
< bean id
= "theDatasource"
class = "org.apache.commons.dbcp.BasicDataSource" >
< property name = "driverClassName" value = "${driverClassName}" />
< property name = "url" value = "${url}" />
... 省略部分屬性的獲取,形式如下:
< property name = "xxx" value = "${dbcp.properties 配置文件中的鍵 }" />
...
</ bean >
注意的是使用${}來獲取值,必須把properties文件引入到spring的容器管理,因此要想成功通過${}來獲取相應的值,還應beans.xml中增加如下內容: <context:property-placeholder location="classpath:dbcp.properties" /> 而要想使用<context:XXX/>還應在beans.xml增加名稱空間:xmlns:context="http://www.springframework.org/schema/context"
和schema 信息:http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
關於這裏的名稱空間及schema 信息可參A2.8 節。
5. 基於XML 的事務管理
步驟一、拷貝UserDaoImp 爲UserDaoImp2 ,然後把內面所有關於事務的註解去掉
步驟二、建立beansXML.xml 文件,並配置事務管理器(和六.2. 步驟一 完全相同)。
步驟三、配置事務
< aop:config
>
< aop:pointcut id = "txPointcut"
expression = "execution(* com.asm.dao.impl.UserDaoImp2.*(..)) " />
< aop:advisor advice-ref = "txAdvice" pointcut-ref = "txPointcut" />
</ aop:config >
< tx:advice id = "txAdvice" transaction-manager = "txManager" >
< tx:attributes >
< tx:method
name =
"get*" read-only
= "true" />
--> 攔截
get 開頭的方法,並設置只讀屬性
< tx:method name = "*" /> --> 攔截所有方法
</ tx:attributes >
</ tx:advice >
解析 :根據txManager 事務管理器來配置一個事務通知器:txAdvice 。而<tx:method> 中的name 屬性指定方法名(可以使用通配符),其它屬性就好比註解的傳播屬性配置(可參六.3(1) )。然後再使用切面配置一個切入點,再對這個切入點引入事務通知器。
步驟四、把UserDaoImp2 納入spring 容器管理。即在beansXML.xml 做如下配置:
< bean id = "userDaoImp2" class = "com.asm.dao.impl.UserDaoImp2" >
< property name = "datasouce" ref = "theDatasource" />
</ bean
>
步驟五、編寫寫測試類,測試事務管理器是否起作用,省略...
5. 總結與JDBC 的集成
(1 )首先是配置好一個事務管理器(2 )基於註解:一是涉及到事務的類打上事務註解標記,二是在xml 配置文件中開啓事務註解功能;基於配置:事務通知器加進apo 配置中。(3 )涉及到事務的類必須納入spring 容器管理,事務才能起作用。(4 )強調 :不論是基於註解還是基於xml 配置實現事務,都要依賴於動態代理技術。以UserDaoImp 說明,它實現了接口,所以它的代理對象實質是一個接口對象,因而通過getBean 獲取的UseDaoImp 實質上是一個接口對象,所以特別要注意類型轉換(參六.2. 步驟四. 說明 )。(5 )爲了簡化CRUD 操作,我們通常會使用spring 提供的JdbcTemplate 類。
七、 SSH 集成實例
1. 分析實例及準備環境
建立spring_06_SSH
項目 ,此項目主要演示struts1.x 、hibernate 、spring 的集成。集成前的一個比較關鍵的因素是搭建好環境,這一步要求我們對集成的框架所用到的jar 包有比較清楚的認識。下面列表展示了三個框架所用到的jar 包。
hibernate3.3 所用到的jar 包 :
antlr-2.7.6.jar 開源語法分析生成器(lib/required)
commons-collections-3.1.jar Commons 集合類庫,與連接池有關(lib/required)
dom4j-1.6.1.jar xml 解析類庫(lib/required)
javassist-3.9.0.GA.jar 分析,編輯和創建java 字節碼類庫(lib/required)
jta-1.1.jar 事務處理api (lib/required)
slf4j-api-1.5.8.jar 日誌處理 (lib/required)--> 用log4j 實現
hibernate3.jar 核心
ehcache-1.2.3.jar 二級緩存(lib/optional/ehcache )
Spring2.5 安裝包所用的jar 包:
dist/spring.jar
lib/aspectj/aspectjweaver.jar 、aspectjrt.jar
lib/cglib/cgligb-nodep-2.1_3.jar
lib/j2ee/common-annotations.jar
lib/jakarta-commons/commons-logging.jar 、 commons-dbcp.jar 、commons-pool.jar
dist/modules/spring-webmvc-struts.jar
lib/log4j/log4j-1.2.15.jar 供srping 與hibernate 使用的日誌記錄jar 包
lib/slf4j/ slf4j-log4j12-1.5.0.jar 日誌轉換jar 包,實現把log4j.jar 包適配到slf4j 標準。
struts1.10 所用到的jar 包
導入lib 目錄下的所有jar 包。但爲了避免jar 包衝突,不要導入antlr.jar (hibernate 已經導入)
jar 包說明
:綠色字樣是框架所需的一些基本包 。hibernate 的開發jar 包主要集中在required 目錄和核心jar 包,還需要特別說明的是它的日誌包問題:hibernate 使用了slf4j 來記錄日誌,但是slf4j 只是一個日誌記錄標準,需要具體的實現纔可以進行日誌記錄,我們可以使用它本身的實現(如slf4j-simple-1.5.8.jar ),也可以使用其它的實現,如log4j
來記錄日誌,但是使用log4j 需要導入slf4j-log4j12-1.5.8.jar 來進行適配。在這裏爲了和spring 的日誌記錄結合,我們使用了log4j 的實現。而要使用log4j 日誌記錄,可以在spring 的lib 子目錄中找到。Spring 框架中除了常用的jar 包外,還增加了dbcp
連接池 相關的包,需要說明的是連接池的基本包爲:dbcp 、pool 、collections (在hibernate 中已導入)。爲了與strus 相結合,還增加了一個spring-webmvc-struts.jar 包。
經驗:spring 的作用是來集成其它的框架,所以對於許多集成所要用到的jar 包,在spring 中都能找到,因而對於項目中出現的類裝載相關的錯誤,應首先查看jar 包是否衝突,是否導入了需要的包,建議集成時,其它框架只需導入它們自己所需的基本包,而對於集成所用的包都可以從spring 提供的lib 中查詢。
2. 集成spring+hibernate
對於框架的集成最好的方式是分佈集成,比如這裏先集成spring+hibernate 並測試,測試通過後再來集成struts 。
步驟一、建立接口UserServeice ,代碼如下:
package com.asm.service;
public interface UserService {
public abstract void save(User user);
public abstract void delete(Integer id);
public abstract void update(User user);
public abstract User getUser(Integer id);
@SuppressWarnings ( "unchecked" )
public abstract List<User> getUsers();
}
步驟二、對應的實現類
UserServiceBean ,代碼如下:
package com.asm.service.impl;
public class UserServiceBean implements UserService {
@Resource
private SessionFactory sf ;
@Transactional
public void save(User user) {
sf .getCurrentSession().persist(user);
}
...
其它實現方法省略
}
注意事項 :
(1) 在每個方法上都要加上事務註解,特別是
save 和
update 方法一定要加上事務註解,因爲把
sessionFactory 對象是通過
spring 注入 (
註解方式注入 )
,而 spring 中配置的
sessionFactory 對象又納入了
spring 容器的事務管理,所以要加上事務標記。
(2) 一定要通過
getCurrentSession 方法得到
session 對象,因爲使用
openSession 方式得到的
Session 對象不受 spring
容器管理。
實體類
User 及相應的
User.hbm.xml 配置在此省略。
步驟三、在
beans.xml 中進行配置,內容如下:
< bean
id =
"theDatasource"
class = "org.apache.commons.dbcp.BasicDataSource" >
< property name = "driverClassName" value = "com.mysql.jdbc.Driver" />
< property name = "url" value = "jdbc:mysql://localhost:3306/ssh" />
< property name = "username" value = "root" />
< property name = "password" value = "123456" />
... 數據源配置,省略其它,可參看“與 jdbc 的集成”。
</ bean >
< bean id = "txManager"
class = "org.springframework.orm.hibernate3.HibernateTransactionManager" >
< property name = "sessionFactory" ref = "sessionFactory" />
</ bean >
< tx:annotation-driven transaction-manager = "txManager" />
< bean id = "sessionFactory"
class = "org.springframework.orm.hibernate3.LocalSessionFactoryBean" >
< property name = "dataSource" ref = "theDatasource" />
< property name = "mappingResources" >
< list >
< value > com/asm/entity/User.hbm.xml </ value >
</ list >
</ property >
< property name = "hibernateProperties" >
< value >
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true
hibernate.format_sql=true
</ value >
</ property >
<!—
如果想就把此 bean 對應的屬性配置放到 hibernate.cfg.xml 中去配置,可以在 hibernate.cfg.xml 中配好後,再使用下面的配置加載 hibernate.cfg.xml 配置文件:
<property name="configLocation" value="hibernate.cfg.xml" />
-->
</ bean >
< bean id = "userServiceBean" class = "com.asm.service.impl.UserServiceBean" />
說明:這裏配置的 sessionFactory
實質就是對 hibernate
的 sessionFactory
進行了一次包裝,它的配置依賴於一個數據源實例
bean ,最後再把 sessionFactory
納入 spring
容器的事務管理,但是要注意的是這時使用的是事務管理器類爲
HibernateTransactionManager
。
配置簡圖:
步驟四、建立
junit 單元進行測試,測試代碼如下:
package junit.test;
public class UserServiceTest {
private static UserService us ;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
ApplicationContext ctx= new ClassPathXmlApplicationContext( "beans.xml" );
us =(UserService) ctx.getBean( "userServiceBean" );
}
@Test
public void testSave() {
us .save( new User( " 李某某 " ));
}
... 其它測試方法省略。
}
測試通過,完成了 MVC
的 M
層業務邏輯層的開發。
3. 集成struts 框架
首先準備好struts 框架的基本配置環境:(1 )在web.xml 中配置struts 的核心控制器Actionservlet (配置代碼省略)。 (2 )在struts-config.xml 中建立strus-config.xml 配置文件,它的配置內容如下:
<? xml version
= "1.0" encoding
= "ISO-8859-1"
?>
<! DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
"http://struts.apache.org/dtds/struts-config_1_3.dtd" >
< struts-config >
< action-mappings >
< action path = "/list" type = "com.asm.action.UserAction" >
< forward name = "success" path = "/WEB-INF/page/showUser.jsp" ></ forward >
</ action >
</ action-mappings >
</ struts-config >
(3 )上面所用到的UserAction 的代碼如下:
package com.asm.action;
public class UserAction extends Action {
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
WebApplicationContext ctx = WebApplicationContextUtils
.getWebApplicationContext ( this . servlet .getServletContext());
UserService us = (UserService) ctx.getBean( "userServiceBean" );
List<User> users = us.getUsers();
request.setAttribute( "users" ,users);
return mapping.findForward( "success" );
}
}
說明:要想此實例真正可用,還需在web.xml 中增加一個監聽器的配置,內容如下:
< context-param >
< param-name > contextConfigLocation </ param-name >
< param-value > classpath:beans.xml </ param-value >
</ context-param >
< listener >
< listener-class >
org.springframework.web.context.ContextLoaderListener
</ listener-class >
</ listener >
此監聽器的作用就是在啓動時把spring 容器進行實例化,並把spring 容器實例 WebApplicationContext 對象 放到application 作用域中,大致進行類似這樣的操作:servletContext.setAttribute( WebApplicationContext. ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE ,spring 容器實例WebApplicationContext 對象) 。因此我們也可以通過保存的名字來獲取 WebApplicationContext 對象,但是爲了方便, spring 提供一個工具類 WebApplicationContextUtils 來獲取此對象,關於這個工具類的使用,在 UserAction 就是使用了此工具類來獲取 spring 容器實例。保存一個 List<User> 對象後,在 showUser.jsp 遍歷 List 對象,代碼如下:
< c:forEach items = "${users} "var = "user" >
${user.id } -- ${user.name } < br />
</ c:forEach >
4. 改進集成struts 框架
上面的代碼其實並非整合struts 的最佳方式,整合struts 的優雅方式應該是把Action 也納入spring 容器管理。具體操作如下:
步驟一 、修改UserAction 類,修改後的代碼如下:
@Resource
private UserService userService ;
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setAttribute( "users" , userService .getUsers());
return mapping.findForward( "success" );
}
說明 :上面的userService 屬性通過spring 的註解方式注入,這樣可以避免獲取UserService 對象的繁瑣過程。
步驟二 、在beans.xml 配置UserAction ,納入spring 容器管理,配置內容如下:
< bean name = "/list" class = "com.asm.action.UserAction" />
說明 :此實例 bean 的配置使用了 name 屬性,而未使用 id 屬性,因爲 id 不支持“ / ”這樣的特殊字符。還需要說明的是這裏的 name 之所以取值爲“ /list ”,是因爲納入 spring 容器管理的 Action 的取名必須保證與 action 的 path 屬性值相同(爲什麼如此做,後面有解釋)。
步驟三 、添加一個 ActionServet 的處理器類。 Struts1.x 中的 ActionServlet 最終其實是把請求派發給 RequestProcessor 類來處理,而如果想自己編寫處理請求類,可以在 struts-config.xml 中配置一個 <controller> 來處理這些請求。 Spring 提供了一個
DelegatingRequestProcessor 類,它繼承自 RequestProcessor 類,它除了具備父類的功能,還主要完成一件重要的工作:把 ActionServlet 派發過來的請求交給在 spring 中配置的 Action bean 實例來處理(由於最終它是把請求交給了 spring 中配置的 action bean 實例處理,所以在 struts-config.xml 中關於此 action 的 type 屬性可以不寫)。因此我們還應
struts-config.xml 的中增加如下配置代碼:
< controller
>
< set-property property = "processorClass"
value = "org.springframework.web.struts.DelegatingRequestProcessor" />
</ controller >
關於這個處理器也可如下配置:
< controller
processorClass =
"org.springframework.web.struts.DelegatingRequestProcessor"
/>
|
5. 使用二級緩存
步驟一、在使用hibernate 框架時,可以爲hibenate 配置一個第三方緩存。通常的做法是在hibernate 的主配置文件中開啓緩存相關的功能。在此項目中,由於是集成了spring 框架,所以我們可以在spring 容器的beans.xml 中的sessionFactory bean 實例的
hibernateProperties 屬性中增加如下配置:
cache.use_second_level_cache=true -->
開啓二級緩存
hibernate.cache.use_query_cache=false --> 不使用查詢緩存
hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider
--> 第三方緩存實現類
步驟二、第三方緩存EhCache 的配置,在src 目錄下編寫ehcache.xml 配置文件,內容如下:
<? xml version = "1.0" encoding = "UTF-8" ?>
<!--
defaultCache 節點爲缺省的緩存策略
maxElementsInMemory 內存中最大允許存在的對象數量
eternal 設置緩存中的對象是否永遠不過期
overflowToDisk 把溢出的對象存放到硬盤上
timeToIdleSeconds 指定緩存對象空閒多長時間就過期 , 過期的對象會被清除掉
timeToLiveSeconds 指定緩存對象總的存活時間
diskPersistent 當 jvm 結束是是否持久化對象
diskExpiryThreadIntervalSeconds 指定專門用於清除過期對象的監聽線程的輪詢時間
-->
< ehcache >
< diskStore path = "C:/cache" />
< defaultCache maxElementsInMemory = "1000" eternal = "false" overflowToDisk = "true"
timeToIdleSeconds = "120"
timeToLiveSeconds = "180"
diskPersistent = "false"
diskExpiryThreadIntervalSeconds = "60" />
< cache name = "com.asm.entity.User" maxElementsInMemory = "100" eternal = "false"
overflowToDisk = "true" timeToIdleSeconds = "300" timeToLiveSeconds = "600" diskPersistent = "false" />
</ ehcache >
步驟三、在User.htm.xml 中的<class> 目錄下增加如下配置:
< cache usage = "read-write" region = "com.asm.entity.User" />
說明 :這裏的region 的值和ehcache.xml 配置下的<cache> 中的name 屬性值相同。結合echcache.xml 作如下說明: 通常情況下,實體類的二級緩存配置通常使用默認的<defaultCache> 這個配置,但是如果想使用特有的緩存配置,可以用實體類的<cacche regin> 來和ehcache.xml 中的<cache name> 關聯。
步驟四、測試二級緩存。在 UserServiceTest 中增加如下測試代碼:
public void testGetUser() {
System. out .println( us .getUser(1).getName());
System. out .println( " 關閉數據庫服務 " );
try {
Thread.sleep (1000*40);
} catch (InterruptedException e) {
e.printStackTrace();
}
System. out .println( " 關閉數據庫準備從緩存中獲取 " );
System. out .println( us .getUser(1).getName());
}
說明:當執行到控制檯顯示“關閉數據庫服務”時,我們手工停掉數據庫服務,休眠 40 秒後,仍能獲取數據,證明是從緩存中取得的數據。
小結使用緩存步驟 :開啓支持緩存的配置、對第三方緩存類進行配置、在實體類配置文件中標記使用二級緩存。
6. 亂碼問題
當前臺傳遞的數據包含中文時,獲取這些中文數據時會出現亂碼。大致原因 如下:在struts 框架中,我們把配置的*.do 的請求會被ActionServlet 攔截到,而訪問ActionServlet 時用到的默認編碼爲ISO8859-1 , 即是說設置了 request.setCharacterEncoding (“ ISO8859-1 ”) 。這時ActionServlet 在獲取參數時就會得到亂碼,ActionServlet 填充ActionForm 時就把亂碼填充進ActionForm 。當填充完畢後會把*.do 的請求派發給相應的Action, 而Action 從ActionForm 中獲取數據時自然就出現了亂碼。解決此問題很簡單,只需把spring 提供的一個過濾器類配置到web.xml 中,即增加如下代碼:
< filter >
< filter-name > encodingFilter </ filter-name >
< filter-class >
org.springframework.web.filter.CharacterEncodingFilter
</ filter-class >
< init-param >
< param-name > encoding </ param-name >
< param-value > UTF-8 </ param-value >
</ init-param >
</ filter >
< filter-mapping >
< filter-name > encodingFilter </ filter-name >
< url-pattern > /* </ url-pattern >
</ filter-mapping >
在以前我們是自己編寫此filter 類,而spring 爲我們提供了CharacterEncodingFilter 類,我們就自需配置即可。另要注意在配置時一定要指定一個encoding 參數名及值. 補充 :關於這裏的亂碼問題可參“struts1.x 深入學習筆記 - 四、2. 步驟五. 問題 ”。
7.OSIV
Spring 對於OpenSessionInView 也提供一個filter 類來解決此問題:
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
八、 SSJ 集成實例
1. 搭建環境
hibernate 核心包
/lib/bytecode/cglib/Hibernate-cglib-repack.jar
hibernate 註解包
hibernate-annotations.jar
Lib/Ejb3-persistence.jar 、hibernate-commons-annotations.jar
Hibernate 針對JPA 的實現包
Hibernate-entitymanager.jar
上面列舉的jar 包(基於hibernate 實現的JPA 包)+SSH 集成時的所有jar 包
2. 集成spring+JPA
建立spring_07_SSJ 項目 並導入相關ja 包,此項目基本參照上一個項目,只是把JPA 的實現加了進去。
步驟一、搭建JPA 開發環境
在src 下建立META-INF/persistence.xml 文件,此文件的主要內容如下:
< persistence
xmlns =
"http://java.sun.com/xml/ns/persistence"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version = "1.0" >
< persistence-unit name = "jpaDemo"
transaction-type = "RESOURCE_LOCAL" >
< provider > org.hibernate.ejb.HibernatePersistence </ provider >
< properties >
< property name = "hibernate.dialect"
value = "org.hibernate.dialect.MySQL5Dialect" />
< property name = "hibernate.hbm2ddl.auto" value = "update" />
< property name = "hibernate.show_sql" value = "true" />
< property name = "hibernate.format_sql" value = "true" />
<!-- 配置要連接的數據庫信息: -->
< property name = "hibernate.connection.driver_class"
value = "com.mysql.jdbc.Driver" />
< property name = "hibernate.connection.url"
value = "jdbc:mysql://localhost:3306/ssj" />
< property name = "hibernate.connection.username" value = "root" />
< property name = "hibernate.connection.password" value = "123456" />
</ properties >
</ persistence-unit >
</ persistence >
對應的持久化 User 的代碼如下:
package com.asm.entity;
@Entity
public class User {
@Id
@GeneratedValue
private Integer id ;
@Column (name= "u_name" )
private String name ;
public User() {
}
public User(String name) {
this . name = name;
}
... 省略 get/set 方法,特別注意要在實體在打上實體相關的標記: @Entity @Id
@GeneratedValue @Column
}
步驟二、把JPA 納入spring 容器管理
在beans.xml 中的配置如下:
< context:annotation-config />
< bean id = "localEMF"
class = "org.springframework.orm.jpa.LocalEntityManagerFactoryBean" >
< property name = "persistenceUnitName" value = "jpaDemo" />
</ bean >
< bean id = "txManager"
class = "org.springframework.orm.jpa.JpaTransactionManager" >
< property name = "entityManagerFactory" ref = "localEMF" />
</ bean >
< tx:annotation-driven transaction-manager = "txManager" />
說明:localEMF 實例bean 的使用是把JPA 的persistence.xml 配置的jpaDemo 持久化單元加進JPA 實體工廠管理( LocalEntityManagerFactoryBean 包裝了JPA 所使用的 EntityManager 對象的工廠類 EntityManagerFactoryBean )。再把工廠類加進事務管理器。
步驟三、UserServiceBean 的代碼如下:
package com.asm.service.impl;
public class UserServiceBean implements UserService {
@PersistenceContext
private EntityManager em ;
@Transactional
public void save(User user) {
em .persist(user);
}
... 省略其它的實現方法
}
說明: em 屬性依賴於 spring 容器注入。只要把此類交給 spring 容器管理, spring 容器會根據 @PersistenceContext 註解,來用配置的產生 EntityManager 對象的 JPA 工廠類來完成此對象的注入。 強調 的是在 save 、 update 方法上一定要打上事務註解標記。
步驟四、測試UserServiceBean 的業務方法
測試代碼同上一個項目的測試代碼
3. 集成struts :參上一個項目
4.OSIV 問題
在集成hibernate 時,OSIV 指的是sesion 關閉後的的解決方案。而在使用JPA 時,取代session 的是 EntityManager , 所以要解決因 EntityManager 關閉的OSIV 問題,應使用spring 提供的 org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
九、SSH2 集成實例
1. 搭建環境並集成hibenate
基本環境SSH 集成相同,只是要導入開發struts2 的開發包(在2.1.8 需要導入6 個基本包), 與hibenate 的集成可以前面的項目說明。
2. 集成strust2
步驟一、建立UserAction ,代碼如下:
package com.asm.action;
public class UserAction extends ActionSupport {
@Resource
private UserService userService ;
private String username ;
@Override
public String execute() throws Exception {
User user= new User( username );
userService .save(user);
return "success" ;
}
... 省略 username 的 get/set 方法
}
說明: userService 屬性通過 spring 注入。在 bean.xml 配置 UserAction 如下 :
< bean id = "userAction" class = "com.asm.action.UserAction" />
配置此action ,在src 建立struts.xml 文件,配置如下:
<? xml version = "1.0" encoding = "UTF-8" ?>
<! DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd" >
< struts >
< constant name = "struts.devMode" value = "true" />
< package name = "baseSSH" namespace = "/" extends = "struts-default" >
< action name = "add" class = "com.asm.action.UserAction" >
< result name = "success" > /success.jsp </ result >
</ action >
</ package >
</ struts >
步驟二、配置一個監聽器,實例化spring 容器,和集成struts1 相同。即在web.xml 中增加如下代碼:
< context-param >
< param-name > contextConfigLocation </ param-name >
< param-value > classpath:beans.xml </ param-value >
</ context-param >
< listener >
< listener-class >
org.springframework.web.context.ContextLoaderListener
</ listener-class >
</ listener >
步驟三、前臺index.jsp 頁面,代碼如下:
< form action = "<%= request.getContextPath() %> /add.action ">
用戶名: < input type = "text" name = "username" >
< input type = "submit" value = " 提交 " >
</ form >
步驟四、發佈測試。
十、整合總結:
十一、錯誤總結:
1. 使用new ClassPathXmlApplicationContext 對象時未傳遞 beans.xml 配置文件出錯: BeanFactory not initialized or already closed
另外如果 ctx 對象關閉後,再刷新也可以重新使用 ctx 對象來獲取 bean 實例
OpenSession 得到的session 不受Spring 容器管理
JPA 實體一定要有entity 註解標誌— 涉及到保存或更新操作時,一定要打上事務標記