隨筆-28  評論-15  文章-81  trackbacks-0
          Spring的AOP(Aspect Oriented Programming)框架允許你將分散在系統(tǒng)中的功能放到一個地方—切面。依賴Spring的強大切入點機制,何時何地在系統(tǒng)中采用切面你有很多種選擇。在本章中作者向我們介紹并且展示了Spring AOP基礎(chǔ)的方方面面。
          一、AOP介紹
          AOP提供了另外一種思考程序結(jié)構(gòu)的角度,彌補了OOP(面向?qū)ο缶幊蹋┑牟蛔恪4蠹铱梢詤⒖聪年烤帉懙摹禨pring開發(fā)指南》中的AOP部分,有簡單明晰的對比和解釋。就像剛開始理解OO概念一樣,對于新手來說AOP也是非常抽象難以理解的,不能僅從一個概念上去定義AOP。假如我們有一個系統(tǒng),分為好多個模塊,每個模塊都負責(zé)處理一項重要的功能。但是每個模塊都需要一些相似的輔助功能如安全、日志輸出等等。這就是一種交叉業(yè)務(wù),而這種“交叉”非常適合用AOP來解決。(在AOP剛出現(xiàn)的時候被大家翻譯成面向方面,而后來一部分人翻譯成面向切面。誰更正確?也許在真正理解了AOP以后才能判斷出來。)
          1.AOP術(shù)語
          l         切面(Aspect):切面是你要實現(xiàn)的交叉功能。它是應(yīng)用系統(tǒng)模塊化的一個切面領(lǐng)域。
          l         連接點(Join point):連接點是應(yīng)用程序執(zhí)行過程中插入切面的地點(在程序執(zhí)行過程中某個特定的點)。在Spring AOP中一個連接點代表一個方法的執(zhí)行。這個點可以是方法調(diào)用,異常拋出或者是要修改的字段。通過申明一個import org.aspectj.lang.JoinPoint類型的參數(shù)可以使通知(Advice)的主體部分獲得連接點信息。
          l         通知Advice):通知切面的實際實現(xiàn)(參考手冊:Action taken by an aspect at a particular join point.)。它通知應(yīng)用系統(tǒng)新的行為。通知包括好多種類,在后面單獨列出。(Advice一詞在夏昕的《Spring開發(fā)指南》中被翻譯為處理邏輯
          l         切入點(Pointcut):切入點定義了通知應(yīng)該應(yīng)用在哪些連接點。通知(Advice)可以應(yīng)用到AOP的任何連接點。通知(Advice)將和一個切入點表達式關(guān)聯(lián),并在滿足這個連接點的切入點上運行(例如:在執(zhí)行一個特定名稱的方法時)切入點表達式如何和連接點匹配是AOP的核心Spring使用缺省的AspectJ切入點的語法。
          l         引入(Introduction):(也被叫做內(nèi)部類型聲明“inter-type declaration”)引入允許你為已經(jīng)存在的類添加新的方法和屬性。Spring允許引入新的接口(以及一個對應(yīng)的實現(xiàn))到任何被代理的對象。
          l         目標對象(Target Object):目標對象是被通知對象。Spring AOP是運行時代里實現(xiàn)的,所以這個對象永遠是一個被代理對象。
          l         AOP代理(AOP Proxy):代理是將通知(Advice)應(yīng)用到目標對象后創(chuàng)建的對象。在Spring2.0中對于使用最新引入的基于模式(schema-based)風(fēng)格和@AspectJ風(fēng)格切面聲明的用戶來說,代理的創(chuàng)建是透明的。(要理解這里的代理“Proxy”,需要理解代理“Proxy”模式:http://blog.csdn.net/qutr/archive/2006/07/27/987253.aspx
          l         織入(Weaving):把切面(Aspect)連接到其它的應(yīng)用程序類型或者對象上來創(chuàng)建一個被通知(advised)的對象。可以在編譯時做這件事(例如使用AspectJ編譯器),也可以在類加載或運行時完成。 Spring和其他純Java AOP框架一樣, 在運行時完成織入。
          通知(Advice)類型:
          l         前置通知(Before Advice):在一個連接點之前執(zhí)行的通知,但這個通知不能阻止連接點錢的執(zhí)行(除非它拋出異常)
          l         返回后通知(After returning advice):在一個連接點正常完成后執(zhí)行的通知。例如:一個方法正常返回,沒有拋出任何異常。
          l         拋出后通知(After throwing advice):在一個方法拋出異常時執(zhí)行的通知。
          l         Finally后通知(After finally advice):當(dāng)某連接點退出的時候執(zhí)行的通知(不論是正常返回還是拋出異常)。
          l         環(huán)繞通知(Around advice):包圍一個連接點的通知,就像方法調(diào)用。這是最強大的一種通知類型。環(huán)繞通知可以在方法調(diào)用前后完成自定義的行為。它也會選擇是否繼續(xù)執(zhí)行連接點或直接返回他們自己的返回值域或拋出異常來結(jié)束執(zhí)行。
          2.Spring的AOP實現(xiàn)
          在Spring中所有的通知都以Java類的形式編寫。所以你可以像普通Java開發(fā)那樣在IDE(Eclipse或NetBeans)中開發(fā)切面。而且,定義在什么地方應(yīng)用通用的切入點通常在Spring的配置文件中配置。
          Spring有兩種代理創(chuàng)建方式:
          (1)            如果目標對象Target Object)實現(xiàn)了一個或多個接口暴露的方法,Spring將使用JDKjava.lang.reflect.Proxy類創(chuàng)建代理。
          (2)            如果目標對象Target Object)沒有實現(xiàn)任何接口,Spring使用CGLIBhttp://cglib.sourceforge.net/ )庫生成目標對象的子類。在創(chuàng)建這個子類的時候,Spring將通知織入,并且將對目標對象的調(diào)用委托給這個子類。注意用這種方式創(chuàng)建代理時需要將Spring發(fā)行包中lib/cglib文件加下的jar文件加載到工程文件中。使用這種代理時注意兩點:
          l         對接口創(chuàng)建代理優(yōu)于對類創(chuàng)建代理,這樣會產(chǎn)生更加松耦合的系統(tǒng)。
          l         標記為final的方法不能被通知。
          通過上面的一大堆名詞解釋應(yīng)該對AOP以及Spring中的AOP有了一個大概的了解了。下面進一步詳細解釋Spring中的AOP。
           
          二、創(chuàng)建通知
          1.前置通知(Before Advice
          創(chuàng)建前置通知需要實現(xiàn)org.springframework.aop.MethodBeforeAdvice接口,該接口只有before(Method method, Object[] args, Object target) throws Throwable這個方法。我們可以這樣理解前置通知:比如你到一個比較高檔的飯店去吃飯,進門時會有禮儀小姐給你開門并且向您問好,這個禮儀小姐的行為就可以視為前置通知
          package springinaction.chapter03.createadvice;
           
          import java.lang.reflect.Method;
          import org.springframework.aop.MethodBeforeAdvice;
           
          publicclass BeforeAdvice implements MethodBeforeAdvice
          {
                   //實現(xiàn)MethodBeforeAdvicebefore方法
                   publicvoid before(Method method, Object[] args, Object target)
                   {
                             System.out.println("befor advice");
                   }//end before
                  
          }//end class BeforeAdvice
          2.返回后通知(After returning advice
          創(chuàng)建返回后通知需要實現(xiàn)org.springframework.aop.AfterReturningAdvice接口,該接口也只有一個方法:void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable。同樣我們也可以這樣理解After returning advice:當(dāng)你在這家飯店用餐完后,禮儀小姐還會為您開門,并且歡迎您下次再來。這時禮儀小姐的行為就是“After returning advice”。
          package springinaction.chapter03.createadvice;
           
          import java.lang.reflect.Method;
          import org.springframework.aop.AfterReturningAdvice;
           
          publicclass AfterReturningAdviceImp implements AfterReturningAdvice
          {
                   publicvoid afterReturning(Object returnValue, Method method, Object[] arg2, Object target) throws Throwable
                   {
                             System.out.println("afterReturning");
                   }
          }
          3.環(huán)繞通知(Around advice
          創(chuàng)建環(huán)繞通知需要實現(xiàn)org.aopalliance.intercept.MethodInterceptor接口,同樣要實現(xiàn)一個invoke方法,該方法有一個MethodInvocation類型的參數(shù)。MethodInterceptor能夠控制目標方法是否真的被調(diào)用。通過調(diào)用MethodInterceptor.proceed()方法來調(diào)用目標方法。MethodInterceptor讓你可以控制返回的對象,你可以返回一個與proceed()方法返回對象完全不同的對象。我們可以這樣理解環(huán)繞通知:當(dāng)你用餐完后,飯店要確保你已經(jīng)付款了,在付款后停止再出售食物給你。
          package springinaction.chapter03.createadvice;
           
          import org.aopalliance.intercept.MethodInterceptor;
          import org.aopalliance.intercept.MethodInvocation;
           
          publicclass AroundAdvice implements MethodInterceptor
          {
                   public Object invoke(MethodInvocation mi)
                   {
                             Object obj = null;
                             //do something....
                             return obj;
                   }//end invoke(...)
                  
          }//end class AroundAdvice()
          4.拋出后通知(After throwing advice
          創(chuàng)建拋出后通知需要實現(xiàn)org.springframework.aop.ThrowsAdvice接口,該接口沒有任何方法,但是要實現(xiàn)這個接口的類必須實現(xiàn)afterThorwing(Throwable throwable)或者afterThrowing(Method method, Object[] args, Object target)形式的一種,根據(jù)拋出的異常的類型恰當(dāng)?shù)姆椒▽⒈徽{(diào)用。如果你在該飯店用餐后沒有付錢就走或者多給錢他們沒有找給你大概異常通知也就發(fā)生了。J
          package springinaction.chapter03.createadvice;
           
          import java.lang.reflect.Method;
          import org.springframework.aop.ThrowsAdvice;
           
          publicclass AfterThorwsAdvice implements ThrowsAdvice
          {
                   publicvoid afterThorwing(Throwable throwable)
                   {
                             //do something....
                   }
                  
                   publicvoid afterThrowing(Method method, Object[] args, Object target)
                   {
                             //do something....
                   }
          }
          一般來說環(huán)繞通知是用的最廣泛的一個通知類型,但是他們(Spring小組)鼓勵我們用最合適的通知。例如我們有一個簡單的驗證身份的功能,那么我們只需要前置通知就可能實現(xiàn)我們要求的功能了。
           
          三、定義切入點
          在上面我們定義了各種通知。可以看到一個通知是要被執(zhí)行的一個方法。光把通知定義出來不行,我們還要確定這些通知在我們的系統(tǒng)什么地方應(yīng)用,否則通知是毫無用處的。這就是切入點的用處。切入點決定了一個特定的類的特定方法是否滿足一條特定規(guī)則。如果確實符合,通知就應(yīng)用到該方法上。
          Spring根據(jù)需要織入通知的類和方法來定義切入點。Advice根據(jù)他們的特性織入目標類和方法。Spring的切入點框架的核心接口是Pointcut。
          publicinterface Pointcut
          {
                   ClassFilter getClassFilter();
                   MethodMatcher getMethodMatcher();
          }
                   ClassFilter接口決定了一個類是否符合通知的要求:
          package org.springframework.aop;
          publicinterface ClassFilter
          {
                   boolean matches(Class clazz);
                   ClassFilter TRUE = TrueClassFilter.INSTANCE;
          }
                 實現(xiàn)這個接口的類決定了以參數(shù)傳入進來的類是否應(yīng)該被通知。ClassFilter.TRUE是規(guī)范的的適合任何類的ClassFilter實例,他適用于創(chuàng)建只根據(jù)方法決定時候符合要求的切入點。
                   ClassFilter接口利用類過濾切面,MethodMactcher接口可以通過方法過濾切面:
          package org.springframework.aop;
          import java.lang.reflect.Method;
           
          publicinterface MethodMatcher
          {
                   boolean matches(Method method, Class targetClass);
                   boolean isRuntime();
                   boolean matches(Method method, Class targetClass, Object[] args);
                  
                   MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
          }
                 MethodMactcher接口有三個方法。matches(Method, Class)根據(jù)目標類和方法決定一個方法是否該被通知。isRuntime()方法被調(diào)用來決定MethodMatcher的類型。有兩種類型:靜態(tài)類型和動態(tài)類型。靜態(tài)pointcut的意思是Advice總是被執(zhí)行,此時的isRuntime()方法返回false。動態(tài)pointcut根據(jù)運行時方法的參數(shù)值決定通知是否需要執(zhí)行,isRuntime()方法返回true
          有一個非常重要的名詞Advisor:大多數(shù)切面是由定義切面行為的通知和定義切面在什么地方執(zhí)行的切入點組合而成的。在Spring中把通知和切入點組合到一個對象中。PointcutAdvisor提供這些功能。
          package org.springframework.aop;
          publicinterface PointcutAdvisor extends Advisor
          {
                   Pointcut getPointcut();
          }
                   Spring中的RegexpMethodPointcut讓你利用正則表達式來定義切入點。如果你對正則表達式不了解的話,趕快去學(xué)學(xué)吧,非常重要的內(nèi)容。(開個玩笑的說,用了正則表達式能使你的代碼上檔次J,下面列出定義切入點時經(jīng)常使用的符號:
          符號
          描述
          示例
          ·
          (英文點“.”)匹配任何單個字符
          setFoo.匹配setFooB,但不匹配setFoo或setFooBar
          +
          匹配前一個字符一次或多次
          setFoo.+匹配setFooB或setFooBar,但不匹配setFoo
          *
          匹配前一個字符0次或多次
          setFoo.*匹配setFooB、setFooBar和setFoo
          \
          匹配任何正則表達式符號
          \.setFoo.+匹配setFoo,但不匹配setFoo
           
          下面引用《精通Srping》中的例子,這個例子講的還是比較清晰的。如果前面的Ioc一章理解的很好的話,理解這個例子不是很難。難點在于ProxyFactoryBean這個類。
          package jingtongspring;
           
          import org.apache.commons.logging.Log;
          import org.apache.commons.logging.LogFactory;
          import org.springframework.aop.MethodBeforeAdvice;
          import java.lang.reflect.Method;
           
          publicclass LoggingBeforAdvice implements MethodBeforeAdvice
          {
                   protectedstaticfinal Log log = LogFactory.getLog(LoggingBeforAdvice.class);
                  
                   publicvoid before(Method arg0, Object[] arg1, Object arg2) throws Throwable
                   {
                             log.info("before: The invocation of getContent()");
                   }
          }
          下面是配置文件:
          <?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 = "helloworldbean"
                         class = "org.springframework.aop.framework.ProxyFactoryBean">
                             <property name = "proxyInterfaces">
                                      <value>jingtongspring.IHelloWord</value>
                             </property>
                             <property name="target">
                                      <ref local = "helloworldbeanTarget"/>
                             </property>
                             <property name="interceptorNames">
                                      <list>
                                               <value>loggingBerforeAdvisor</value>
                                      </list>
                             </property>
                   </bean>
                  
                   <bean id="helloworldbeanTarget"
                             class="jingtongspring.HelloWord"/>
                  
                   <bean id="loggingBeforAdbisor"
                             class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
                             <property name="advice">
                                      <ref local="loggingBeforeAdvice"/>
                             </property>
                             <property name="pattern">
                                      <value>.*</value>
                             </property>
                   </bean>
                  
                   <bean id="loggingBeforeAdvice"
                             class="jingtongspring.LoggingBeforeAdvice"/>
          </beans>
           
          四、創(chuàng)建引入
          在前面我們列出了Spring的四種通知類型,并且有相應(yīng)的簡單例子。但是引入(Introduction)通知與其他類型的通知有所不同,它影響整個類。通過給需要消息的類添加方法和屬性來實現(xiàn)。(看上去很神氣,因為說道類那就是已經(jīng)封裝好了的,給類添加方法除非是繼承該類。Spring中怎樣實現(xiàn)?)
          Spring通過一個特殊的方法攔截器接口IntroductionInterceptor來實現(xiàn)引入。這個接口添加一個方法:boolean implementsInterface(Class intf)這個方法對于引入如何工作非常關(guān)鍵。如果IntroductionInterceptor是為了實現(xiàn)指定接口,那么方法implementsInterface應(yīng)該返回true。也就是說,對用這個接口聲明的方法的任何調(diào)用將被委托給IntroductionInterceptorinvoke()方法。invoke()方法負責(zé)實現(xiàn)這個方法,不能調(diào)用MethodInvocation.proceed()。它引入了新的借口,調(diào)用目標對象是沒有用的。(注意:在本書中提到了一個IntroductionMethodInterceptor接口,我查看Spring2.0 rc3的源代碼并沒有該類,可能是在Spring1.x中存在的,IntroductionInterceptor 接口的定義形式是這樣的:publicinterface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {}invoke()方法估計是在它的兩個父接口之一中存在。
          下面看例子:
          先實現(xiàn)一個接口:
          package springinaction.chapter03.createadvice;
           
          import java.util.Date;
           
          publicinterface Auditable
          {
                   void setLastModifiedDate(Date date);
                   Date getLastModifiedDate();
          }
           
          再實現(xiàn)一個類:
          package springinaction.chapter03.createadvice;
           
          import java.util.Date;
           
          import org.aopalliance.intercept.MethodInvocation;
          import org.springframework.aop.IntroductionInterceptor;
           
          publicclass AuditableMixin implements IntroductionInterceptor, Auditable
          {
                   private Date lastModifiedDate;
                  
                   publicboolean implementsInterface(Class intf)
                   {
                             return intf.isAssignableFrom(Auditable.class);
                   }//end implementsInterface(...)
                  
                   public Object invoke(MethodInvocation m) throws Throwable
                   {
                             if(this.implementsInterface(m.getMethod().getDeclaringClass())){
                                      return m.getMethod().invoke(this, m.getArguments());
                             }
                             else{
                                      return m.proceed();
                             }
                   }//end invoke(...)
                  
                   public Date getLastModifiedDate()
                   {
                             returnthis.lastModifiedDate;
                   }//end getLastModifiedDate()
                  
                   publicvoid setLastModifiedDate(Date lastModifiedDate)
                   {
                             this.lastModifiedDate = lastModifiedDate;
                   }
                  
                  
          }//end class AuditableMixin
           
          AuditableMixin類中實現(xiàn)了IntroductionInterceptor接口和業(yè)務(wù)接口Auditable。如果被申明調(diào)用方法的類型是Auditable類型,implementsInterface方法返回true。這說明,對于Auditable的兩個方法,攔截器必須提供實現(xiàn),在invoke方法中實現(xiàn)。對于任何Auditable接口方法的調(diào)用,調(diào)用我們的攔截器。對于其他方法的調(diào)用我們讓Method Invocation處理。
                   Spring提供了一個一個方便的類來處理我們的大多數(shù)應(yīng)用,這個類是:DelegatingIntroductionInterceptor應(yīng)用這個類我們不再需要實現(xiàn)invoke這個方法了因為在DelegatingIntroductionInterceptor類中已經(jīng)實現(xiàn)好了(可以參看一下源代碼)。看下面的例子:
          package springinaction.chapter03.createadvice;
           
          import java.util.Date;
           
          import org.springframework.aop.support.DelegatingIntroductionInterceptor;
           
          publicclass ImmutableMixin extends DelegatingIntroductionInterceptor implements Auditable
          {
                   private Date lastModifiedDate;
                  
                   public Date getLastModifiedDate()
                   {
                             returnthis.lastModifiedDate;
                   }//end getLastModifiedDate()
                  
                   publicvoid setLastModifiedDate(Date lastModifiedDate)
                   {
                             this.lastModifiedDate = lastModifiedDate;
                   }//end setLastModifiedDate(...)
                  
          }//end class ImmutableMixin
              DelegatingIntroductionInterceptor類也要實現(xiàn)你的混合類暴露的任何方法,并且將任何對這些方法的調(diào)用委托給這個混合類。因為ImmutableMixin類實現(xiàn)了Auditable接口,對這個接口的方法的所有調(diào)用都將調(diào)用我們的攔截器。任何其他方法委托給目標對象。如果你的攔截器要實現(xiàn)一個接口,你不想把它暴露成一個混合體,那么只要簡單的將這個接口傳遞給DelegatingIntroductionInterceptor類的suppressInterface()方法就可以了(該方法在DelegatingIntroductionInterceptor類的父類IntroductionInfoSupport中實現(xiàn))。還有一點:如果你的混合體要改變?nèi)魏文繕藢ο蠓椒ǖ男袨榈脑挘憔托枰獙崿F(xiàn)invoke()方法。
           
          有了自己的引入通知后,我們需要創(chuàng)建Advisor。因為引入通知之應(yīng)用在類層次上,所以引入有他們自己的AdvisorIntroductionAdvisorSpring也提供了一個適合大多數(shù)情況的缺省實現(xiàn)。名為:DefaultIntroductionAdvisor,它有一個以IntroductionInterceptor作為參數(shù)的構(gòu)造函數(shù)。
           
          在本章中使用了大量的ProxyFactoryBean來創(chuàng)建一個被通知的類。當(dāng)你想明確的控制你的通知類如何組合時,這種是方法最好、最簡單的選擇。ProxyFactoryBean有很多控制行為的屬性。在書中作者列出了一個詳細的表格進行了說明,這里就不羅列了。詳細的ProxyFactoryBean大家也可以看一下它的源代碼。
           
          五、小結(jié)
          寫到這里我不得不打住了,再寫下去我都不知道寫的是什么了,對于新手來說AOP確實是非常抽象的概念,要想真正理解AOP并且編寫出AOP模塊,在深入理解概念的同時還要進行大量的實踐才行,從實踐中去真正的體會AOP。那么AOP有三個最核心的概念,這三個概念是:Advice、Pointcut和Advisor。Advice是你想向其他程序內(nèi)部不同地方注入的代碼。Pointcut定義了需要注入Advisor的位置,這個位置通常是某個特定的類的一個public方法。Advisor是Pointcut和Advice的裝配器,是將Advice注入程序中預(yù)定義位置的代碼。按照這樣的主線再去解讀AOP的各個概念,再通過實踐理解AOP應(yīng)該是比較容易的。
          如果你對AOP的概念和內(nèi)涵難以理解,那就多找?guī)妆緯蚱渌Y料看看。最主要的還是看別人寫的代碼和自己大量的實踐。下面我列出幾個參考大家可以找來看看:
          1.         最重要的還是應(yīng)該參考官方文檔(也就是Spring參考手冊)。已經(jīng)在Spring官方網(wǎng)站上組織翻譯了。看他們的回帖說是正在進行翻譯審定,可能是以最新的Rc4為最后標準了。最終的Spring 2.0發(fā)布版本在9月26號出來。鏈接:(http://www.jactiongroup.net/reference2/html/
          2.         夏昕編寫的OpenDoc《Spring開發(fā)指南》,雖然沒有全方位的解釋AOP但是文檔中的內(nèi)容還是將一些概念講解的很清晰。鏈接:(http://wiki.redsaga.com/confluence/homepage.action 需要自己查找)
          3.         《精通Spring》,里面關(guān)于AOP應(yīng)用配置的例子還是容易懂的。鏈接:(http://www.china-pub.com/computers/common/info.asp?id=24483
          4.         《Pro Spring》中文版已經(jīng)出版,這本書我在書店里翻著看了一陣,翻譯的還是比較不錯的,書有些厚(和板磚一樣厚,但是比板磚大J)但確實講的不錯。希望想買本書學(xué)習(xí)Spring的人建議買這本,看中文版的學(xué)習(xí)速度比較快,畢竟沒有語言障礙。如果你的英文能力強,建議直接看英文原文,因為網(wǎng)上到處都有下載的。我看得就是英文版,比較好理解。鏈接:(http://www.china-pub.com/computers/common/info.asp?id=29859
          posted on 2007-10-23 21:44 譚明 閱讀(479) 評論(0)  編輯  收藏 所屬分類: Spring
          主站蜘蛛池模板: 阳新县| 宁乡县| 瑞丽市| 临朐县| 滨海县| 南溪县| 建湖县| 天等县| 嘉义市| 额尔古纳市| 南江县| 富裕县| 雅江县| 本溪| 郓城县| 绵竹市| 荔浦县| 千阳县| 玛多县| 萨迦县| 合阳县| 登封市| 鄱阳县| 兴城市| 屏东市| 阳西县| 惠安县| 彭水| 巴青县| 南川市| 柳林县| 广河县| 东山县| 墨玉县| 池州市| 南江县| 普宁市| 汉中市| 达日县| 昭通市| 西乡县|