上善若水
          In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
          posts - 146,comments - 147,trackbacks - 0

          一個(gè)月前因?yàn)槟承┦虑楸焕幉ㄈ欤诰频昀镩e著無事,然后開始看Log4J的源碼。原本之后的一個(gè)星期就應(yīng)該開始寫了,無奈又遇到一些事情,遲遲沒有動(dòng)筆。感覺工作后要做好一件額外的事情總是很難,每天下班后才能看代碼、寫文章,而如果中途遇到一些沒有預(yù)料到的事情就很容易不了了之了,所以現(xiàn)在如果出現(xiàn)能靜下心來看代碼、寫文章的時(shí)間,我都是特別珍惜。我一直不知道如何開場(chǎng)一篇文章,所以先用一些廢話做引子….:(

          在軟件開發(fā)過程中,出現(xiàn)bug總是在所難免;事實(shí)上,以我個(gè)人經(jīng)驗(yàn),即使在實(shí)際開發(fā)階段,fix bug時(shí)間要遠(yuǎn)超過寫代碼的時(shí)間。在開發(fā)階段,比較有效的fix bug的方法當(dāng)然是調(diào)試,然而如果代碼比較復(fù)雜,而且開始對(duì)代碼不是很熟悉,那么我們很容易在方法調(diào)用之間迷失方向;如果bug出現(xiàn)在多線程環(huán)境中,那么很多時(shí)候調(diào)試就無能為力了;另外當(dāng)代碼部署到服務(wù)器上運(yùn)行時(shí),不管是在UAT測(cè)試環(huán)境還是Production環(huán)境,此時(shí)要調(diào)試很多時(shí)候是不可能。為了解決這些問題,我們可以在開發(fā)過程中事先在一些關(guān)鍵點(diǎn)上打印出一些日志信息(log),以利于在出問題后能知道當(dāng)時(shí)運(yùn)行的環(huán)境信息,特別是在production上,我們可以分析log以確定問題所在,當(dāng)然前提log信息足夠多。不過加入日志信息后,不難想象,它會(huì)對(duì)程序的運(yùn)行性能產(chǎn)生一定的影響,而且如果log過多會(huì)導(dǎo)致有用的信息不容易找到,如果過少又不具備很大的參考價(jià)值,這樣的日志除了降低程序的性能,貌似對(duì)其他方面沒有幫助。關(guān)于性能,Log4J號(hào)稱在這面做了很多優(yōu)化,但是據(jù)說logback做的更好(logback的源碼還沒來得及看,而且也沒用過,所以還不是很了解);而關(guān)于如何寫log、在哪里寫log、要把那些信息寫入log中,個(gè)人感覺這是一門很大的學(xué)問,而且也是根據(jù)不同項(xiàng)目而不同,而本人也還沒有達(dá)到那種可以列出一些比較詳細(xì)的指導(dǎo)點(diǎn),因而本文將不會(huì)涉及到這方面,本文主要從源碼角度關(guān)注Log4J是如何實(shí)現(xiàn)的。

          以下將用循序漸進(jìn)的方式來講解簡(jiǎn)單的日志類實(shí)現(xiàn),并最后引出Log4J日志系統(tǒng)的核心實(shí)現(xiàn)。

          直接打印日志到控制臺(tái)

          最簡(jiǎn)單的Logging功能實(shí)現(xiàn),我想應(yīng)該就是直接使用System.out.println()將需要的信息打印到控制臺(tái)中了:

           1     @Test
           2     public void testBasic() {
           3        System.out.println("Begin to execute testBasic() method");
           4       
           5        System.out.println("Executing");
           6        try {
           7            throw new Exception("Deliberately throw an Exception");
           8        } catch(Exception e) {
           9            System.out.println("Catching an Exception: " + e.getMessage());
          10            e.printStackTrace(System.out);
          11        }
          12       
          13        System.out.println("Execute testBasic() method finished.");
          14     }

           

          這種方法最大的好處就是簡(jiǎn)單方便,而且不用引入第三方的依賴包。而它的功能自然也是最弱的:

          1.       首先它只能將日志信息打印到控制臺(tái);

          2.       它不支持分類日志信息,有些日志只是用于調(diào)試信息,在上production后不可以打印出來;而另一些日志信息則是包含一些重要信息,需要再production現(xiàn)實(shí)出來;有些時(shí)候?yàn)榱诉M(jìn)一步提升程序運(yùn)行性能或者部署者認(rèn)為系統(tǒng)已經(jīng)很穩(wěn)定了,日志信息并不是很重要,因而只需要打印錯(cuò)誤信息、甚至關(guān)閉日志功能,在這種情況下,用這種方法就會(huì)非常麻煩。

          3.       每一句日志記錄中可能包含一些相似的信息,如日志級(jí)別、日志記錄打印時(shí)間、日志記錄對(duì)應(yīng)的類等信息,如果要實(shí)現(xiàn)這種功能,則會(huì)出現(xiàn)很多重復(fù)代碼。

          4.       很多時(shí)候,我們希望保留日志,以備日后分析、審查用,隨著日志信息的不斷積累,我們并不希望所有的日志文件都在同一個(gè)文件中,而是希望日志文件能夠根據(jù)一定的規(guī)則自動(dòng)的切換使用新的文件。

          封裝日志類SimpleLog1

          封裝是面向?qū)ο蟮娜筇匦灾唬褜?duì)象的數(shù)據(jù)和行為聚合在一起,只提供給外界需要的接口,以提高代碼的可重用性、降低代碼之間的耦合、進(jìn)而也增加代碼的內(nèi)聚性。因而對(duì)上述直接打印日志到控制臺(tái)的一個(gè)簡(jiǎn)單的重構(gòu)即是將打印日志的代碼都提取到一個(gè)簡(jiǎn)單的日志類中:SimpleLog1

          SimpleLog1也是將日志直接打印到控制臺(tái),但是它引入了日志級(jí)別(Level,或類別)的支持:tracedebuginfowarnerrorfatalalloff;另外,它也會(huì)自動(dòng)的在每一條日志記錄之前加入日志級(jí)別、日志名稱、日志消息、異常堆棧等信息。

           1 public class SimpleLog1 {
           2     public static final int LOG_LEVEL_TRACE = 1;
           3     public static final int LOG_LEVEL_DEBUG = 2;
           4     public static final int LOG_LEVEL_INFO   = 3;
           5     public static final int LOG_LEVEL_WARN   = 4;
           6     public static final int LOG_LEVEL_ERROR = 5;
           7     public static final int LOG_LEVEL_FATAL = 6;
           8     public static final int LOG_LEVEL_ALL    = (LOG_LEVEL_TRACE - 1);
           9     public static final int LOG_LEVEL_OFF    = (LOG_LEVEL_FATAL + 1);
          10    
          11     private final String name;
          12     private final int level;
          13     public SimpleLog1(String name, int level) {
          14         this.name = name;
          15         this.level = level;
          16     }
          17     public SimpleLog1(Class<?> cls, int level) {
          18         this(cls.getName(), level);
          19     }
          20     public void trace(String message) {
          21         trace(message, null);
          22     }
          23     public void trace(String message, Throwable cause) {
          24         log(LOG_LEVEL_TRACE, message, cause);
          25     }
          26     public boolean isTraceEnabled() {
          27         return isLevelEnabled(LOG_LEVEL_TRACE);
          28     }
          29     
          30     private void log(int type, String message, Throwable cause) {
          31         if(!isLevelEnabled(type)) {
          32             return;
          33         }
          34         StringBuilder builder = new StringBuilder(32);
          35        
          36         putLevelString(builder, type);
          37         putLogName(builder, name);
          38         builder.append(message);
          39         if(cause != null) {
          40             putCauseInfo(builder, cause);
          41         }
          42    
          43         writeLog(builder);
          44     }
          45     private void putLevelString(StringBuilder builder, int type) {
          46        switch(type) {
          47             case SimpleLog1.LOG_LEVEL_TRACE:
          48                 builder.append("[TRACE] "); break;
          49             case SimpleLog1.LOG_LEVEL_DEBUG:
          50                 builder.append("[DEBUG] "); break;
          51             case SimpleLog1.LOG_LEVEL_INFO: 
          52                 builder.append("[INFO] "); break;
          53             case SimpleLog1.LOG_LEVEL_WARN: 
          54                 builder.append("[WARN] "); break;
          55             case SimpleLog1.LOG_LEVEL_ERROR:
          56                 builder.append("[ERROR] "); break;
          57             case SimpleLog1.LOG_LEVEL_FATAL:
          58                 builder.append("[FATAL] "); break;
          59        }
          60     }
          61     private void putLogName(StringBuilder builder, String name) {
          62         builder.append(name);
          63         builder.append("-");
          64     }
          65     private void putCauseInfo(StringBuilder builder, Throwable cause) {
          66        builder.append("<");
          67        builder.append(cause.getMessage());
          68        builder.append(">");
          69        builder.append(System.getProperty("line.separator"));
          70       
          71        StringWriter writer = new StringWriter();
          72        PrintWriter printer = new PrintWriter(writer);
          73        cause.printStackTrace(printer);
          74        printer.close();
          75        builder.append(writer.toString());
          76     }
          77     private void writeLog(StringBuilder builder) {
          78         System.out.println(builder.toString());
          79     }
          80     private boolean isLevelEnabled(int level) {
          81     if(level >= this.level) {
          82             return true;
          83         }
          84         return false;
          85     }
          86 }

           

          將打印日志封裝成一個(gè)類并支持不同級(jí)別的日志打印信息后,打印日志的代碼看起來更合理一些了,而且也可以為每一條日志記錄加入一些共同的信息了,如日志級(jí)別、日志名稱等:

           1     @Test
           2     public void testSimpleLog1() {
           3        SimpleLog1 log = new SimpleLog1("levin.commons.logging.test.TestBasic",
           4               SimpleLog1.LOG_LEVEL_DEBUG);
           5        log.info("Begin to execute testBasic() method");
           6       
           7        log.info("Executing");
           8       
           9        try {
          10            throw new Exception("Deliberately throw an Exception");
          11        } catch(Exception e) {
          12            log.error("Catching an Exception", e);
          13        }
          14       
          15        log.info("Execute testBasic() method finished.");
          16     }

           

          然而上述的日志類SimpleLog1還是太簡(jiǎn)單了,它需要為每個(gè)日志打印實(shí)例指定日志級(jí)別、不支持打印日志到文件、不能方便的配置每條日志記錄的共同信息。因而我們還需要支持可配置的日志類。

          可配置的日志類SimpleLog2

          雖然日志功能在應(yīng)用程序開發(fā)中是一個(gè)非常重要的部件,有些時(shí)候日志信息的好壞可以直接影響程序開發(fā)的進(jìn)度。然而日志本身不涉及到任何業(yè)務(wù)邏輯,因而需要盡量減少它的侵入性,也就說它提供的接口應(yīng)該盡量的簡(jiǎn)單。為了實(shí)現(xiàn)接口的簡(jiǎn)單性,其中一種方法就是使用配置文件記錄SimpleLog2的配置信息,SimpleLog2則根據(jù)配置信息初始化每一個(gè)SimpleLog2實(shí)例。這些配置信息包括是否顯示日志名稱、時(shí)間信息;如果顯示日志打印時(shí)間,其格式如何;默認(rèn)的日志級(jí)別是什么;支持單獨(dú)配置一些日志名稱的日志級(jí)別;如果將日志打印到日志文件,則日志文件的名稱和目錄在哪里等信息。

            1 public class SimpleLog2 {
            2     
            3     private static final String PROP_PREFIX = "levin.commons.logging.simplelog.";
            4     private static final String PROP_LOG_PREFIX = PROP_PREFIX + "log.";
            5     private static final String DEFAULT_DATA_FORMAT = "yyyy-MM-dd HH:mm:ss,SSS zzz";
            6     private static final String DEFAULT_LEVEL = "debug";
            7    
            8     private static SimpleDateFormat dateFormat = null;
            9     private static boolean showLogName = true;
           10     private static boolean showShortName = false;
           11     private static boolean showDateInfo = false;
           12     private static boolean flush = true;
           13     private static int rootLevel;
           14     private static Map<String, Integer> configLevels;
           15     private static PrintStream out;
           16    
           17     static {
           18         InputStream in = getConfigInputStream("simplelog.properties");
           19         Properties props = new Properties();
           20         if(in != null) {
           21             try {
           22                 props.load(in);
           23                 in.close();
           24             } catch(IOException e) {
           25                 // Ignored
           26             }
           27         }
           28         // Put all system properties
           29         props.putAll(System.getProperties());
           30    
           31         showLogName = getBooleanProperty(props, PROP_PREFIX + "showLogName""true");
           32         showShortName = getBooleanProperty(props, PROP_PREFIX + "showShortName""false");
           33         showDateInfo = getBooleanProperty(props, PROP_PREFIX + "showDateInfo""false");
           34         String dateFormatStr = getProperty(props, PROP_PREFIX + "dateFormat", DEFAULT_DATA_FORMAT);
           35         dateFormat = new SimpleDateFormat(dateFormatStr);
           36         rootLevel = toIntegerLevel(getProperty(props, PROP_PREFIX + "root.level", DEFAULT_LEVEL));
           37         configLevels = parseConfigLevels(props);
           38         String logFile = getProperty(props, PROP_PREFIX + "logFile""");
           39         boolean append = getBooleanProperty(props, PROP_PREFIX + "logFile.append""true");
           40         out = getPrintStream(logFile, append);
           41         flush = getBooleanProperty(props, PROP_PREFIX + "logFile.flush""true");
           42    
           43         // Add shutdown hook
           44         Runtime runtime = Runtime.getRuntime();
           45         runtime.addShutdownHook(new Thread() {
           46             @Override
           47             public void run() {
           48                try {
           49                    shutdown();
           50                } catch(Exception e) {
           51                    System.err.println("Shutdown SimpleLog2 application failed.");
           52                   e.printStackTrace(System.err);
           53                }
           54             }
           55         });
           56     }   
           57     private static Map<String, Integer> parseConfigLevels(Properties props) {
           58         Map<String, Integer> map = new TreeMap<String, Integer>();
           59         for(String key : props.stringPropertyNames()) {
           60             if(key != null && key.startsWith(PROP_LOG_PREFIX)) {
           61                 String logLevelValue = props.getProperty(key);
           62                 String logName = parseLogName(key);
           63                 map.put(logName, toIntegerLevel(logLevelValue));
           64             }
           65         }
           66         return map;
           67     }
           68     private static String parseLogName(String logNameKey) {
           69         return logNameKey.substring(PROP_LOG_PREFIX.length());
           70     }
           71     private static PrintStream getPrintStream(String logFile, boolean append) {
           72         if(logFile == null || logFile.isEmpty()) {
           73             return System.out;
           74         }
           75        
           76         PrintStream out = null;
           77         try {
           78             out = new PrintStream(new FileOutputStream(logFile, append));
           79         } catch(IOException e) {
           80             System.err.println("Error while create logFile[" +
           81                    logFile + " PrintStream: " + e.getMessage());
           82             System.err.println("Output log info to console by default");
           83             return System.out;
           84         }
           85        
           86         return out;
           87     }
           88     private static synchronized void writeLog(StringBuilder builder) {
           89         out.println(builder.toString());
           90         if(flush) {
           91             out.flush();
           92         }
           93     }
           94     private static InputStream getConfigInputStream(String configName) {
           95         ClassLoader classLoader = getContextClassLoader();
           96         InputStream in = classLoader.getResourceAsStream(configName);
           97         if(in == null) {
           98            in = SimpleLog2.class.getClassLoader().getResourceAsStream(configName);
           99         }
          100         if(in == null) {
          101             in = SimpleLog2.class.getResourceAsStream(configName);
          102         }
          103         return in;
          104     }
          105     private static ClassLoader getContextClassLoader() {
          106         return Thread.currentThread().getContextClassLoader();
          107     }
          108     private String name;
          109     private int level;
          110     private String shortName;
          111    
          112     public SimpleLog2(String name) {
          113         this.name = name;
          114         this.level = getLogLevel(name);
          115     }
          116     public SimpleLog2(Class<?> cls) {
          117         this(cls.getName());
          118     }
          119     public void setLevel(int level) {
          120         this.level = level;
          121     }
          122     public void trace(String message) {
          123        trace(message, null);
          124     }
          125     public void trace(String message, Throwable cause) {
          126         log(LOG_LEVEL_TRACE, message, cause);
          127     }
          128     public boolean isTraceEnabled() {
          129         return isLevelEnabled(LOG_LEVEL_TRACE);
          130     }
          131       
          132     private int getLogLevel(String logName) {
          133         if(configLevels == null || configLevels.isEmpty()) {
          134             return rootLevel;
          135         }
          136         int logLevel = -1;
          137         for(String name : configLevels.keySet()) {
          138             if(logName.startsWith(name)) {
          139                logLevel = configLevels.get(name);
          140             }
          141         }
          142         if(logLevel == -1) {
          143             logLevel = rootLevel;
          144         }
          145         return logLevel;
          146     }
          147     private void log(int type, String message, Throwable cause) {
          148         if(!isLevelEnabled(type)) {
          149             return;
          150         }
          151        
          152         StringBuilder builder = new StringBuilder(32);
          153        
          154         putDateInfo(builder);
          155         putLevelString(builder, type);
          156         putLogName(builder, name);
          157         builder.append(message);
          158         putCauseInfo(builder, cause);
          159    
          160         writeLog(builder);
          161     }
          162     private void putDateInfo(StringBuilder builder) {
          163     if(showDateInfo) {
          164            Date date = new Date();
          165            String dateStr = null;
          166            synchronized(dateFormat) {
          167                dateStr = dateFormat.format(date);
          168            }
          169            builder.append(dateStr);
          170         }
          171     }
          172     private void putLevelString(StringBuilder builder, int type) {
          173        switch(type) {
          174             case SimpleLog1.LOG_LEVEL_TRACE:
          175                 builder.append("[TRACE] "); break;
          176             case SimpleLog1.LOG_LEVEL_DEBUG:
          177                 builder.append("[DEBUG] "); break;
          178             case SimpleLog1.LOG_LEVEL_INFO: 
          179                 builder.append("[INFO] "); break;
          180             case SimpleLog1.LOG_LEVEL_WARN: 
          181                 builder.append("[WARN] "); break;
          182             case SimpleLog1.LOG_LEVEL_ERROR:
          183                 builder.append("[ERROR] "); break;
          184             case SimpleLog1.LOG_LEVEL_FATAL:
          185                 builder.append("[FATAL] "); break;
          186         }
          187     }
          188     private void putLogName(StringBuilder builder, String name) {
          189         if(showShortName) {
          190            builder.append(getShortName(name));
          191         } else if(showLogName) {
          192            builder.append(name);
          193         }
          194         builder.append(" - ");
          195    }
          196    private void putCauseInfo(StringBuilder builder, Throwable cause) {
          197        if(cause == null) {
          198           return;
          199        }
          200        builder.append("<");
          201        builder.append(cause.getMessage());
          202        builder.append(">");
          203        builder.append(System.getProperty("line.separator"));
          204       
          205        StringWriter writer = new StringWriter();
          206        PrintWriter printer = new PrintWriter(writer);
          207        cause.printStackTrace(printer);
          208        printer.close();
          209        builder.append(writer.toString());
          210     }
          211     private boolean isLevelEnabled(int level) {
          212         if(level >= this.level) {
          213            return true;
          214         }
          215         return false;
          216     }
          217     private String getShortName(String name) {
          218         if(shortName == null) {
          219            if(name == null) {
          220                shortName = "null";
          221                return shortName;
          222            }
          223             int idx = name.lastIndexOf(".");
          224            if(idx < 0) {
          225                shortName = name;
          226            } else {
          227                shortName = name.substring(idx + 1);
          228            }
          229         }
          230         return shortName;
          231     }
          232 }

           

          在使用配置文件后,在沒有增加SimpleLog2接口復(fù)雜性的基礎(chǔ)上,使其功能更加強(qiáng)大,并且每個(gè)SimpleLog2的實(shí)例的日志級(jí)別都是不需要在代碼中配置,并且它還支持了不同日志實(shí)例級(jí)別的繼承性:

           1     @Test
           2     public void testSimpleLog2() {
           3        SimpleLog2 log = new SimpleLog2("levin.commons.logging.test.TestBasic");
           4        log.info("Begin to execute testBasic() method");
           5       
           6        log.info("Executing");
           7       
           8        try {
           9            throw new Exception("Deliberately throw an Exception");
          10        } catch(Exception e) {
          11            log.error("Catching an Exception", e);
          12        }
          13       
          14        log.info("Execute testBasic() method finished.");
          15     }

           

          其中典型的配置文件如下(simplelog.properties):

          levin.commons.logging.simplelog.showShortName=true
          levin.commons.logging.simplelog.showDateInfo
          =true
          levin.commons.logging.simplelog.dateFormat
          =yyyy-MM-dd HH:mm:ss,SSS zzz
          levin.commons.logging.simplelog.root.level
          =debug
          levin.commons.logging.simplelog.logFile
          =log/levin.log
          levin.commons.logging.simplelog.logFile.append
          =true
          levin.commons.logging.simplelog.logFile.flush
          =true
           
          levin.commons.logging.simplelog.log.level
          =fatal
          levin.commons.logging.simplelog.log.level.commons
          =info

           

          Log4J核心架構(gòu)

          到目前為止,我們已經(jīng)簡(jiǎn)單的實(shí)現(xiàn)了一個(gè)可用的日志系統(tǒng)了,它只有一個(gè)類,而且還支持配置文件。事實(shí)上,這里的實(shí)現(xiàn)參考了commons-logging中的SimpleLog類,而且也部分借鑒了Log4J的實(shí)現(xiàn),只是Log4J將日志打印的各個(gè)模塊用不同的類進(jìn)行了封裝:Logger類封裝了一個(gè)命名日志類,它是Log4J和使用它的應(yīng)用程序的主要接口,提供打印不同級(jí)別日志的接口;Logger類包含了Level字段和Appender集合,其中Level表達(dá)當(dāng)前Logger類可打印日志信息的級(jí)別;Appender接口用于對(duì)日志輸出目的地的抽象,它可以是控制臺(tái)、文件、網(wǎng)絡(luò)等;Appender中包含了Layout字段,Layout類是對(duì)一條日志記錄的格式的抽象,它定義了每條日志記錄打印的信息內(nèi)容、格式、不同類型信息的排列順序等;而LoggingEvent類則是對(duì)每一條日志記錄的抽象和封裝,從而每一條日志記錄所具有的信息都可以從LoggingEvent實(shí)例中查詢,LoggingEvent的設(shè)計(jì)思想有點(diǎn)類似很多系統(tǒng)中的Context概念或者Servlet中的HttpRequestHttpSession等類的設(shè)計(jì),它將每一次日志打印的請(qǐng)求信息封裝在一個(gè)類中,保證了數(shù)據(jù)的內(nèi)聚性,所有需要獲取請(qǐng)求信息的類只要拿到這個(gè)實(shí)例即可,而且如果需要改變請(qǐng)求中的部分?jǐn)?shù)據(jù),只要修改該實(shí)例即可保證該修改對(duì)所有的模塊可見,從而簡(jiǎn)化了編程模型,也不會(huì)因?yàn)閷⒑芏嘈畔⒎稚⒌讲煌胤蕉胶笃诔霈F(xiàn)數(shù)據(jù)同步問題或是數(shù)據(jù)太分散而無法管理的問題。

          通過以上分析,我們可以知道Log4J的日志打印功能的最核心類即:LoggerAppenderLevelLayoutLoggingEvent。它們的類結(jié)構(gòu)圖如下:


          一次日志打印的序列圖如下:


           

          posted on 2012-06-12 23:38 DLevin 閱讀(4700) 評(píng)論(3)  編輯  收藏 所屬分類: Logging

          FeedBack:
          # re: 深入Log4J源碼之SimpleLog
          2012-06-14 16:47 | foo
          還是要向樓主表達(dá)一下敬佩之意
          我要是在酒店閑來無事,是絕不會(huì)看什么源碼的  回復(fù)  更多評(píng)論
            
          # re: 深入Log4J源碼之SimpleLog[未登錄]
          2012-06-25 01:41 | killer
          樓主好文章,很有參考意義  回復(fù)  更多評(píng)論
            
          # re: 深入Log4J源碼之SimpleLog
          2015-07-31 17:55 | 野馬
          樓主很多好文章,感謝!  回復(fù)  更多評(píng)論
            
          主站蜘蛛池模板: 通化市| 库尔勒市| 堆龙德庆县| 阜新| 分宜县| 南部县| 武汉市| 孟村| 永仁县| 台南市| 黑山县| 巧家县| 松滋市| 巩留县| 娄烦县| 长岭县| 岢岚县| 平遥县| 绍兴市| 宁阳县| 溧水县| 江达县| 密云县| 夏邑县| 株洲市| 嵊泗县| 舟山市| 梁山县| 宁强县| 通辽市| 绍兴市| 新蔡县| 米易县| 阿巴嘎旗| 阳山县| 阜宁县| 鄂尔多斯市| 镇远县| 汾阳市| 隆林| 肥东县|