[java手把手教程][第二季]java後端部落格系統文章系統——No10

pc859107393發表於2017-08-03

專案github地址:github.com/pc859107393…

實時專案同步的地址是國內的碼雲:git.oschina.net/859107393/m…

我的簡書首頁是:www.jianshu.com/users/86b79…

上一期是:[手把手教程][第二季]java 後端部落格系統文章系統——No9

行走的java全棧
行走的java全棧

工具

  • IDE為idea2017.1.5
  • JDK環境為1.8
  • gradle構建,版本:2.14.1
  • Mysql版本為5.5.27
  • Tomcat版本為7.0.52
  • 流程圖繪製(xmind)
  • 建模分析軟體PowerDesigner16.5
  • 資料庫工具MySQLWorkBench,版本:6.3.7build

本期目標

完成EhCache接入

Ehcache資源引入

在我們工程目錄下面的build.gradle檔案中引入gradle資源。

// ehcache核心依賴
compile group: 'net.sf.ehcache', name: 'ehcache', version: '2.10.3'

// mybatis-ehcache依賴
compile group: 'org.mybatis.caches', name: 'mybatis-ehcache', version: '1.1.0'

//shiro-ehcache依賴
compile group: 'org.apache.shiro', name: 'shiro-ehcache', version: '1.4.0'複製程式碼

開啟Ehcache相關配置

本來按照道理來說,我們的Ehcache是二級快取用來降低資料庫壓力,也就應該寫入我們的spring-dao.xml中,但是Ehcache因為要和Shiro整合,我們順帶也就將其寫入spring-web.xml中,如下:

    <!-- ================ Shiro start ================ -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="AccoutRealm"/>
        <!-- 二級快取 -->
        <property name="cacheManager" ref="shiroCacheManager"/>
    </bean>

    <!-- 專案自定義的Realm -->
    <bean id="AccoutRealm" class="cn.acheng1314.shiro.ShiroRealm">
        <!-- 自定義密碼加密演算法  -->
        <property name="credentialsMatcher" ref="passwordMatcher"/>
    </bean>

    <bean id="passwordMatcher" class="cn.acheng1314.shiro.MyCredentialsMatcher"/>

    <!-- Shiro Filter -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 核心安全介面 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- 登入頁面 -->
        <property name="loginUrl" value="/main/login"/>
        <!-- 登陸成功頁面 -->
        <property name="successUrl" value="/endSupport/index"/>
        <!-- 未授權頁面 -->
        <property name="unauthorizedUrl" value="/endSupport/unauthorized"/>
        <!-- shiro 連線約束配置 -->
        <property name="filterChainDefinitions">
            <value>
                /static/*/** = anon
                <!--前臺可以匿名訪問-->
                /front/*/** = anon
                /index.jsp = anon
                /static/uploadFiles/** = anon
                /endSupport/*/** = authc
                /druid/*/** = authc
            </value>
        </property>
    </bean>

    <cache:annotation-driven cache-manager="cacheManager"/>

    <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:config/shiro-ehcache.xml"/>
        <property name="shared" value="true"></property> <!-- 這裡是關鍵!!!沒有必錯  -->
    </bean>

    <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManager" ref="ehCacheManager"/>
        <!--配置檔案-->
        <!--<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>-->
    </bean>

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
          p:cacheManager-ref="ehCacheManager"/>

    <bean id="lifecycleBeanPostProcessor"
          class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true"/>
    </bean>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>
    <!--================ Shiro end ================ -->複製程式碼

上面的配置中,我們可以將Ehcache的配置完全的拆分出來,如下:

    <cache:annotation-driven cache-manager="cacheManager"/>

    <bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:config/shiro-ehcache.xml"/>
        <property name="shared" value="true"></property> <!-- 這裡是關鍵!!!沒有必錯  -->
    </bean>

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
          p:cacheManager-ref="ehCacheManager"/>複製程式碼

在上面的配置中,我們開啟了spring的cache,注入了‘org.springframework.cache.CacheManager’的子類作為我們的CacheManager。

CacheManager具體由EhCacheCacheManager實現。在EhCacheCacheManager中有以下的方法來實現CacheManager的注入。

    /**
     * Set the backing EhCache {@link net.sf.ehcache.CacheManager}.
     */
    public void setCacheManager(net.sf.ehcache.CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }複製程式碼

