spring緩存機制(EhCache配置方式)

一、SPRING與EhCache緩存的聯繫
調用一個緩存方法時會把該方法參數和返回結果作爲一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時將不再執行該方法,而是直接從緩存中獲取結果進行返回。所以在使用Spring Cache的時候我們要保證我們緩存的方法對於相同的方法參數要有相同的返回結果。

Spring爲我們提供了幾個註解來支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable標記的方法在執行後Spring Cache將緩存其返回結果,而使用@CacheEvict標記的方法會在方法執行前或者執行後移除Spring Cache中的某些元素。
二、EhCache實現的配置

  1. 需要導入的jar包:ehcache-core-2.4.3.jar,slf4j-api-1.6.1.jar(該緩存工具所使用的日誌工具)
  2. 在類加載路徑下添加一個ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="true" monitoring="autodetect"
         dynamicConfig="true">
    <diskStore path="java.io.tmpdir"/>
    <transactionManagerLookup class="net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup"
                              properties="jndiName=java:/TransactionManager" propertySeparator=";"/>
    <cacheManagerEventListenerFactory class="" properties=""/>
    <cacheManagerPeerListenerFactory
            class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"/>
   <!-- 
    maxElementsInMemory:設置緩存中最多可放多少個對象
    eternal:設置緩存是否永久有效
    timeToIdleSeconds:設置緩存的對象在多少秒沒被使用的被清除掉
    timeToLiveSeconds:設置對象在過期前可緩存多少秒
    -->
    <!--
        <diskStore>==========當內存緩存中對象數量超過maxElementsInMemory時,將緩存對象寫到磁盤緩存中(需對象實現序列化接口)
        <diskStore path="">==用來配置磁盤緩存使用的物理路徑,Ehcache磁盤緩存使用的文件後綴名是*.data和*.index
        name=================緩存名稱,cache的唯一標識(ehcache會把這個cache放到HashMap裏)
        maxElementsOnDisk====磁盤緩存中最多可以存放的元素數量,0表示無窮大
        maxElementsInMemory==內存緩存中最多可以存放的元素數量,若放入Cache中的元素超過這個數值,則有以下兩種情況
                             1)若overflowToDisk=true,則會將Cache中多出的元素放入磁盤文件中
                             2)若overflowToDisk=false,則根據memoryStoreEvictionPolicy策略替換Cache中原有的元素
        eternal==============緩存中對象是否永久有效,即是否永駐內存,true時將忽略timeToIdleSeconds和timeToLiveSeconds
        timeToIdleSeconds====緩存數據在失效前的允許閒置時間(單位:秒),僅當eternal=false時使用,默認值是0表示可閒置時間無窮大,此爲可選屬性
                             即訪問這個cache中元素的最大間隔時間,若超過這個時間沒有訪問此Cache中的某個元素,那麼此元素將被從Cache中清除
        timeToLiveSeconds====緩存數據在失效前的允許存活時間(單位:秒),僅當eternal=false時使用,默認值是0表示可存活時間無窮大
                             即Cache中的某元素從創建到清楚的生存時間,也就是說從創建開始計時,當超過這個時間時,此元素將從Cache中清除
        overflowToDisk=======內存不足時,是否啓用磁盤緩存(即內存中對象數量達到maxElementsInMemory時,Ehcache會將對象寫到磁盤中)
                             會根據標籤中path值查找對應的屬性值,寫入磁盤的文件會放在path文件夾下,文件的名稱是cache的名稱,後綴名是data
        diskPersistent=======是否持久化磁盤緩存,當這個屬性的值爲true時,系統在初始化時會在磁盤中查找文件名爲cache名稱,後綴名爲index的文件
                             這個文件中存放了已經持久化在磁盤中的cache的index,找到後會把cache加載到內存
                             要想把cache真正持久化到磁盤,寫程序時注意執行net.sf.ehcache.Cache.put(Element element)後要調用flush()方法
        diskExpiryThreadIntervalSeconds==磁盤緩存的清理線程運行間隔,默認是120秒
        diskSpoolBufferSizeMB============設置DiskStore(磁盤緩存)的緩存區大小,默認是30MB
        memoryStoreEvictionPolicy========內存存儲與釋放策略,即達到maxElementsInMemory限制時,Ehcache會根據指定策略清理內存
                                         共有三種策略,分別爲LRU(最近最少使用)、LFU(最常用的)、FIFO(先進先出)
