springmvc4.3+ehcache2.10 集成测试
springmvc4.3+ehcache2.10 集成测试
说起这个,很惭愧,花费了大半天的时间才搞定;过程很坎坷;我只想说一句 -fuck me-
- 出现配置问题;主要是把ehcache-config.xml文件import进去,然后通过一个base.xml文件加载到contextConfigLocation中;---【发现不行】
- 后来放在xxx-servlet.xml文件中,依然是import进去;【好使】
<mvc:annotation-driven/>
<context:component-scan base-package="org.exceptiondemo"/>
<import resource="classpath*:ecache-config.xml"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
*** -接着-**
- 针对注解
@Cacheable(value=cacheName)
发现可指定key,和不指定key,都不好使; - 后来经测试,单纯的key(比如#id)这样好用;但是对于
SearchModel
这种实体的就不行了;---【废了很大劲很大劲,关键还没解决】 - -最后-
- 怀疑是不是每次请求,被认为不同的
SearchModel
对象呢? - 尼玛,果然实现了
equal\hashcode
就可以正常命中缓存了;【fuck】- 【2017-06-14】查看了官方的一个说明This approach works well for most use-cases; As long as parameters have natural keys and implement valid hashCode() and equals() methods. If that is not the case the the strategy needs to be changed.
http://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/html/cache.html
- 【2017-06-14】查看了官方的一个说明This approach works well for most use-cases; As long as parameters have natural keys and implement valid hashCode() and equals() methods. If that is not the case the the strategy needs to be changed.
感受
期间翻阅了百度、Google、bing;中英文文章,大多是针对很老的版本;
- 有使用
ehcache
+com.googlecode.ehcache.annotations
;发现spring3.1之后,就封装了ehcache的支持;具体可以看org.springframework.cache.ehcache
; - 还有使用
org.apache.ecache
的;--- 这个没看,具体不过多废话
最后,贴完整代码
Step1、引入依赖,只要是ehcache + spring-context-support;其他根据自己情况添加即可;
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.0.RELEASE</version>
</dependency>
<!-- JSTL taglib -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.1</version>
</dependency>
Step2、配置cache规则;ehcache.xml文件;
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
>
<diskStore path="java.io.tmpdir/demo"/>
<defaultCache eternal="false"
maxEntriesLocalHeap="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"/>
<cache name="use_search_model"
eternal="false"
maxEntriesLocalHeap="200"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="600"
statistics="true"
timeToLiveSeconds="600"/>
<cache name="baseCache"
eternal="false"
maxEntriesLocalHeap="200"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="600"
statistics="true"
timeToLiveSeconds="600"/>
<!--
eternal="false" // 元素是否永恒,如果是就永不过期(必须设置)
maxElementsInMemory="1000" // 缓存容量的内存最大值(必须设置)
overflowToDisk="false" // 当缓存达到maxElementsInMemory值是,是否允许溢出到磁盘(必须设置)
diskPersistent="false" // 磁盘缓存在VM重新启动时是否保持(默认为false)
timeToIdleSeconds="0" // 导致元素过期的访问间隔(秒为单位). 0表示可以永远空闲,默认为0
timeToLiveSeconds="600" // 元素在缓存里存在的时间(秒为单位). 0 表示永远存在不过期
memoryStoreEvictionPolicy="LFU" // 当达到maxElementsInMemory时,如何强制进行驱逐默认使用"最近使用(LRU)"策略,其它还有先入先出FIFO,最少使用LFU,较少使用LRU
-->
</ehcache>
Step3、配置ecache-config.xml,主要是让spring管理ehcache
<?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:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd ">
<cache:annotation-driven cache-manager="cacheManager"/>
<bean id="ehcacheManager" 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="ehcacheManager"/>
<!--<property name="transactionAware" value="true"/>-->
</bean>
</beans>
Step4、引入到项目中,比如我的spring-servlet.xml中;
网上介绍有两种方式,一个是引入web.xml中,一种是引入xxx-servlet.xml中;而我只是测试放入xxx-servlet.xml中;直接放入web.xml中没测试通过;
<?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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
<context:component-scan base-package="org.exceptiondemo"/>
<!--引入配置-->
<import resource="classpath*:ecache-config.xml"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
测试代码
Controller代码
@Controller
public class IndexController {
@Autowired
private UserService userService;
private static Logger log = LoggerFactory.getLogger(IndexController.class);
@RequestMapping(value = "/")
public String hello() throws IOException {
List<String> result = userService.selectByIds(9527L);
String msg = "print log, current level: {},result = {}";
log.info(msg, "info", result);
SearchModel searchModel = new SearchModel();
String r = userService.selectById(searchModel);
// render hello.jsp page
return "index";
}
}
Service层代码
@Service
public class UserServiceImpl implements UserService {
private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Cacheable(value = "use_search_model")//, key = "#root.targetClass+#root.methodName")
public String selectById(SearchModel searchModel) {
logger.info("i am in with searchModel .");
return "user_" + searchModel.getId();
}
@Cacheable(value = "baseCache", key = "#id")
public List<String> selectByIds(Long id) {
logger.info("i am in.");
List<String> lists = new ArrayList<String>();
lists.add("user1_" + id);
lists.add("user2_" + id);
return lists;
}
}
SearchModel
特别注意,需要实现
each
、hashcode
重写;否则,@Cacheable
不好使;
public class SearchModel {
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getCategory() {
return category;
}
public void setCategory(Integer category) {
this.category = category;
}
public String getBeginTime() {
return beginTime;
}
public void setBeginTime(String beginTime) {
this.beginTime = beginTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
public String getSearchDate() {
return searchDate;
}
public void setSearchDate(String searchDate) {
this.searchDate = searchDate;
}
private Long id;
private Integer category;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private String beginTime;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private String endTime;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private String searchDate;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SearchModel that = (SearchModel) o;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (category != null ? !category.equals(that.category) : that.category != null) return false;
if (beginTime != null ? !beginTime.equals(that.beginTime) : that.beginTime != null) return false;
if (endTime != null ? !endTime.equals(that.endTime) : that.endTime != null) return false;
return searchDate != null ? searchDate.equals(that.searchDate) : that.searchDate == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (category != null ? category.hashCode() : 0);
result = 31 * result + (beginTime != null ? beginTime.hashCode() : 0);
result = 31 * result + (endTime != null ? endTime.hashCode() : 0);
result = 31 * result + (searchDate != null ? searchDate.hashCode() : 0);
return result;
}
}
最后补上logback日志配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!-- logback中一共有5种有效级别,分别是TRACE、DEBUG、INFO、WARN、ERROR,优先级依次从低到高 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="DIR_NAME" value="testlogback"/>
<property name="user.dir" value="d:"/>
<!-- 将记录日志打印到控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<!-- RollingFileAppender begin -->
<appender name="ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>30MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/trace.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>TRACE</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<appender name="SPRING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 根据时间来制定滚动策略 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 根据文件大小来制定滚动策略 -->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
</encoder>
</appender>
<!-- RollingFileAppender end -->
<!-- logger begin -->
<!-- 本项目的日志记录,分级打印 -->
<logger name="org.exceptiondemo" level="TRACE" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ERROR"/>
<appender-ref ref="WARN"/>
<appender-ref ref="INFO"/>
<appender-ref ref="DEBUG"/>
<appender-ref ref="TRACE"/>
</logger>
<!-- SPRING框架日志 -->
<logger name="org.springframework" level="TRACE" additivity="false">
<appender-ref ref="SPRING"/>
</logger>
<root level="TRACE">
<appender-ref ref="ALL"/>
<appender-ref ref="STDOUT"/>
</root>
<!-- logger end -->
</configuration>
注意和限制
基于 proxy 的 spring aop 带来的内部调用问题
上面介绍过 spring cache 的原理,即它是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题,如果对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效
说在最后
以上是一个小白在使用
ehcache
过程中踩到的坑;走过的坎坷路;而对于功能来说,其实只是冰山一角;还有很多比较常用的注解、key生成规则;以及如何支持集群、如何预热cache数据等等;
以上言论和代码,只代表个人;如果有人愿意指导或者交流,不胜感激。本人java小白,拜师也是可以的。😃
资料
- 预热 http://www.ehcache.org/documentation/2.8/configuration/configuration#cache-warming-for-multi-tier-caches
- https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/
- http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html#key--
- http://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/html/cache.html
点滴积累,每天进步一点点!O(∩_∩)O~