Spring是一個非常優(yōu)秀的輕量級框架,通過Spring的IoC容器,我們的關(guān)注點(diǎn)便放到了需要實(shí)現(xiàn)的業(yè)務(wù)邏輯 上。對AOP的支持則能讓我們動態(tài)增強(qiáng)業(yè)務(wù)方法。編寫普通的業(yè)務(wù)邏輯Bean是非常容易而且易于測試的,因?yàn)樗苊撾xJ2EE容器(如 Servlet,jsp環(huán)境)單獨(dú)進(jìn)行單元測試。最后的一步便是在Spring框架中將這些業(yè)務(wù)Bean以XML配置文件的方式組織起來,它們就按照我們 預(yù)定的目標(biāo)正常工作了!非常容易!
本文將給出一個基本的Spring入門示例,并演示如何使用Spring的AOP將復(fù)雜的業(yè)務(wù)邏輯分離到每個方面中。
1.開發(fā)環(huán)境配置2.編寫B(tài)ean接口及其實(shí)現(xiàn)3.在Spring中配置Bean并獲得Bean的實(shí)例4.編寫Advisor以增強(qiáng) ServiceBean5.總結(jié)
1.開發(fā)環(huán)境配置首先,需要正確配置Java環(huán)境。推薦安裝JDK1.4.2,并正確配置環(huán)境變量:
JAVA_HOME=<JDK安裝目錄>CLASSPATH=.Path=%JAVA_HOME%"bin;……
我們將使用免費(fèi)的Eclipse 3.1作為IDE。新建一個Java Project,將Spring的發(fā)布包spring.jar以及commons-logging-1.0.4.jar復(fù)制到Project目錄下,并在 Project > Properties中配置好Java Build Path:
我們實(shí)現(xiàn)一個管理用戶的業(yè)務(wù)Bean。首先定義一個ServiceBean接口,聲明一些業(yè)務(wù)方法:
/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
/** * Interface of service facade. * * @author Xuefeng */public interface ServiceBean { void addUser(String username, String passWord); void deleteUser(String username); boolean findUser(String username); String getPassword(String username);}
然后在MyServiceBean中實(shí)現(xiàn)接口:
/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import java.util.*;
public class MyServiceBean implements ServiceBean {
private String dir; private Map map = new HashMap();
public void setUserDir(String dir) { this.dir = dir; System.out.println("Set user dir to: " + dir); }
public void addUser(String username, String password) { if(!map.containsKey(username)) map.put(username, password); else throw new RuntimeException("User already exist."); }
public void deleteUser(String username) { if(map.remove(username)==null) throw new RuntimeException("User not exist."); }
public boolean findUser(String username) { return map.containsKey(username); }
public String getPassword(String username) { return (String)map.get(username); }}
為了簡化邏輯,我們使用一個Map保存用戶名和口令。
現(xiàn)在,我們已經(jīng)有了一個業(yè)務(wù)Bean。要測試它非常容易,因?yàn)榈侥壳盀橹梗覀冞€沒有涉及到Spring容器,也沒有涉及到任何Web容器(假定這 是一個Web應(yīng)用程序關(guān)于用戶管理的業(yè)務(wù)Bean)。完全可以直接進(jìn)行Unit測試,或者,簡單地寫個main方法測試:
/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
public class Main {
public static void main(String[] args) throws Exception { ServiceBean service = new MyServiceBean(); service.addUser("bill", "hello"); service.addUser("tom", "goodbye"); service.addUser("tracy", "morning"); System.out.println("tom's password is: " + service.getPassword("tom")); if(service.findUser("tom")) { service.deleteUser("tom"); } }}
執(zhí)行結(jié)果:
我們已經(jīng)在一個main方法中實(shí)現(xiàn)了業(yè)務(wù),不過,將對象的生命周期交給容器管理是更好的辦法,我們就不必為初始化對象和銷毀對象進(jìn)行硬編碼,從而獲 得更大的靈活性和可測試性。
想要把ServiceBean交給Spring來管理,我們需要一個XML配置文件。新建一個beans.xml,放到src目錄下,確保在 classpath中能找到此配置文件,輸入以下內(nèi)容:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean id="service" class="com.crackj2ee.example.spring.MyServiceBean" /></beans>
以上XML聲明了一個id為service的Bean,默認(rèn)地,Spring為每個聲明的Bean僅創(chuàng)建一個實(shí)例,并通過id來引用這個 Bean。下面,我們修改main方法,讓Spring來管理業(yè)務(wù)Bean:
/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import org.springframework.beans.factory.xml.XmlBeanFactory;import org.springframework.core.io.ClassPathResource;
public class Main {
public static void main(String[] args) throws Exception { // init factory: XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml")); // use service bean: ServiceBean service = (ServiceBean)factory.getBean("service"); service.addUser("bill", "hello"); service.addUser("tom", "goodbye"); service.addUser("tracy", "morning"); System.out.println("tom's password is """ + service.getPassword("tom") + """"); if(service.findUser("tom")) { service.deleteUser("tom"); } // close factory: factory.destroySingletons(); }}
執(zhí)行結(jié)果:
由于我們要通過main方法啟動Spring環(huán)境,因此,首先需要初始化一個BeanFactory。紅色部分是初始化Spring的 BeanFactory的典型代碼,只需要保證beans.xml文件位于classpath中。
然后,在BeanFactory中通過id查找,即可獲得相應(yīng)的Bean的實(shí)例,并將其適當(dāng)轉(zhuǎn)型為合適的接口。
接著,實(shí)現(xiàn)一系列業(yè)務(wù)操作,在應(yīng)用程序結(jié)束前,讓Spring銷毀所有的Bean實(shí)例。
對比上一個版本的Main,可以看出,最大的變化是不需要自己管理Bean的生命周期。另一個好處是在不更改實(shí)現(xiàn)類的前提下,動態(tài)地為應(yīng)用程序增加 功能。
4.編寫Advisor以增強(qiáng)ServiceBean所謂AOP即是將分散在各個方法處的公共代碼提取到一處,并通過類似攔截器的機(jī)制實(shí)現(xiàn)代碼的動態(tài)織入。可以簡單地想象成,在某個方法的調(diào)用前、返回 前、調(diào)用后和拋出異常時,動態(tài)插入自己的代碼。在弄清楚Pointcut、Advice之類的術(shù)語前,不如編寫一個最簡單的AOP應(yīng)用來體驗(yàn)一下。
考慮一下通常的Web應(yīng)用程序都會有日志記錄,我們來編寫一個LogAdvisor,對每個業(yè)務(wù)方法調(diào)用前都作一個記錄:
/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import java.lang.reflect.Method;import org.springframework.aop.MethodBeforeAdvice;
public class LogAdvisor implements MethodBeforeAdvice { public void before(Method m, Object[] args, Object target) throws Throwable { System.out.println("[Log] " + target.getClass().getName() + "." + m.getName() + "()"); }}
然后,修改beans.xml:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans> <bean id="serviceTarget" class="com.crackj2ee.example.spring.MyServiceBean" />
<bean id="logAdvisor" class="com.crackj2ee.example.spring.LogAdvisor" />
<bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"><value>com.crackj2ee.example.spring.ServiceBean</value></property> <property name="target"><ref local="serviceTarget"/></property> <property name="interceptorNames"> <list> <value>logAdvisor</value> </list> </property> </bean></beans>
注意觀察修改后的配置文件,我們使用了一個ProxyFactoryBean作為service來與客戶端打交道,而真正的業(yè)務(wù)Bean即
MyServiceBean被聲明為serviceTarget并作為參數(shù)對象傳遞給ProxyFactoryBean,proxyInterfaces
指定了返回的接口類型。對于客戶端而言,將感覺不出任何變化,但卻動態(tài)加入了LogAdvisor,關(guān)系如下:
運(yùn)行結(jié)果如下,可以很容易看到調(diào)用了哪些方法:
要截獲指定的某些方法也是可以的。下面的例子將修改getPassword()方法的返回值:
/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;
public class PasswordAdvisor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { Object ret = invocation.proceed(); if(ret==null) return null; String password = (String)ret; StringBuffer encrypt = new StringBuffer(password.length()); for(int i=0; i<password.length(); i++) encrypt.append('*'); return encrypt.toString(); }}
這個PasswordAdvisor將截獲ServiceBean的getPassword()方法的返回值,并將其改為"***"。繼續(xù) 修改beans.xml:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"><beans> <bean id="serviceTarget" class="com.crackj2ee.example.spring.MyServiceBean" />
<bean id="logAdvisor" class="com.crackj2ee.example.spring.LogAdvisor" />
<bean id="passwordAdvisorTarget" class="com.crackj2ee.example.spring.PasswordAdvisor" />
<bean id="passwordAdvisor" class="org.springframework.aop.support.RegeXPMethodPointcutAdvisor"> <property name="advice"> <ref local="passwordAdvisorTarget"/> </property> <property name="patterns"> <list> <value>.*getPassword</value> </list> </property> </bean>
<bean id="service" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"><value>com.crackj2ee.example.spring.ServiceBean</value></property> <property name="target"><ref local="serviceTarget"/></property> <property name="interceptorNames"> <list> <value>logAdvisor</value> <value>passwordAdvisor</value> </list> </property> </bean></beans>
利用Spring提供的一個RegexMethodPointcutAdvisor可以非常容易地指定要截獲的方法。運(yùn)行結(jié)果如下,可以看到返回結(jié)果變
為"******":
還需要繼續(xù)增強(qiáng)ServiceBean?我們編寫一個ExceptionAdvisor,在業(yè)務(wù)方法拋出異常時能做一些處理:
/** * Copyright_2006, Liao Xuefeng * Created on 2006-3-9 * For more information, please visit: http://www.crackj2ee.com */package com.crackj2ee.example.spring;
import org.springframework.aop.ThrowsAdvice;
public class ExceptionAdvisor implements ThrowsAdvice { public void afterThrowing(RuntimeException re) throws Throwable { System.out.println("[Exception] " + re.getMessage()); }}
將此Advice添加到beans.xml中,然后在業(yè)務(wù)Bean中刪除一個不存在的用戶,故意拋出異常:
service.deleteUser("not-exist");
再次運(yùn)行,注意到ExceptionAdvisor記錄下了異常:
利用Spring非常強(qiáng)大的IoC容器和AOP功能,我們能實(shí)現(xiàn)非常靈活的應(yīng)用,讓Spring容器管理業(yè)務(wù)對象的生命周期,利用AOP增強(qiáng)功能, 卻不影響業(yè)務(wù)接口,從而避免更改客戶端代碼。
為了實(shí)現(xiàn)這一目標(biāo),必須始終牢記:面向接口編程。而Spring默認(rèn)的AOP代理也是通過Java的代理接口實(shí)現(xiàn)的。雖然Spring也可以用 CGLIB實(shí)現(xiàn)對普通類的代理,但是,業(yè)務(wù)對象只要沒有接口,就會變得難以擴(kuò)展、維護(hù)和測試。
歡迎來信與作者交流:asklxf@163.com
可以從此處下載完整的Eclipse工程:
springbasic.rar
(出處:http://www.jzwiki.com/article_1215945431010.shtml#)