-->
    <defaultCache
            maxElementsInMemory="10000"         
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            diskSpoolBufferSizeMB="30"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            statistics="false"
            />
    <cache name="user1"
           maxElementsInMemory="10000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
      />
    <cache name="user2"
           maxElementsInMemory="10000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
      />
     <cache name="user3"
           maxElementsInMemory="10000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
      />
       <cache name="user4"
           maxElementsInMemory="10000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
      />
</ehcache>

3.spring使用EhCacheCacheManager作爲Ehcache緩存實現緩存管理器,因此只要該對象在Spring容器中,它就可以作爲緩存管理器使用,但EhCacheCacheManager底層要依賴一個net.sf.ehcache.CacheManager作爲實際的緩存管理器。Spring配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:cache="http://www.springframework.org/schema/cache"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
            http://www.springframework.org/schema/cache  
            http://www.springframework.org/schema/cache/spring-cache-3.2.xsd"     
      default-lazy-init="true" default-merge="true">
    <context:annotation-config/>
    <!-- 自動掃描指定包及其子包下的所有bean類 -->
    <context:component-scan base-package="com.aspect.service,com.aspect.service.impl,com.home.bean,com.home.model,cache">
    <!-- 只是以Axe結尾的類當成Spring容器中的bean -->
    <!-- type:指定過濾器類型
         1.annotation:Annotation過濾器,該過濾器需要指定一個Annotation名,如lee.AnnotationTest
         2.assignable:類過濾器,該過濾器直接指定一個java類
         3.regex,正則表達式過濾器,指定過濾規則,使用如下
         4.aspectJ:AspectJ過濾器,如org.example.*Service+
     -->
    <!--<context:include-filter type="regex" expression=".*Axe"/>  -->
    <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect"/>
    <context:exclude-filter type="regex" expression=".*TeacherConfig"/>
    <context:exclude-filter type="assignable" expression="org.springframework.context.support.ResourceBundleMessageSource"/>
    <context:exclude-filter type="regex" expression="com.home.model.C*"/>
    </context:component-scan>
     <!-- 啓動@AspectJ支持 -->
    <aop:aspectj-autoproxy/>
    <!-- 啓用緩存註解功能(請將其配置在Spring主配置文件中) -->  
     <cache:annotation-driven cache-manager="cacheManager"/>  
    <!-- 國際化配置開始 -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" lazy-init="true">
       <!-- 驅動spring調用messageSource bean的setBasenems()方法,該方法需要一個數組參數,使用list元素配置多個數組元素 -->
      <property name="basenames">
            <list>
                <value>message</value>
            </list>
            <!-- 如果有多個資源文件,全部列在此處 -->
       </property></bean>
    <bean id="persons" class="com.home.bean.Persons"></bean>
    <!-- 國際化配置結束 -->
    <!-- 數據庫連接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/onlinebookshop"/>
        <property name="username" value="root"/>
        <property name="password" value=""/>
    </bean>
    <!-- 有了這個bean的聲明,spring纔會去掃描@Autowired註解,@Autowired也纔有用 -->
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/><!--  -->
    </bean>
     <!-- 緩存配置 -->  
        <!-- 啓用緩存註解功能(請將其配置在Spring主配置文件中) -->  
        <cache:annotation-driven cache-manager="cacheManager"/>  
        <!-- Spring自己的基於java.util.concurrent.ConcurrentHashMap實現的緩存管理器(該功能是從Spring3.1開始提供的) -->  
        <!--   
        <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">  
            <property name="caches">  
                <set>  
                    <bean name="myCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"/>  
                </set>  
            </property>  
        </bean>  
         -->  
        <!-- 若只想使用Spring自身提供的緩存器,則註釋掉下面的兩個關於Ehcache配置的bean,並啓用上面的SimpleCacheManager即可 -->  
        <!-- Spring提供的基於的Ehcache實現的緩存管理器 -->  
        <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">  
            <property name="configLocation" value="classpath:ehcache.xml"/>  
        </bean>  
        <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">  
            <property name="cacheManager" ref="cacheManagerFactory"/>  
        </bean> 