所以最後我們通過實現具體的EhCacheManagerFactoryBean來引入cache的設定。當然在EhCacheManagerFactoryBean中我們可以找到對應的方法如:configLocation和shared。我們通過配置configLocation引入了shiro-ehcache.xml這個配置檔案。

接著,我們在Shiro中引入Ehcache。配置如下:

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="AccoutRealm"/>
    <!-- 二級快取 -->
    <property name="cacheManager" ref="shiroCacheManager"/>
</bean>
<bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <property name="cacheManager" ref="ehCacheManager"/>
    <!--配置檔案-->
    <!--<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>-->
</bean>複製程式碼

從上面不難看出我們把Ehcache注入到了Shiro的securityManager中,同時我們的shiroCacheManager具體是由ehCacheManager來實現。

當然最上面的程式碼中出掉Ehcache相關的,剩下的就是Shiro的相關設定了。

Ehcache配置檔案

我們從上面可以看到Ehcache配置檔案是在config目錄下的shiro-ehcache.xml檔案,具體內容如下:

<ehcache updateCheck="false" name="shiroCache">
    <diskStore path="java.io.tmpdir"/>

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"/>

    <!--updateCheck="false" 不檢查更新當前使用的Ehcache的版本
        eternal:快取中物件是否為永久的,如果是,超時設定將被忽略,物件從不過期。
        maxElementsInMemory:快取中允許建立的最大物件數
        overflowToDisk:記憶體不足時,是否啟用磁碟快取。
        timeToIdleSeconds:快取資料的鈍化時間,也就是在一個元素消亡之前,
         兩次訪問時間的最大時間間隔值,這隻能在元素不是永久駐留時有效,
         如果該值是 0 就意味著元素可以停頓無窮長的時間。
        timeToLiveSeconds:快取資料的生存時間,也就是一個元素從構建到消亡的最大時間間隔值,
         這隻能在元素不是永久駐留時有效,如果該值是0就意味著元素可以停頓無窮長的時間。
        memoryStoreEvictionPolicy:快取滿了之後的淘汰演算法。
        1 FIFO,先進先出
        2 LFU,最少被使用,快取的元素有一個hit屬性,hit值最小的將會被清出快取。
        3 LRU,最近最少使用的,快取的元素有一個時間戳,當快取容量滿了,而又需要騰出地方來快取新的元素的時候,那麼現有快取元素中時間戳離當前時間最遠的元素將被清出快取。-->
</ehcache>複製程式碼

啟用快取

在上面我們已經把快取相關的東西已經設定完成了,現在我們是需要對dao層啟用快取。這個時候我們需要怎麼做呢?兩種方法!

方法一:在mapper配置檔案中直接粗暴的啟用設定

我們直接編輯mapper配置檔案加入以下內容:

<cache type="org.mybatis.caches.ehcache.LoggingEhcache">
    <property name="timeToIdleSeconds" value="3600"/>
    <property name="timeToLiveSeconds" value="3600"/>
    <property name="maxEntriesLocalHeap" value="1000"/>
    <property name="maxEntriesLocalDisk" value="10000000"/>
    <property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>複製程式碼

對於我們不想啟用快取的方法,直接在末尾加上‘ useCache="false" ’,如:

<select id="findAllPublish" resultType="cn.acheng1314.domain.PostCustom" useCache="false">
    ···省略方法詳細sql···
</select>複製程式碼

方法二:在service層的方法處註解

我們先看看以前不加快取的service方法是怎麼樣子的。

@Service("weichatService")
public class WeichatServiceImpl {

    @Autowired
    private SiteConfigDao siteConfigDao;

    @Autowired
    private WeChatDao weChatDao;

    public static String updateMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=";

