Spring是一個非常優秀的輕量級框架,通過Spring的IoC容器,我們的關注點便放到了需要實現的業務邏輯上。對AOP的支持則能讓我們動態增強業務方法。編寫普通的業務邏輯Bean是非常容易而且易于測試的,因為它能脫離J2EE容器(如Servlet,JSP環境)單獨進行單元測試。最后的一步便是在Spring框架中將這些業務Bean以XML配置文件的方式組織起來,它們就按照我們預定的目標正常工作了!非常容易! 本文將給出一個基本的Spring入門示例,并演示如何使用Spring的AOP將復雜的業務邏輯分離到每個方面中。 1.開發環境配置 2.編寫Bean接口及其實現 3.在Spring中配置Bean并獲得Bean的實例 4.編寫Advisor以增強ServiceBean 5.總結 1.開發環境配置首先,需要正確配置Java環境。推薦安裝JDK1.4.2,并正確配置環境變量: JAVA_HOME=<JDK安裝目錄> CLASSPATH=. Path=%JAVA_HOME%\\bin;…… 我們將使用免費的Eclipse 3.1作為IDE。新建一個Java Project,將Spring的發布包spring.jar以及commons-logging-1.0.4.jar復制到Project目錄下,并在Project > Properties中配置好Java Build Path: ![src=uppic/20060407/1405270.jpg]()
2.編寫Bean接口及其實現我們實現一個管理用戶的業務Bean。首先定義一個ServiceBean接口,聲明一些業務方法: /** ?* 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中實現接口: /** ?* 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保存用戶名和口令。 現在,我們已經有了一個業務Bean。要測試它非常容易,因為到目前為止,我們還沒有涉及到Spring容器,也沒有涉及到任何Web容器(假定這是一個Web應用程序關于用戶管理的業務Bean)。完全可以直接進行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"); ??????? } ??? } } 執行結果:
![src=uppic/20060407/1405271.jpg]() 3.在Spring中配置Bean并獲得Bean的實例我們已經在一個main方法中實現了業務,不過,將對象的生命周期交給容器管理是更好的辦法,我們就不必為初始化對象和銷毀對象進行硬編碼,從而獲得更大的靈活性和可測試性。 想要把ServiceBean交給Spring來管理,我們需要一個XML配置文件。新建一個beans.xml,放到src目錄下,確保在classpath中能找到此配置文件,輸入以下內容: <?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,默認地,Spring為每個聲明的Bean僅創建一個實例,并通過id來引用這個Bean。下面,我們修改main方法,讓Spring來管理業務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(); ??? } } 執行結果: ?![src=uppic/20060407/1405272.jpg]() 由于我們要通過main方法啟動Spring環境,因此,首先需要初始化一個BeanFactory。紅色部分是初始化Spring的BeanFactory的典型代碼,只需要保證beans.xml文件位于classpath中。 然后,在BeanFactory中通過id查找,即可獲得相應的Bean的實例,并將其適當轉型為合適的接口。 接著,實現一系列業務操作,在應用程序結束前,讓Spring銷毀所有的Bean實例。 對比上一個版本的Main,可以看出,最大的變化是不需要自己管理Bean的生命周期。另一個好處是在不更改實現類的前提下,動態地為應用程序增加功能。 4.編寫Advisor以增強ServiceBean所謂AOP即是將分散在各個方法處的公共代碼提取到一處,并通過類似攔截器的機制實現代碼的動態織入。可以簡單地想象成,在某個方法的調用前、返回前、調用后和拋出異常時,動態插入自己的代碼。在弄清楚Pointcut、Advice之類的術語前,不如編寫一個最簡單的AOP應用來體驗一下。 考慮一下通常的Web應用程序都會有日志記錄,我們來編寫一個LogAdvisor,對每個業務方法調用前都作一個記錄: /** ?* 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來與客戶端打交道,而真正的業務Bean即MyServiceBean被聲明為serviceTarget并作為參數對象傳遞給ProxyFactoryBean,proxyInterfaces指定了返回的接口類型。對于客戶端而言,將感覺不出任何變化,但卻動態加入了LogAdvisor,關系如下: ?![src=uppic/20060407/1405273.jpg]() 運行結果如下,可以很容易看到調用了哪些方法: ?![src=uppic/20060407/1405274.jpg]() 要截獲指定的某些方法也是可以的。下面的例子將修改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()方法的返回值,并將其改為***。繼續修改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可以非常容易地指定要截獲的方法。運行結果如下,可以看到返回結果變為******: ?![src=uppic/20060407/1405275.jpg]() 還需要繼續增強ServiceBean?我們編寫一個ExceptionAdvisor,在業務方法拋出異常時能做一些處理: /** ?* 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中,然后在業務Bean中刪除一個不存在的用戶,故意拋出異常: service.deleteUser("not-exist"); 再次運行,注意到ExceptionAdvisor記錄下了異常: ?![src=uppic/20060407/1405276.jpg]() 5.總結利用Spring非常強大的IoC容器和AOP功能,我們能實現非常靈活的應用,讓Spring容器管理業務對象的生命周期,利用AOP增強功能,卻不影響業務接口,從而避免更改客戶端代碼。 為了實現這一目標,必須始終牢記:面向接口編程。而Spring默認的AOP代理也是通過Java的代理接口實現的。雖然Spring也可以用CGLIB實現對普通類的代理,但是,業務對象只要沒有接口,就會變得難以擴展、維護和測試。 |