三、緩存註解的說明

1.@Cacheable
可以標記在一個方法上,也可以標記在一個類上。當標記在一個方法上時表示該方法是支持緩存的,當標記在一個類上時則表示該類所有的方法都是支持緩存的。對於一個支持緩存的方法,Spring會在其被調用後將其返回值緩存起來,以保證下次利用同樣的參數來執行該方法時可以直接從緩存中獲取結果,而不需要再次執行該方法。Spring在緩存方法的返回值時是以鍵值對進行緩存的,值就是方法的返回結果,至於鍵的話,Spring又支持兩種策略,默認策略和自定義策略。
需要注意的是當一個支持緩存的方法在對象內部被調用時是不會觸發緩存功能的。@Cacheable可以指定三個屬性,value、key和condition。

2.@CachePut
在支持Spring Cache的環境下,對於使用@Cacheable標註的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的緩存元素,如果存在就不再執行該方法,而是直接從緩存中獲取結果進行返回,否則纔會執行並將返回結果存入指定的緩存中。而@CachePut也可以聲明一個方法支持緩存功能。與@Cacheable不同的是使用@CachePut標註的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。
@CachePut也可以標註在類上和方法上。使用@CachePut時我們可以指定的屬性跟@Cacheable是一樣的。
3.@CacheEvict
@CacheEvict是用來標註在需要清除緩存元素的方法或類上的。當標記在一個類上時表示其中所有的方法的執行都會觸發緩存的清除操作。@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的語義與@Cacheable對應的屬性類似。即value表示清除操作是發生在哪些Cache上的(對應Cache的名稱);key表示需要清除的是哪個key,如未指定則會使用默認策略生成的key;condition表示清除操作發生的條件。
4.主要屬性如下:
key:緩存的Key,當我們沒有指定該屬性時,Spring將使用默認策略生成key(表示使用方法的參數類型及參數值作爲key),key屬性是用來指定Spring緩存方法的返回結果時對應的key的。該屬性支持SpringEL表達式。我們還可以自定義策略:自定義策略是指我們可以通過Spring的EL表達式來指定我們的key。這裏的EL表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”或者“#p參數index”
key的生成策略有兩種:一種是默認策略,一種是自定義策略
¹默認的key生成策略是通過KeyGenerator生成的,其默認策略如下:
1.如果方法沒有參數,則使用0作爲key。
2.如果只有一個參數的話則使用該參數作爲key。
3.如果參數多餘一個的話則使用所有參數的hashCode作爲key
²自定義策略是指我們可以通過Spring的EL表達式來指定我們的key。這裏的EL表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”或者“#p參數index