    /**
     * 同步微信選單到微信公眾號上面
     *
     * @return
     */
    public String synWeichatMenu() {
        try {
            WeiChatMenuBean menuBean = creatWeMenuList();
            if (null == menuBean) return GsonUtils.toJsonObjStr(null, ResponseCode.FAILED, "選單內容不能為空!");
            String menuJson = GsonUtils.toJson(menuBean);
            LogPrintUtil.getInstance(this.getClass()).logOutLittle(menuJson);
            WeiChatResPM pm = null; //微信響應的應答
            String responseStr = HttpClientUtil.doJsonPost(String.format("%s%s", updateMenuUrl, getAccessToken()), menuJson);
            LogPrintUtil.getInstance(this.getClass()).logOutLittle(responseStr);
            pm = GsonUtils.fromJson(responseStr, WeiChatResPM.class);
            if (pm.getErrcode() == 0) return GsonUtils.toJsonObjStr(null, ResponseCode.OK, "同步微信選單成功!");
            else throw new Exception(pm.getErrmsg());
        } catch (Exception e) {
            e.printStackTrace();
            return GsonUtils.toJsonObjStr(null, ResponseCode.FAILED, "同步失敗!原因:" + e.getMessage());
        }
    }

    public String getAccessToken() throws Exception {
        MyWeiConfig weiConfig = getWeiConfig();
        return WeiChatUtils.getSingleton(weiConfig.getAppid(), weiConfig.getAppsecret()).getWeAccessToken();
    }

    /**
     * 獲取微信設定,包裝了微信的appid,secret和token
     *
     * @return
     */
    public MyWeiConfig getWeiConfig() {
        String weiChatAppid = "", weichatAppsecret = "", token = "";
        MyWeiConfig apiConfig;
        try {
            List<HashMap<String, String>> siteInfo = getAllSiteInfo();
            LogPrintUtil.getInstance(this.getClass()).logOutLittle(siteInfo.toString());
            for (HashMap<String, String> map : siteInfo) {

                Set<Map.Entry<String, String>> sets = map.entrySet();      //獲取HashMap鍵值對

                for (Map.Entry<String, String> set : sets) {             //遍歷HashMap鍵值對
                    String mKey = set.getValue();
                    if (mKey.contains(MySiteMap.WECHAT_APPID)) {
                        weiChatAppid = map.get("option_value");
                    } else if (mKey.contains(MySiteMap.WECHAT_APPSECRET))
                        weichatAppsecret = map.get("option_value");
                    else if (mKey.contains(MySiteMap.WECHAT_TOKEN))
                        token = map.get("option_value");
                }
            }
            apiConfig = new MyWeiConfig(weiChatAppid, weichatAppsecret, token);
            return apiConfig;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 儲存微信設定,內部遍歷
     *
     * @param weiConfig 微信的設定資訊
     * @throws Exception
     */
    public void saveConfig(MyWeiConfig weiConfig) throws Exception {
        if (weiConfig != null && !StringUtils.isEmpty(weiConfig.getAppid(), weiConfig.getAppsecret(), weiConfig.getToken())) {
            String[] key = {MySiteMap.WECHAT_APPID, MySiteMap.WECHAT_APPSECRET, MySiteMap.WECHAT_TOKEN};
            String[] value = {weiConfig.getAppid(), weiConfig.getAppsecret(), weiConfig.getToken()};
            for (int i = 0; i < key.length; i++)
                siteConfigDao.updateOneByKey(key[i], value[i]);
        } else {
            throw new Exception("微信相關設定不能為空!");
        }
    }

    public MyWeChatMenu findOneById(Integer id) {
        return weChatDao.findOneById(id);
    }

    ······
}複製程式碼

如果我們加上對應的cache註解後是什麼樣子呢?這裡就不得不提spring-cache!

因為我們專案本身核心框架是spring,也就是依賴spring的相關框架都有對應的spring實現。也就是說這時候,我們在Ehcache下面找不到對應註解的時候我們開啟spring-cache包後,可以找到註解了。這時候我們直接拿spring-cache會有什麼效果呢?

其實這時候我們完全可以參考別人的寫法,畢竟比人已經很完善了,我就不再一一搞出來了。

當然簡單概括的說說主要是用了三個關鍵字:@Cacheable、@CachePut、@CacheEvict。詳細介紹請查閱下面的介紹。

關於註解快取的簡單介紹:blog.csdn.net/wabiaozia/a…

關於註解快取的深度介紹:blog.csdn.net/wabiaozia/a…

快取整合完畢後,我們可以在伺服器上看到對應的快取資源如下:

快取檔案
快取檔案


如果你認可我所做的事情,並且認為我做的事對你有一定的幫助,希望你也能打賞我一杯咖啡,謝謝。

支付寶捐贈
支付寶捐贈

相關文章