常言笑的家

          Spring, Hibernate, Struts, Ajax, RoR

          Spring Aop Step-By-Step 學習筆記(上)

          最近由于工作需要,要求掌握關于 Spring 方面的東西。所以花了兩個星期的時間來學習 Spring 的基本知識,主要包括 Ioc Aop 兩方面。
          本文為筆者的 Spring Aop 方面的學習筆記,主要結合了 Spring In Action 第三章 Spring-Reference 第五章 為學習向導。根據自己的理解和書中的實例來一步一步完成對于在 Spring Aop 方面的編程。其中基礎部分 Ioc 需要讀者自己參考資料了解,本文將不做描述。
          說明:我將盡量縮短程序長度,在程序部分將減少注釋說明,重點要讀者自己根據上下文和程序結果理解體會,具體 api 信息請讀者自己參考 Spring-api 文檔和相關資料。
          一.    準備工作:
           
          1.  開發環境:
          l             適合人群:
                            要了解 Spring Ioc ,對 Spring- Aop 可以不了解或者僅僅熟悉 Aop 概念,未參與 Spring Aop 開發實戰的初學者。同時也希望高手對于本文的不足或理解錯誤之處給予指點,謝謝。
          l             開發環境:
          JDK 1.4_2
          l             開發工具:
          Eclipse 3.12 (未采用任何插件,主要是為初學者熟悉和理解 xml 文檔的配置)
          l             所需組件:
          Spring-Framework-1.2.8
          下載地址:
           
          2.    建立工程:
             首先用 Eclpse 建立一個普通 java 項目,導入 jar 文件到編譯環境中,如下:
          a)             Spring.jar Spring 的核心 jar 文件,必須;
          b)             Commons-loggin.jar 日志文件,必須;
          c)             Cglib.jar 動態代理文件,不是必須(本文需要);
          d)             Jak-oro.jar 使用 Perl Awk 正則表達式進行文本解析工具,不是必須(本文需要);
          建立工程如下:

          好了,下來我們開始我們的 Spring-aop 之旅;
           
          二.  Spring -Aop 入門
           
          AOP 全名 Aspect-oriented programming Spring framework 是很有前途的 AOP 技術。作為一種非侵略性的,輕型的 AOP framework ,你無需使用預編譯器或其他的元標簽,便可以在 Java 程序中使用它。這意味著開發團隊里只需一人要對付 AOP framework ,其他人還是像往常一樣編程。
          關鍵性概念:
          1)          Advice 是代碼的具體實現,例如一個實現日志記錄的代碼。
          2)          Pointcut 是在將 Advice 插入到程序的條件。
          3)           advisor 是把 pointcut advice 的組合在一起裝配器。
           
          圖例:
          你的程序可能如上,現在要在三個流程上同時加入日志控制和權限控制,如下:
           

           你的程序可能如上,現在要在三個流程上同時加入日志控制和權限控制,如下:

           

           

                   其中拿日志為例,日志控制和流程之間的穿插點處叫做連接點( Joinpoint ),而 Advice 就是我們日志處理的具體代碼, Pointcut 就是定義一個規則,對三個和業務有關的連接點進行過濾和匹配(例如我們對于業務 1 不做日志處理)。 Advisor 就是將符合的規則的剩下的兩個連接點和具體的日志記錄代碼組合在一起。
           
          三.  Spring-Aop 前置通知、后置通知、環繞通知、異常通知實現
           
          我以 Spring In Action 提供的例子進行二次改造,完成我們自己的流程。業務流程很簡單,顧客到商店買東西,店員根據顧客的需要給于顧客提供服務。實現方法前插入,方法后插入,環繞,異常四種。
           
                 代碼:
                    建立一個用戶類;
          public   class  Customer  {
               
          private  String name  =   " 悠~游! " ;
               
          public  String getName()  {
                    
          return  name;
               }

          }

          三個產品
          public class Cheese {
             
          public String toString()
                   
          return "奶酪!";
             }

          }

          public class Pepper {
               
          public String toString()
                    
          return "胡椒粉!";
               }

          }

          public class Squish {
               
          public String toString()
                    
          return "果醬!";
               }

          }

                    建立一個接口;
          public   interface  KwikEMart  {
               Squish buySquish(Customer customer) 
          throws  KwikEMartException;
               Pepper buyPepper(Customer customer) 
          throws  KwikEMartException;
               Cheese buyCheese(Customer customer) 
          throws  KwikEMartException;
          }
          實現這個接口,我們實現三個方法,買奶酪,買胡椒粉,買果醬;
          public   class  ApuKwikEMart  implements  KwikEMart  {
               
          private   boolean  cheeseIsEmpty  =   false ;

               
          private   boolean  pepperIsEmpty  =   false ;

               
          private   boolean  squishIsEmpty  =   false ;

               
          public  Cheese buyCheese(Customer customer)  throws  NoMoreCheeseException {

                    
          if  (cheeseIsEmpty)  {
                         
          throw   new  NoMoreCheeseException();
                    }


                    Cheese s 
          =   new  Cheese();
                    System.out.println(
          " --我想買: "   +  s);
                    
          return  s;

               }


               
          public  Pepper buyPepper(Customer customer)  throws  NoMorePepperException {

                    
          if  (pepperIsEmpty)  {
                         
          throw   new  NoMorePepperException();
                    }


                    Pepper s 
          =   new  Pepper();
                    System.out.println(
          " --我想買: "   +  s);
                    
          return  s;

               }


               
          public  Squish buySquish(Customer customer)  throws  NoMoreSquishException {

                    
          if  (squishIsEmpty)  {
                         
          throw   new  NoMoreSquishException();
                    }


                    Squish s 
          =   new  Squish();
                    System.out.println(
          " --我想買: "   +  s);
                    
          return  s;

               }


               
          public   void  setCheeseIsEmpty( boolean  cheeseIsEmpty)  {
                    
          this .cheeseIsEmpty  =  cheeseIsEmpty;
               }


               
          public   void  setPepperIsEmpty( boolean  pepperIsEmpty)  {
                    
          this .pepperIsEmpty  =  pepperIsEmpty;
               }


               
          public   void  setSquishIsEmpty( boolean  squishIsEmpty)  {
                    
          this .squishIsEmpty  =  squishIsEmpty;
               }


          }

          環繞通知的實現,必須實現invoke方法,通過調用invoke.proceed()手工調用對象方法:
          public   class  OnePerCustomerInterceptor  implements  MethodInterceptor  {
               
               
          private  Set customers = new  HashSet();

               
          public  Object invoke(MethodInvocation invoke)  throws  Throwable  {
                    
                    Customer customer
          = (Customer)invoke.getArguments()[ 0 ];
                    
          if (customers.contains(customer))
                         
          throw   new  KwikEMartException( " One per customer. " );
                    }

                    
                    System.out.println(
          " 店員: " + customer.getName()  +   "  ,Can I help you ? " );
                    Object squishee
          = invoke.proceed();  // 手工調用對象方法;
                    System.out.println( " 店員:OK!  "   +  customer.getName()  +   " .give you!  "  );
                    
                    customers.add(squishee);

                    
          return  squishee;
               }

          }

          前置通知的實現;
          public class WelcomeAdvice implements MethodBeforeAdvice {

               
          public void before(Method method, Object[] args, Object target) throws Throwable {
                    
                    Customer customer 
          = (Customer) args[0];
                    System.out.println(
          "店員::Hello " + customer.getName() + " . How are you doing?");

               }

          }

          public class Customer {
               
          private String name = "悠~游!";
               
          public String getName() {
                    
          return name;
               }

          }
          后置通知實現;
          public class ThankYouAdvice implements AfterReturningAdvice {

               
          public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
                    
                    Customer customer 
          = (Customer) args[0];
                    System.out.println(
          "店員:Thank you " + customer.getName() + " . Come again! " );

               }

          }
          系統異常處理通知實現;
          public   class  KwikEmartExceptionAdvice  implements  ThrowsAdvice  {

               
          public   void  afterThrowing(NoMoreSquishException e)  {
                    System.out.println(
          " 系統:NoMoreSquisheesException異常截獲了:  "   +  e.getMessage());
               }

               
          public   void  afterThrowing(NoMoreCheeseException e)  {
                    System.out.println(
          " 系統:NoMoreCheeseException異常截獲了:  "   +  e.getMessage());
               }

               
          public   void  afterThrowing(NoMorePepperException e)  {
                    System.out.println(
          " 系統:NoMorePepperException異常截獲了:  "   +  e.getMessage());
               }

          }

          自定義的異常接口;
          public class KwikEMartException extends Exception {

               
          private static final long serialVersionUID = -3962577696326432053L;

               String retValue 
          = "KwikEMartException 異常!";

               
          public KwikEMartException(String name) {
                    retValue 
          = name;
               }

               
          public KwikEMartException() {

               }

               
          public String getMessage() {
                    
          return retValue;
               }


          }
          沒有更多的奶酪異常;
          public class NoMoreCheeseException extends KwikEMartException {
               
          private static final long serialVersionUID = -3961123496322432053L;

               String retValue 
          = "NoMoreCheeseException 異常!";

               
          public NoMoreCheeseException() {
                    
          super();
               }


               
          public NoMoreCheeseException(String name) {
                    
          super(name);
               }


               
          public String getMessage() {

                    
          return retValue;
               }

          }
          沒有更多的胡椒粉異常;
          public   class  NoMorePepperException  extends  KwikEMartException  {
               
          private   static   final   long  serialVersionUID  =   - 3961234696322432053L ;

               String retValue 
          =   " NoMorePepperException 異常! " ;

               
          public  NoMorePepperException()  {
                    
          super ();
               }


               
          public  NoMorePepperException(String name)  {
                    
          super (name);
               }


               
          public  String getMessage()  {

                    
          return  retValue;
               }

          }

          沒有更多的果醬異常;
          public   class  NoMoreSquishException  extends  KwikEMartException  {
               
          private   static   final   long  serialVersionUID  =   - 3121234696322432053L ;

               String retValue 
          =   " NoMoreSquishException 異常! " ;

               
          public  NoMoreSquishException()  {
                    
          super ();
               }


               
          public  NoMoreSquishException(String name)  {
                    
          super (name);
               }


               
          public  String getMessage()  {

                    
          return  retValue;
               }

          }
          運行實例類;
          public class RunDemo {

                         
          public static void kwikEMart() {

                         ClassPathXmlApplicationContext context 
          = new ClassPathXmlApplicationContext("demo/kwikemart.xml");

                              
          //如果你想通過類來引用這個的話,就要用到CGLIB.jar了,同時在代理工廠里面設置:
                              
          //<property name="proxyTargetClass" value="true" />
                              KwikEMart akem = (KwikEMart) context.getBean("kwikEMart");
               
                              
          try {
                                   akem.buySquish(
          new Customer());
                                   akem.buyPepper(
          new Customer());
                                   akem.buyCheese(
          new Customer());
                              }
           catch (KwikEMartException e) {
                                   
          //異常已經被截獲了,不信你看控制臺!~;
                              }

                         }


                         
          public static void main(String[] args) {
                              kwikEMart();
                         }

          }

          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="kwikEMartTarget" class="demo.ApuKwikEMart">
                    
          <!-- 
                         把這里注釋去掉的話,程序調用的時候測試異常通知;
                         <property name="cheeseIsEmpty">
                              <value>true</value>
                         </property>
                         <property name="pepperIsEmpty">
                              <value>true</value>
                         </property>
                         <property name="squishIsEmpty">
                              <value>true</value>
                         </property>
                    
          -->
               
          </bean>

               
          <!-- 方法調用前通知 -->
               
          <bean id="welcomeAdvice" class="demo.advice.WelcomeAdvice" />
               
          <!-- 方法調用后通知 -->
               
          <bean id="thankYouAdvice" class="demo.advice.ThankYouAdvice" />
               
          <!-- 環繞調用通知 -->
               
          <bean id="onePerCustomerInterceptor" class="demo.advice.OnePerCustomerInterceptor" />
               
          <!-- 異常調用通知 -->
               
          <bean id="kwikEmartExceptionAdvice" class="demo.advice.KwikEmartExceptionAdvice" />
               
               
          <bean id="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean">
                    
          <property name="proxyInterfaces" value="demo.KwikEMart" />
                    
          <property name="interceptorNames">
                         
          <list>
                              
                                   
          <value>welcomeAdvice</value>
                                   
          <value>thankYouAdvice</value>
                                   
          <value>onePerCustomerInterceptor</value>
                                   
          <value>kwikEmartExceptionAdvice</value>
                              
                         
          </list>
                    
          </property>
                    
          <property name="target">
                         
          <ref bean="kwikEMartTarget" />
                    
          </property>
               
          </bean>

          </beans>

          這個例子東西很多,不過每個類的代碼都不大。如果你對 org.springframework.aop.framework.ProxyFactoryBean 不是很了解的話可以看我下篇尾處的介紹。 讀清楚之后,我們運行RunDemo 類,查看控制臺結果,如下:
           
          店員::Hello 悠~游! . How are you doing?
          店員:悠~游! ,Can I help you ?
          -- 我想買:果醬!
          店員:OK! 悠~游!.give you!
          店員:Thank you 悠~游! . Come again!
          店員::Hello 悠~游! . How are you doing?
          店員:悠~游! ,Can I help you ?
          -- 我想買:胡椒粉!
          店員:OK! 悠~游!.give you!
          店員:Thank you 悠~游! . Come again!
          店員::Hello 悠~游! . How are you doing?
          店員:悠~游! ,Can I help you ?
          -- 我想買:奶酪!
          店員:OK! 悠~游!.give you!
          店員:Thank you 悠~游! . Come again!
           
              我們將 kwikEMartTarget 里面的注釋去掉,測試異常實現,如下:
              
          店員::Hello 悠~游! . How are you doing?
          店員:悠~游! ,Can I help you ?
          系統:NoMoreSquisheesException異常截獲了: NoMoreSquishException 異常!
               好好理解一下,我就不廢話了,我們進行下一節。
           
           
           
          四. Spring-Aop Pointcut+advice+Advisor 實現
           
          我們修改我們的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="kwikEMartTarget" class="demo.ApuKwikEMart"></bean>

               
          <!-- 環繞調用通知 -->
               
          <bean id="onePerCustomerInterceptor" class="demo.advice.OnePerCustomerInterceptor" />

               
          <!-- 使用NameMatchMethodPointcut -->
               
          <bean id="nameMatchfilterPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
                    
          <property name="mappedName">
                         
          <!-- 
                              buy.* 以buy開頭的方法;
                         
          -->
                         
          <value>buy*</value>
                    
          </property>
               
          </bean>

               
          <!-- 使用Perl5RegexpMethodPointcut -->
               
          <bean id="regexpFilterPointcut" class="org.springframework.aop.support.Perl5RegexpMethodPointcut">
                    
          <property name="pattern">
                         
          <!-- 
                              .*buy.+ 以buy開頭的方法;
                              .*buyS.+ 以buyS開頭的方法;
                         
          -->
                         
          <value>.*suy.+</value>
                    
          </property>
               
          </bean>

               
          <bean id="runDemofilterPointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
                    
          <property name="pointcut">
                         
          <ref bean="nameMatchfilterPointcut" />
                    
          </property>
                    
          <property name="advice">
                         
          <ref bean="onePerCustomerInterceptor" />
                    
          </property>
               
          </bean>
               
          <bean id="kwikEMart" class="org.springframework.aop.framework.ProxyFactoryBean">
                    
          <property name="proxyInterfaces" value="demo.KwikEMart" />
                    
          <property name="interceptorNames">
                         
          <list>
                              
          <value>runDemofilterPointcutAdvisor</value>
                         
          </list>
                    
          </property>
                    
          <property name="target">
                         
          <ref bean="kwikEMartTarget" />
                    
          </property>
               
          </bean>

          </beans>
           
          運行,結果如下:
          店員:悠~游! ,Can I help you ?
          --我想買:果醬!
          店員:OK! 悠~游!.give you!
          店員:悠~游! ,Can I help you ?
          --我想買:胡椒粉!
          店員:OK! 悠~游!.give you!
          店員:悠~游! ,Can I help you ?
          --我想買:奶酪!
                  店員:OK! 悠~游!.give you!
           
          在這里簡單說明一下xml文檔:nameMatchfilterPointcutregexpFilterPointcut 是我們自己定義好規則的Pointcut。nameMatchfilterPointcut 根據mappedName來設置過濾規則, regexpFilterPointcut則是用pattern來設置過濾規則。runDemofilterPointcutAdvisor則將我們的Pointcutadvice組合在一起。
          讀者可以自己修改runDemofilterPointcutAdvisorpointcut來切換不同的Pointcut。如果需要RegexpMethodPointcut 的子類的實現,必須要oro包支持。(注:RegexpMethodPointcut有倆個子類的實現,JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut)。
               但是,如果我們想讓我們的Advisor同時實現多個Pointcut+advice怎么辦呢?利用Spring In Action里面的實例,我們來自己實現我們的Pointcut
           
              代碼:
           
           
          public class MyUnionPointcut implements Pointcut {
               
               
          private Pointcut delegate;

               
          public ClassFilter getClassFilter() {
                    
          return getDelegate().getClassFilter();
               }


               
          private Pointcut getDelegate() {
                    
          if (delegate == null{
                         
          throw new AopConfigException("No pointcuts have been configured.");
                    }

                    
          return delegate;
               }


               
          public MethodMatcher getMethodMatcher() {
                    
          return getDelegate().getMethodMatcher();
               }


               
          public void setPointcuts(List pointcuts) {

                    
          if (pointcuts == null || pointcuts.size() == 0{
                         
          throw new AopConfigException("Must have at least one Pointcut.");
                    }

                    delegate 
          = (Pointcut) pointcuts.get(0);

                    
          for (int i = 1; i < pointcuts.size(); i++{
                         Pointcut pointcut 
          = (Pointcut) pointcuts.get(i);
                         delegate 
          = Pointcuts.union(delegate, pointcut);
                    }


               }

          }

          其實就是繼承Pointcut類,實現getMethodMatcher()方法即可,接下來看我們把那兩個Pointcut組合成一個,當其中一個Pointcut滿足的時候就返回true,調用我們的Advice。
           
          在xml中,加入下面代碼:
          <bean id="myUnionPointcut" class="demo.utils.MyUnionPointcut">
                    
          <property name="pointcuts">
                         
          <list>
                         
          <!-- 這里說明一下:動態切入點和靜態切入點集合在一起的時候好像有問題? -->
                              
          <ref bean="nameMatchfilterPointcut" />
                              
          <ref bean="regexpFilterPointcut" />
                         
          </list> 
                    
          </property>
               
          </bean>
          修改runDemofilterPointcutAdvisor的pointcut來加入我們組合好的Pointcut。

               
          <bean id="runDemofilterPointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
                    
          <property name="pointcut">
                         
          <ref bean=”myUnionPointcut" />
                    
          </property>
                    
          <property name="advice">
                         
          <ref bean="onePerCustomerInterceptor" />
                    
          </property>
               
          </bean>
           
           
              
          同時在運行前,讀者可以自己修改兩個pointcut的匹配方法,來匹配更多的可選項。同時可以參考的一個類是org.springframework.aop.support.UnionPointcut,它實現兩個pointcut的聯合。如果讀者想實現更加靈活的匹配,需要自己來定義自己的pointcut。(如第一個pointcut交叉第二個pointcut,聯合第三個pointcut),交叉的工具類為ComposablePointcut。
          運行的結果請讀者自己試驗。這個時候您可能在想,這些pointcut都是Spring自己的實現,我們能否自己來定義我們自己規則的pointcut呢?當然可以!~
          代碼:
          /**
           * 
           * 自己定義的靜態切入點
           * 滿足條件是:當傳入的數字大于1塊錢的時候才可以;
           * 
           * 
          @author 悠~游
           * 
          @since 2006-06-22
           * 
          @link www.uusam.com
           
          */

          public class MyPointcut extends StaticMethodMatcherPointcut implements Serializable {

               
          private static final long serialVersionUID = -101281038294508751L;

               
          private int money = 0;

               
          /**
                * 實現方法,業務邏輯為:根據輸入的錢數和動作(必須是以buy開頭的方法),來確定是否有錢購買商品,買完之后去掉商品的價格;
                
          */

               
          public boolean matches(Method method, Class targetClass) {
                    
          if (method.getName().indexOf("buyCheese"== 0 && money >= 100{
                         money 
          -= 100;
                         
          return true;
                    }
           else if (method.getName().indexOf("buyPepper"== 0 && money >= 5{
                         money 
          -= 5;
                         
          return true;
                    }
           else if (method.getName().indexOf("buySquish"== 0 && money >= 10{
                         money 
          -= 10;
                         
          return true;
                    }

                    System.out.println(
          "門衛:你要買的東西太貴,你的錢 "+money+" 太少!~ ,取消服務!");
                    
          return false;
               }


               
          public void setMoney(int money) {
                    
          this.money = money;
               }


          }

          這個就是我們自己定義的靜態Pointcut,主要實現自己的matches方法。看看如何加入到我們的XML文檔中:
          <!-- 使用自定義的切入點 -->
               
          <bean id="myPointcut" class="demo.pointcut.MyPointcut">
                    
          <property name="money">
                         
          <value>100</value>
                    
          </property>
               
          </bean>
              
           
          很簡單不是么?我們定義一個數字,就是用戶的money,進入商店時候兜里的錢^_^。同樣修改我們的myUnionPointcut里面的pointcuts,加入我們的pointcut。
           
          <bean id="myUnionPointcut" class="demo.utils.MyUnionPointcut">
                    
          <property name="pointcuts">
                         
          <list>
                              
          <!—上面兩個要設置成不通過哦,或者索性就去掉先。 -->
                              
          <ref bean="nameMatchfilterPointcut" />
                              
          <ref bean="regexpFilterPointcut" />
          <ref bean="myPointcut" />
                         
          </list> 
                    
          </property>
               
          </bean>
           
              
          當上面兩個Pointcut定義的規則不通過的時候,程序開始校驗我們的myPointcut。運行,結果如下:
          店員:悠~游! ,Can I help you ?
          --我想買:果醬!
          店員:OK! 悠~游!.give you!
          店員:悠~游! ,Can I help you ?
          --我想買:胡椒粉!
          店員:OK! 悠~游!.give you!
          門衛:你要買的東西太貴,你的錢 85 太少!~ , 取消服務!
          --我想買:奶酪!//服務員沒了...
              
               好了,是不是我們想要的結果呢?呵呵。
               同時,Spring 提供動態Pointcut。關于動態的說明我就不在熬述了,我們這里只關心具體Spring帶給我們的具體實現方法,具體應用請讀者自己斟酌使用。
              
           
              
          <!-- 定制動態接入點,來判斷當前線程堆棧中是否有demo.RunDemo這個類; -->
               
          <bean id="runDemoPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
                    
          <constructor-arg>
                         
          <value>demo.RunDemo</value>
                    
          </constructor-arg>
               
          </bean>
               修改下面的引用我們的動態Pointcut;
          <bean id="myUnionPointcut" class="demo.utils.MyUnionPointcut">
                    
          <property name="pointcuts">
          <list>
               
          <ref bean=" runDemoPointcut " />
                   
          </list> 
              
          </property>
          </bean>
           
               運行,結果如下:
          店員:悠~游! ,Can I help you ?
          --我想買:果醬!
          店員:OK! 悠~游!.give you!
          店員:悠~游! ,Can I help you ?
          --我想買:胡椒粉!
          店員:OK! 悠~游!.give you!
          店員:悠~游! ,Can I help you ?
          --我想買:奶酪!
          店員:OK! 悠~游!.give you!
              
               動態切入點是根據當前堆棧信息進行方法匹配的一種規則,讀者可以自己修改demo.RunDemo,如java.lang.Integer,來看看結果。
           
          --我想買:果醬!
          --我想買:胡椒粉!
          --我想買:奶酪!
              
               到這里能夠讀下來已經很不容易了,呵呵。還是站起來走動一下吧,接下來我們將搞定其他的一些東東。

          posted on 2006-12-19 13:12 常言笑 閱讀(394) 評論(0)  編輯  收藏 所屬分類: JAVA/J2EE

          My Links

          Blog Stats

          常用鏈接

          留言簿(5)

          隨筆分類

          隨筆檔案

          搜索

          積分與排名

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 茂名市| 剑河县| 甘谷县| 芮城县| 静乐县| 龙南县| 三原县| 三门县| 丰城市| 建湖县| 通城县| 米林县| 麦盖提县| 镇原县| 巧家县| 蕲春县| 黄浦区| 商丘市| 沛县| 铜川市| 定远县| 教育| 安化县| 阜康市| 五华县| 永济市| 淄博市| 洛扎县| 平顺县| 尉氏县| 土默特右旗| 浦东新区| 克拉玛依市| 健康| 敖汉旗| 宁陵县| 弋阳县| 东源县| 凭祥市| 岳阳市| 会昌县|