condition:有的時候我們可能並不希望緩存一個方法所有的返回結果。通過condition屬性可以實現這一功能。condition屬性默認爲空,表示將緩存所有的調用情形。其值是通過SpringEL表達式來指定的,當爲true時表示進行緩存處理;當爲false時表示不進行緩存處理,即每次調用該方法時該方法都會執行一次。如下示例表示只有當user的id爲偶數時纔會進行緩存
allEntries:是boolean類型,表示是否需要清除緩存中的所有元素。默認爲false,表示不需要。當指定了allEntries爲true時,Spring Cache將忽略指定的key。有的時候我們需要Cache一下清除所有的元素,這比一個一個清除元素更有效率。
beforeInvocation:
清除操作默認是在對應方法成功執行之後觸發的,即方法如果因爲拋出異常而未能成功返回時也不會觸發清除操作。使用beforeInvocation可以改變觸發清除操作的時間,當我們指定該屬性值爲true時,Spring會在調用該方法之前清除緩存中的指定元素。
@CacheEvict(value=”users”, beforeInvocation=true)
public void delete(Integer id) {
System.out.println(“delete user by id: ” + id);
}
注意我們也可以使用ehcache的去除策略最近使用(LRU)”策略,其它還有先入先出FIFO,最少使用LFU,較少使用LRU
基於註解配置:
配置Spring對基於註解的Cache的支持,首先我們需要在Spring的配置文件中引入cache命名空間,其次通過就可以啓用Spring對基於註解的Cache的支持。
有一個mode屬性,可選值有proxy和aspectj。默認是使用proxy。當mode爲proxy時,只有緩存方法在外部被調用的時候Spring Cache纔會發生作用,這也就意味着如果一個緩存方法在其聲明對象內部被調用時Spring Cache是不會發生作用的。而mode爲aspectj時就不會有這種問題。另外使用proxy時,只有public方法上的@Cacheable等標註纔會起作用,如果需要非public方法上的方法也可以使用Spring Cache時把mode設置爲aspectj。此外,還可以指定一個proxy-target-class屬性,表示是否要代理class,默認爲false。我們前面提到的@Cacheable、@cacheEvict等也可以標註在接口上,這對於基於接口的代理來說是沒有什麼問題的,但是需要注意的是當我們設置proxy-target-class爲true或者mode爲aspectj時,是直接基於class進行操作的,定義在接口上的@Cacheable等Cache註解不會被識別到,那對應的Spring Cache也不會起作用了。
需要注意的是隻會去尋找定義在同一個ApplicationContext下的@Cacheable等緩存註解。

四、使用實例


 1. 封裝一個實體類
 package cache;
