美文网首页java程序员
[java手把手教程][第二季]java后端博客系统文章系统——

[java手把手教程][第二季]java后端博客系统文章系统——

作者: Clone丶记忆 | 来源:发表于2017-08-03 11:16 被阅读275次

    项目github地址:https://github.com/pc859107393/SpringMvcMybatis

    实时项目同步的地址是国内的码云:https://git.oschina.net/859107393/mmBlog-ser

    我的简书首页是:http://www.jianshu.com/users/86b79c50cfb3/latest_articles

    上一期是:[手把手教程][第二季]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。详细介绍请查阅下面的介绍。

    关于注解缓存的简单介绍:http://blog.csdn.net/wabiaozia/article/details/51596508

    关于注解缓存的深度介绍:http://blog.csdn.net/wabiaozia/article/details/51596546


    如果你认可我所做的事情,并且认为我做的事对你有一定的帮助,希望你也能打赏我一杯咖啡,谢谢。

    支付宝打赏支付宝打赏

    相关文章

      网友评论

        本文标题:[java手把手教程][第二季]java后端博客系统文章系统——

        本文链接:https://www.haomeiwen.com/subject/oqzulxtx.html