目录
[2.1. @Cacheable](#2.1. @Cacheable)
[2.2. @CachePut](#2.2. @CachePut)
[2.3. @CacheEvict](#2.3. @CacheEvict)
一.整合
1.1.整合应用
1.1.1.pom配置
在项目的 pom.xml 文件中添加Redis的依赖
以下是导入的所有依赖 :
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>ssm2</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>ssm2 Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.plugin.version>3.7.0</maven.compiler.plugin.version> <!--添加jar包依赖--> <!--1.spring 5.0.2.RELEASE相关--> <spring.version>5.0.2.RELEASE</spring.version> <!--2.mybatis相关--> <mybatis.version>3.4.5</mybatis.version> <!--mysql--> <mysql.version>5.1.44</mysql.version> <!--pagehelper分页jar依赖--> <pagehelper.version>5.1.2</pagehelper.version> <!--mybatis与spring集成jar依赖--> <mybatis.spring.version>1.3.1</mybatis.spring.version> <!--3.dbcp2连接池相关 druid--> <commons.dbcp2.version>2.1.1</commons.dbcp2.version> <commons.pool2.version>2.4.3</commons.pool2.version> <!--4.log日志相关--> <log4j2.version>2.9.1</log4j2.version> <!--5.其他--> <junit.version>4.12</junit.version> <servlet.version>4.0.0</servlet.version> <lombok.version>1.18.2</lombok.version> <ehcache.version>2.10.0</ehcache.version> <slf4j-api.version>1.7.7</slf4j-api.version> <redis.version>2.9.0</redis.version> <redis.spring.version>1.7.1.RELEASE</redis.spring.version> </properties> <dependencies> <!--1.spring相关--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!--2.mybatis相关--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!--pagehelper分页插件jar包依赖--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>${pagehelper.version}</version> </dependency> <!--mybatis与spring集成jar包依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybatis.spring.version}</version> </dependency> <!--3.dbcp2连接池相关--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>${commons.dbcp2.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>${commons.pool2.version}</version> </dependency> <!--4.log日志相关依赖--> <!--核心log4j2jar包--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j2.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${log4j2.version}</version> </dependency> <!--web工程需要包含log4j-web,非web工程不需要--> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-web</artifactId> <version>${log4j2.version}</version> </dependency> <!--5.其他--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- jsp依赖--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> <!-- 做服务端参数校验 JSR303 的jar包依赖 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.7.Final</version> </dependency> <!-- 用来SpringMVC支持json数据转换--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.3</version> </dependency> <!-- shiro相关依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>${ehcache.version}</version> </dependency> <!-- slf4j核心包 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j-api.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j-api.version}</version> <scope>runtime</scope> </dependency> <!--用于与slf4j保持桥接 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>${log4j2.version}</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>${redis.version}</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>${redis.spring.version}</version> </dependency> </dependencies> <build> <finalName>ssm2</finalName> <resources> <!--解决mybatis-generator-maven-plugin运行时没有将XxxMapper.xml文件放入target文件夹的问题--> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <!--解决mybatis-generator-maven-plugin运行时没有将jdbc.properites文件放入target文件夹的问题--> <resource> <directory>src/main/resources</directory> <includes> <include>*.properties</include> <include>*.xml</include> </includes> </resource> </resources> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven.compiler.plugin.version}</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <dependencies> <!--使用Mybatis-generator插件不能使用太高版本的mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> </dependencies> <configuration> <overwrite>true</overwrite> </configuration> </plugin> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging --> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
1.1.2.所需配置
在SSM项目中创建一个 spring-readis.xml,配置Redis连接信息,配置数据源,配置序列化器及redis的key生成策略,配置RedisTemplate。
创建redis.properties 进行编写数据连接的信息,包括主机名、端口号、密码等。
redis.hostName=localhost
redis.port=6379
redis.password=123456
redis.timeout=10000
redis.maxIdle=300
redis.maxTotal=1000
redis.maxWaitMillis=1000
redis.minEvictableIdleTimeMillis=300000
redis.numTestsPerEvictionRun=1024
redis.timeBetweenEvictionRunsMillis=30000
redis.testOnBorrow=true
redis.testWhileIdle=true
redis.expiration=3600
创建 spring-redis.xml 配置文件,在其中配置以下
redis连接池的配置
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!--最大空闲数-->
<property name="maxIdle" value="${redis.maxIdle}"/>
<!--连接池的最大数据库连接数 -->
<property name="maxTotal" value="${redis.maxTotal}"/>
<!--最大建立连接等待时间-->
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<!--逐出连接的最小空闲时间 默认1800000毫秒(30分钟)-->
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
<!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3-->
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
<!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1-->
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
<!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个-->
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
<!--在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="${redis.testWhileIdle}"/>
</bean>
配置redis连接工厂
<!-- 3. redis连接工厂 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="poolConfig" ref="poolConfig"/>
<!--IP地址 -->
<property name="hostName" value="${redis.hostName}"/>
<!--端口号 -->
<property name="port" value="${redis.port}"/>
<!--如果Redis设置有密码 -->
<property name="password" value="${redis.password}"/>
<!--客户端超时时间单位是毫秒 -->
<property name="timeout" value="${redis.timeout}"/>
</bean>
redis操作模板
<!-- 4. redis操作模板,使用该对象可以操作redis
相当于session,专门操作数据库。
-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<!--如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!! -->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
<!--开启事务 -->
<property name="enableTransactionSupport" value="true"/>
</bean>
配置缓存管理器
<!-- 5.配置缓存管理器 -->
<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg name="redisOperations" ref="redisTemplate"/>
<!--redis缓存数据过期时间单位秒-->
<property name="defaultExpiration" value="${redis.expiration}"/>
<!--是否使用缓存前缀,与cachePrefix相关-->
<property name="usePrefix" value="true"/>
<!--配置缓存前缀名称-->
<property name="cachePrefix">
<bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
<constructor-arg index="0" value="-cache-"/>
</bean>
</property>
</bean>
创建 CacheKeyGenerator.java 配置缓存生成键名的生成规则
package com.junlinyi.ssm.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
@Slf4j
public class CacheKeyGenerator implements KeyGenerator {
// custom cache key
public static final int NO_PARAM_KEY = 0;
public static final int NULL_PARAM_KEY = 53;
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder key = new StringBuilder();
key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append(":");
if (params.length == 0) {
key.append(NO_PARAM_KEY);
} else {
int count = 0;
for (Object param : params) {
if (0 != count) {//参数之间用,进行分隔
key.append(',');
}
if (param == null) {
key.append(NULL_PARAM_KEY);
} else if (ClassUtils.isPrimitiveArray(param.getClass())) {
int length = Array.getLength(param);
for (int i = 0; i < length; i++) {
key.append(Array.get(param, i));
key.append(',');
}
} else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
key.append(param);
} else {//Java一定要重写hashCode和eqauls
key.append(param.hashCode());
}
count++;
}
}
String finalKey = key.toString();
// IEDA要安装lombok插件
log.debug("using cache key={}", finalKey);
return finalKey;
}
}
最后 spring-redis.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: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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- 1. 引入properties配置文件 -->
<!--<context:property-placeholder location="classpath:redis.properties" />-->
<!-- 2. redis连接池配置-->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!--最大空闲数-->
<property name="maxIdle" value="${redis.maxIdle}"/>
<!--连接池的最大数据库连接数 -->
<property name="maxTotal" value="${redis.maxTotal}"/>
<!--最大建立连接等待时间-->
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<!--逐出连接的最小空闲时间 默认1800000毫秒(30分钟)-->
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
<!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3-->
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
<!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1-->
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
<!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个-->
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
<!--在空闲时检查有效性, 默认false -->
<property name="testWhileIdle" value="${redis.testWhileIdle}"/>
</bean>
<!-- 3. redis连接工厂 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="poolConfig" ref="poolConfig"/>
<!--IP地址 -->
<property name="hostName" value="${redis.hostName}"/>
<!--端口号 -->
<property name="port" value="${redis.port}"/>
<!--如果Redis设置有密码 -->
<property name="password" value="${redis.password}"/>
<!--客户端超时时间单位是毫秒 -->
<property name="timeout" value="${redis.timeout}"/>
</bean>
<!-- 4. redis操作模板,使用该对象可以操作redis
相当于session,专门操作数据库。
-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<!--如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!! -->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
<!--开启事务 -->
<property name="enableTransactionSupport" value="true"/>
</bean>
<!-- 5.配置缓存管理器 -->
<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg name="redisOperations" ref="redisTemplate"/>
<!--redis缓存数据过期时间单位秒-->
<property name="defaultExpiration" value="${redis.expiration}"/>
<!--是否使用缓存前缀,与cachePrefix相关-->
<property name="usePrefix" value="true"/>
<!--配置缓存前缀名称-->
<property name="cachePrefix">
<bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
<constructor-arg index="0" value="-cache-"/>
</bean>
</property>
</bean>
<!--6.配置缓存生成键名的生成规则-->
<bean id="cacheKeyGenerator" class="com.junlinyi.ssm.redis.CacheKeyGenerator"></bean>
<!--7.启用缓存注解功能-->
<cache:annotation-driven cache-manager="redisCacheManager" key-generator="cacheKeyGenerator"/>
</beans>
创建 applicationContext-shiro.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置自定义的Realm-->
<bean id="shiroRealm" class="com.junlinyi.ssm.shiro.MyRealm">
<property name="userBiz" ref="userBiz" />
<!--注意:重要的事情说三次~~~~~~此处加密方式要与用户注册时的算法一致 -->
<!--注意:重要的事情说三次~~~~~~此处加密方式要与用户注册时的算法一致 -->
<!--注意:重要的事情说三次~~~~~~此处加密方式要与用户注册时的算法一致 -->
<!--以下三个配置告诉shiro将如何对用户传来的明文密码进行加密-->
<property name="credentialsMatcher">
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--指定hash算法为MD5-->
<property name="hashAlgorithmName" value="md5"/>
<!--指定散列次数为1024次-->
<property name="hashIterations" value="1024"/>
<!--true指定Hash散列值使用Hex加密存. false表明hash散列值用用Base64-encoded存储-->
<property name="storedCredentialsHexEncoded" value="true"/>
</bean>
</property>
</bean>
<!--注册安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="sessionManager" ref="sessionManager"></property>
<property name="realm" ref="shiroRealm" />
</bean>
<!--Shiro核心过滤器-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager" />
<!-- 身份验证失败,跳转到登录页面 -->
<property name="loginUrl" value="/login"/>
<!-- 身份验证成功,跳转到指定页面 -->
<!--<property name="successUrl" value="/index.jsp"/>-->
<!-- 权限验证失败,跳转到指定页面 -->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!-- Shiro连接约束配置,即过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
<!--
注:anon,authcBasic,auchc,user是认证过滤器
perms,roles,ssl,rest,port是授权过滤器
-->
<!--anon 表示匿名访问,不需要认证以及授权-->
<!--authc表示需要认证 没有进行身份认证是不能进行访问的-->
<!--roles[admin]表示角色认证,必须是拥有admin角色的用户才行-->
/user/login=anon
/user/updatePwd.jsp=authc
/admin/*.jsp=roles[4]
/user/teacher.jsp=perms[2]
<!-- /css/** = anon
/images/** = anon
/js/** = anon
/ = anon
/user/logout = logout
/user/** = anon
/userInfo/** = authc
/dict/** = authc
/console/** = roles[admin]
/** = anon-->
</value>
</property>
</bean>
<!-- Shiro生命周期,保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Session ID 生成器 -->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator">
</bean>
<!--sessionDao自定义会话管理,针对Session会话进行CRUD操作-->
<bean id="customSessionDao" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO">
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<!--会话监听器-->
<bean id="shiroSessionListener" class="com.junlinyi.ssm.shiro.MySessionListener"/>
<!--会话cookie模板-->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!--设置cookie的name-->
<constructor-arg value="shiro.session"/>
<!--设置cookie有效时间 永不过期-->
<property name="maxAge" value="-1"/>
<!--设置httpOnly 防止xss攻击:cookie劫持-->
<property name="httpOnly" value="true"/>
</bean>
<!--SessionManager会话管理器-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!--设置session会话过期时间 毫秒 2分钟=120000-->
<property name="globalSessionTimeout" value="120000"/>
<!--设置sessionDao-->
<property name="sessionDAO" ref="customSessionDao"/>
<!--设置间隔多久检查一次session的有效性 默认1分钟-->
<property name="sessionValidationInterval" value="60000"/>
<!--配置会话验证调度器-->
<!--<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>-->
<!--是否开启检测,默认开启-->
<!--<property name="sessionValidationSchedulerEnabled" value="true"/>-->
<!--是否删除无效的session,默认开启-->
<property name="deleteInvalidSessions" value="true"/>
<!--配置session监听器-->
<property name="sessionListeners">
<list>
<ref bean="shiroSessionListener"/>
</list>
</property>
<!--会话Cookie模板-->
<property name="sessionIdCookie" ref="sessionIdCookie"/>
<!--取消URL后面的JSESSIONID-->
<property name="sessionIdUrlRewritingEnabled" value="true"/>
</bean>
</beans>
在引用的配置文件中引用以上配置的文件 如 applicationContext.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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--1. 引入外部多文件方式 -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
<value>classpath:redis.properties</value>
</list>
</property>
</bean>
<!-- 框架会越学越多,不能将所有的框架配置,放到同一个配制间,否者不便于管理 -->
<import resource="applicationContext-mybatis.xml"></import>
<import resource="spring-redis.xml"></import>
<import resource="applicationContext-shiro.xml"></import>
</beans>
以上是基本的配置步骤,根据具体的项目需求和框架版本可能会有所不同。在实际应用中,还可以根据需要配置Redis的集群、哨兵模式、持久化等功能,以提高系统的可用性和可靠性。
二.注解式开发及应用场景
2.1. @Cacheable
@Cacheable是Spring Framework中的一个注解,用于声明方法的返回值是可以被缓存的。这意味着方法的返回值将被缓存在内存中,以便之后的调用可以直接返回缓存的结果,而无需再次执行该方法。
具体来说,@Cacheable注解的作用如下:
**1. 缓存结果:**当一个被@Cacheable注解修饰的方法被调用时,Spring会先检查缓存中是否存在该方法的返回结果。如果缓存中已经存在,则直接返回缓存中的结果,而不再执行方法体内的代码逻辑。
2. 缓存键生成:****@Cacheable注解可以指定一个缓存键(key)来标识缓存中的数据。默认情况下,缓存键是由方法的参数组成的。如果两次调用的方法参数相同,则会使用相同的缓存键,从而直接返回缓存中的结果。
3. 缓存管理:****@Cacheable注解可以与其他缓存管理工具(如Redis、Ehcache等)进行整合使用。通过在配置文件中配置相应的缓存管理器,可以将方法的返回结果存储到指定的缓存中,以供后续的调用使用。
4. 缓存失效:****@Cacheable 注解还可以指定一个失效时间(TTL)来控制缓存的有效期。当缓存的有效期过期后,下一次调用该方法时会重新执行方法体内的代码逻辑,并将新的结果存储到缓存中。
使用***@Cacheable注解可以有效地减少对数据库或其他资源的访问次数,提高系统的响应速度和并发处理能力***。但需要注意的是,使用缓存时需要权衡缓存的一致性和实时性,避免数据不一致或过期的问题。
常用属性及用法作用
@Cacheable注解有以下常用属性及用法作用:
**value:**指定缓存的名称,用于区分不同的缓存空间。可以在配置文件中配置相应的缓存管理器,以决定将缓存存储在哪个缓存空间中。可以指定多个缓存名称,使用逗号分隔。
**key:**指定缓存的键,用于标识缓存中的数据。默认情况下,缓存键是由方法的参数组成的。可以使用SpEL表达式来指定缓存键,例如:@Cacheable(key = "#id"),其中id是方法的参数。
**condition:**指定一个SpEL表达式,用于判断是否执行缓存操作。只有当表达式的结果为true时,才会执行缓存操作。例如:@Cacheable(condition = "#result != null"),表示只有当方法的返回结果不为空时,才会执行缓存操作。
**unless:**指定一个SpEL表达式,用于判断是否不执行缓存操作。只有当表达式的结果为false时,才会执行缓存操作。例如:@Cacheable(unless = "#result == null"),表示只有当方法的返回结果为空时,才不会执行缓存操作。
**keyGenerator:**指定一个自定义的缓存键生成器,用于生成缓存的键。可以实现KeyGenerator接口来自定义缓存键的生成逻辑。
使用**@Cacheable**注解可以将方法的返回结果缓存起来,提高系统的响应速度和并发处理能力。通过指定缓存名称、缓存键、条件和键生成器等属性,可以对缓存进行更加精细的控制。同时,需要注意权衡缓存的一致性和实时性,避免数据不一致或过期的问题。
基本用法示例:
@Cacheable(value = "clz" ,key = "'cid'+#cid")
用法内属性解释:
- value: 用于指定缓存的名称,你可以在配置文件中定义不同的缓存管理器,这里的"value"指定了要使用的缓存。
- key属性:使用SpEL表达式,"'cid'+#cid"生成缓存的键。这个键将由方法的cid参数的值动态生成。如果方法被调用时传入不同的cid值,将会生成不同的缓存键,因此不同的cid值将会对应不同的缓存项。这允许你在不同的上下文中存储和获取缓存数据。
2.2. @CachePut
**
@CachePut
**是 Spring Framework 中的一个注解,用于将方法的返回值存储到缓存中,通常用于更新缓存中的数据。
@CachePut方法作用:
- 更新缓存数据:@CachePut 用于强制将方法的返回值存储到缓存中,无论缓存中是否已存在相同的键。这对于确保缓存中的数据是最新的非常有用,特别是在需要手动更新缓存数据时。
- 动态生成缓存键:@CachePut 允许你使用 Spring Expression Language (SpEL) 表达式来动态生成缓存项的键。这使得你可以根据方法的参数或其他条件来生成缓存键,以确保不同的缓存项具有不同的键。
- 条件性更新:@CachePut 支持 condition 和 unless 属性,可以根据条件来控制是否执行缓存更新操作。这使你可以在特定条件下才更新缓存,从而更加灵活地管理缓存。
- **清空指定缓存项:**通过配置 allEntries 属性为 true,可以清空与指定缓存相关的所有缓存项,而不仅仅是更新一个特定的缓存项。
- **控制更新时机:**使用 beforeInvocation 属性,你可以控制是在方法执行之前还是方法执行成功后触发缓存更新。
注:
- @CachePut 注解用于更新缓存中的数据,不同于 @Cacheable,它会执行方法体,并将方法返回的值存入缓存中,以确保缓存中的数据是最新的。
- 如果缓存中的数据不存在,@CachePut将创建一个新的缓存项。
基本用法示例:
@CachePut(value = "xx",key = "'cid:'+#cid")
用法内属性解释:
- **
@CachePut
**注解用于将方法的返回值存储到缓存中,通常用于更新缓存中的数据。- 与**
@Cacheable
**不同,它不会检查缓存中是否已存在相同的键,而是直接将方法的返回值存入缓存,以确保缓存中的数据是最新的。- 这对于更新缓存项非常有用,以确保缓存中的数据与后端数据保持同步。
2.3. @CacheEvict
@CacheEvict
是 Spring Framework 中的一个注解,用于从缓存中移除指定的缓存项或清空整个缓存。
@CacheEvict方法作用:
- **清空指定缓存项:**你可以使用 @CacheEvict 来清空一个或多个特定缓存中的缓存项,以确保缓存中的数据保持最新或满足特定条件时清除缓存。
- 条件性清除:@CacheEvict 支持 condition 和 unless 属性,可以根据条件来控制是否执行缓存清除操作。这允许你在满足特定条件时才清除缓存,从而更加灵活地管理缓存。
- **清空整个缓存:**通过设置 allEntries 属性为 true,你可以清空整个缓存,而不仅仅是清除特定的缓存项。这对于需要在某些情况下全局清空缓存的场景非常有用。
- **控制清除时机:**使用 beforeInvocation 属性,你可以控制是在方法执行之前还是方法执行成功后触发缓存清除操作。
- **清除缓存的多个条目:**可以通过 key 属性设置一个缓存键表达式,以删除匹配特定键模式的缓存项。这允许你按一定的模式来删除缓存项。
@CacheEvict注解的主要作用是从缓存中移除指定的缓存项或清空整个缓存,以确保缓存中的数据保持最新或根据条件来清除缓存。它提供了多个属性,使你可以根据需要来配置清除缓存的方式和时机,以满足特定的业务需求。
基本用法示例:
@CacheEvict(value = "xx",key = "'cid:'+#cid",allEntries = true)
用法内属性解释:
- value 属性:设置为 "xx",表示要清除名为 "xx" 的缓存。通常,你需要在配置中定义相应的缓存管理器,以确保它与这个缓存名称关联。
- key 属性:使用 SpEL 表达式 "'cid:'+#cid" 来生成缓存项的键。这个键将由方法的 cid 参数值动态生成,前缀为 "cid:"。这将导致匹配 "cid:" 后跟 cid 参数值的缓存项被清除。
- allEntries 属性:设置为 true,表示清除整个缓存。如果 allEntries 设置为 true,则会忽略 key 属性,而是清除指定缓存中的所有缓存项。在这个示例中,不管 cid 参数的值如何,都会清空名为 "xx" 的缓存中的所有内容。
2.4.总结
@Cacheable
、@CachePut
和@CacheEvict
是 Spring Framework 中用于管理缓存的注解,它们有不同的作用和行为:
- @Cacheable:
- 作用:@Cacheable 用于声明一个方法的返回值可以被缓存,即当方法被调用时,Spring会首先检查缓存,如果缓存中存在相应的结果,就会返回缓存的值而不执行方法体。
- 主要用途: 提高性能,避免重复执行相同的方法,适用于读取操作,不用于更新数据**。**
- **配置:**可以指定缓存的名称、缓存键、条件等。
- @CachePut:
- 作用:@CachePut 用于强制将方法的返回值存储到缓存中,通常用于更新缓存中的数据。
- **主要用途:**更新缓存,将方法返回的值放入缓存,适用于写入操作。
- **配置:**可以指定缓存的名称、缓存键、条件等。
- @CacheEvict:
- 作用:@CacheEvict 用于从缓存中移除指定的缓存项或清空整个缓存。
- **主要用途:**清除缓存项,使缓存中的数据保持最新或根据条件来清除缓存。
- **配置:**可以指定缓存的名称、缓存键、条件、是否清空整个缓存等。
区别总结:
- **
@Cacheable
**用于缓存方法的返回值,避免重复执行方法,适用于读取操作。- **
@CachePut
**用于将方法的返回值存储到缓存中,通常用于更新缓存中的数据,适用于写入操作。- **
@CacheEvict
**用于清除缓存项,可以清空指定缓存项或整个缓存。关于使用:
这些注解可以根据具体的业务需求来组合使用,以实现灵活的缓存策略。例如,可以使用
@Cacheable
缓存读取操作的结果,使用**@CachePut
** 更新缓存项,使用**@CacheEvict
** 清除缓存中的数据,以满足不同的缓存需求。
三.redis的击穿穿透雪崩
当涉及到 Redis 时,"击穿"、"穿透"和"雪崩"是一些常见的问题,涉及缓存系统的高可用性和稳定性。下面我将对这三个问题进行详细解释:
- 击穿(Cache Breakdown):
出现在热点数据失效的情况下。当一个特定的键失效并且接收到了大量的并发请求,这些请求会绕过缓存直接访问数据库,导致数据库压力骤增。这是因为缓存失效后,下一次访问时无法从缓存中获取数据,而必须去数据库中获取。
- 穿透(Cache Penetration):
当恶意用户请求一个不存在于缓存和数据库中的键时,缓存系统无法提供数据,这些请求会直接访问数据库。这可能会导致数据库负载增加,甚至引发拒绝服务攻击。为了应对穿透问题,可以在查询不存在键时设置一个空值或者采用布隆过滤器等方法来过滤无效请求。
- 雪崩(Cache Avalanche):
当缓存中大量的键同时失效,导致大量请求直接访问后端数据库,使得数据库压力骤增,甚至崩溃。这通常是由于缓存中的数据设置了相同的过期时间,导致在同一时间大量的键失效,引发雪崩效应。为了避免雪崩问题,可以采用随机的过期时间、添加热点数据的预加载等策略。
为了应对这些问题,可以采取以下一些解决方案:
- 对于击穿问题,可以使用互斥锁或者设置短暂的过期时间来保护热点数据,确保缓存中有有效的数据。
- 对于穿透问题,可以使用布隆过滤器等技术来过滤无效请求,确保缓存系统只处理有效的请求。
- 对于雪崩问题,可以采用分布式缓存、多级缓存、缓存预热等策略,保证缓存中的数据分布均匀,避免大量数据同时失效。