public class User {
    private String name;
    private int age;
    public User(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
2、接口
package cache;
public interface UserService {
    public User getUserByNameAndAge(String name,int age);
    public User getAnothreUser(String name,int age);
    public User getAnotherUser(String name,int age);
    public User getKeyValue(String name,int age);
    public User getKeyValues(String name,int age);
    public User getCondition(String name,int age);
    public User getConditions(String name,int age);
    public void evictUser(String name,int age);
    public void evictAll();
}

 1. 類級別的緩存
 package cache;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
 * 類級別的緩存
 */
/*
 * 當condition指定的條件爲true時,spring的緩存機制纔會執行緩存;當unless指定的條件爲true時,spring的緩存機制就不執行緩存
 */
@Service("userService")
@Cacheable(value="user1",condition="#age<100")
public class UserServiceImpl implements UserService{
    public User getUserByNameAndAge(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("------正在執行getUserByNameAndAge方法-----");
        return new User(name,age);
    }
    public User getAnothreUser(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("---------正在執行getAnothreUser方法------");
        return new User(name, age);
    }
    public User getAnotherUser(String name, int age) {
        return null;
    }
    public User getKeyValue(String name, int age) {
        System.out.println("------正在執行getKeyValue---");
        return new User(name, age);
    }
    public User getKeyValues(String name, int age) {
        System.out.println("------正在執行getKeyValues---");
        return new User(name, age);
    }
    public User getCondition(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("--------正在執行getCondition--------");
        return new User(name, age);
    }
    public User getConditions(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("--------正在執行getConditions--------");
        return new User(name, age);
    }
    public void evictUser(String name, int age) {
        // TODO Auto-generated method stub

    }
    public void evictAll() {
        // TODO Auto-generated method stub

    }
}
package spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import cache.User;
import cache.UserService;
public class CacheTest {
    public static void main(String[] args) {
        ApplicationContext fsc=new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
        UserService us=fsc.getBean("userService",UserService.class);
        //第一次調用us對象的方法時會執行該方法,並緩存方法的結果
        User u1=us.getUserByNameAndAge("xieyongxue",24);
        //第二次調用us對象的方法時直接利用緩存的數據,並不真正執行該方法
        User u2=us.getAnothreUser("xieyongxue", 24);
        System.out.println(u1==u2);//輸出true
    }
}

2、方法級別的緩存
package cache;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
/**
 * 通過指定方法緩存
 * @author xieyongxue
 *
 */
@Component("personService")
public class PersonServiceImpl implements UserService{
    @Cacheable(value="user1")
    public User getUserByNameAndAge(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("------正在執行getUserByNameAndAge()方法-------");
        return new User(name, age);
    }
    @Cacheable(value="user2")
    public User getAnothreUser(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("------正在執行getAnothreUser()方法-------");
        return new User(name, age);
    }
    @Cacheable(value="user2")
    public User getAnotherUser(String name, int age){
        System.out.println("------正在執行getAnotherUser()方法-------");
        return new User(name, age);
    }
    //key:通過SpEL表達式顯示指定緩存的KEY,這裏指定以name作爲參數作爲緩存的key,這樣只要調用的方法
    //具有相同的name參數,Spring緩存機制就會生效。
    @Cacheable(value="user3",key="#name")
    public User getKeyValue(String name,int age){
        System.out.println("------正在執行getKeyValue---");
        return new User(name, age);
    }
    @Cacheable(value="user3",key="#name")
    public User getKeyValues(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("------正在執行只getKeyValues---");
        return new User(name, age);
    }

    @Cacheable(value="user4",condition="#age<100")
    public User getCondition(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("--------正在執行getCondition--------");
        return new User(name, age);
    }
    @Cacheable(value="user4",condition="#age<100")
    public User getConditions(String name, int age) {
        // TODO Auto-generated method stub
        System.out.println("--------正在執行getConditions--------");
        return new User(name, age);
    }
    @CacheEvict(value="user4")
    public void evictUser(String name,int age){
        System.out.println("---正在清空--"+name+","+age+"對應的緩存--");
    }
    @CacheEvict(value="user3",allEntries=true)
    public void evictAll(){
        System.out.println("--正在清空所有緩存---");
    }
}

package spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import cache.User;
import cache.UserService;
public class CacheTest2 {
    public static void main(String[] args) {
        ApplicationContext fsc=new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
        UserService us=fsc.getBean("personService",UserService.class);
        //第一次調用us對象的方法時會執行該方法,並緩存方法的結果
        User u1=us.getUserByNameAndAge("xieyongxue",24);
        //由於getAnothreUser()方法使用另一個緩存區,因此無法使用getUserByNameAndAge()緩存區中的數據
        User u2=us.getAnothreUser("xieyongxue", 24);
        //第三次調用us對象方法會執行該方法,並緩存結構
        User u3=us.getAnotherUser("xieyongxue", 24);
        //us.getAnotherUser("xieyongxue", 24)已經執行過一次,故下面代碼使用緩存
        User u4=us.getAnotherUser("xieyongxue", 24);
        System.out.println(u1==u2);//輸出false
        System.out.println(u1==u3);//輸出false
        System.out.println(u2==u3);//輸出true
        System.out.println(u3==u4);//輸出true
        System.out.println("##########以下爲測試指定參數作爲緩存的KEY################");
        /**
         * 以下爲測試指定參數作爲緩存的KEY
         */
        User u5=us.getKeyValue("xieyongxue", 24);
        User u6=us.getKeyValues("xieyongxue", 23);
        System.out.println(u5==u6);//輸出true
        /**
         * 下面爲指定condition條件作爲true時緩存,spring的緩存機制纔會執行緩存
         */
        System.out.println("當condition指定的條件爲true時,spring的緩存機制纔會執行緩存");
        User u7=us.getCondition("xieyongxue", 10);
        User u8=us.getConditions("xieyongxue",10);
        User u9=us.getConditions("xieyongxue",10);
        System.out.println(u7==u8);//輸出true
        System.out.println(u7==u9);//輸出true
        System.out.println(u8==u9);//輸出true
        /**
         * 清除緩存
         */
        System.out.println("############清除緩存##########");
        us.evictUser("xieyongxue", 10);
        //由於清除了指定的
        User u10=us.getCondition("xieyongxue",10);
        User u11=us.getConditions("xieyongxue", 10);
        System.out.println(u7==u10);//輸出false
        System.out.println(u7==u11);//輸出false
    }
}
發佈了91 篇原創文章 · 獲贊 7 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章