Spring boot中Shiro和Redis集成后,Spring的@cacheble注解无效。
情况1:Spring boot和Redis集成,@cacheble可用,会把缓存数据写入Redis。
在情况1的前提下:
情况2:Spring boot和Shiro集成,然后用Spring cache抽象出cachemanager,作为Shiro的缓存。控制台未报错,Shiro的认证信息会存入Redis,但出现@cacheble注解无效,即@Cacheble注解的方法的返回值未存入Redis。
在情况2的前提下:
情况3:将Shiro的@Configuration注解去掉,即不用Shiro,@Cacheble可用。
Redis配置清单:
import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import javax.annotation.Resource; import java.lang.reflect.Method; @Configuration @EnableCaching public class RedisCacheConfig extends CachingConfigurerSupport { /** * 通过Spring进行注入,参数在application.properties进行配置{RedisProperties}; */ @Resource private JedisConnectionFactory factory; @Bean @Override public CacheManager cacheManager() { System.out.println("RedisCacheConfig.cacheManager : 实例化"); RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate()); cacheManager.setDefaultExpiration(60 * 30); // 30min return cacheManager; } @Bean public RedisTemplate<Object, Object> redisTemplate() { System.out.println("RedisCacheConfig.redisTemplate : 实例化"); RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>(); redisTemplate.setConnectionFactory(factory); redisTemplate.setKeySerializer(new GenericJackson2JsonRedisSerializer()); // redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } /** * 自定义key. * 此方法将会根据完全限定类名+方法名+所有参数的值生成唯一的一个key,即使@Cacheable中的value属性一样,key也会不一样。 */ @Bean @Override public KeyGenerator keyGenerator() { System.out.println("RedisCacheConfig.keyGenerator : 实例化"); return new KeyGenerator() { @Override public Object generate(Object o, Method method, Object... objects) { // This will generate a unique key of the class name, the method name // and all method parameters appended. StringBuilder sb = new StringBuilder(); sb.append(o.getClass().getName()); sb.append(method.getName()); for (Object obj : objects) { sb.append(obj.toString()); } System.out.println("RedisCacheConfig : keyGenerator <== " + sb.toString()); return sb.toString(); } }; } @Bean @Override public CacheResolver cacheResolver() { return new SimpleCacheResolver(cacheManager()); } @Bean @Override public CacheErrorHandler errorHandler() { // 用于捕获从Cache中进行CRUD时的异常的回调处理器。 return new SimpleCacheErrorHandler(); }
缓存配置:
import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Collection; import java.util.Set; @Component public class SpringCacheManagerWrapper implements CacheManager { public static final Logger log = LoggerFactory.getLogger(SpringCacheManagerWrapper.class); @Resource private org.springframework.cache.CacheManager springCacheManger; @Override public <K, V> Cache<K, V> getCache(String name) throws CacheException { log.debug("getCache: springCacheManger <== " + springCacheManger); org.springframework.cache.Cache springCache = springCacheManger.getCache(name); return new SpringCacheWrapper<K, V>(springCache); } private class SpringCacheWrapper<K, V> implements Cache<K, V> { private org.springframework.cache.Cache springCache; public SpringCacheWrapper(org.springframework.cache.Cache springCache) { this.springCache = springCache; } @Override public V get(K key) throws CacheException { Object value = springCache.get(key); if (value instanceof SimpleValueWrapper) { return (V) ((SimpleValueWrapper)value).get(); } return (V) value; } @Override public V put(K key, V value) throws CacheException { springCache.put(key, value); return value; } @Override public V remove(K key) throws CacheException { Object value = get(key); springCache.evict(key); return (V) value; } @Override public void clear() throws CacheException { springCache.clear(); } @Override public int size() { throw new UnsupportedOperationException("invoke spring cache abstract size method not supported"); } @Override public Set<K> keys() { throw new UnsupportedOperationException("invoke spring cache abstract keys method not supported"); } @Override public Collection<V> values() { throw new UnsupportedOperationException("invoke spring cache abstract values method not supported"); } } }
Shiro配置:
package me.dengfengdecao.demo.config.shiro; import me.dengfengdecao.demo.component.cache.SpringCacheManagerWrapper; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.CachingSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfiguration { /* 缓存管理器 使用Redis实现 */ @Resource private SpringCacheManagerWrapper springCacheManagerWrapper; @Bean(name="shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); System.out.println("ShiroConfiguration.shiroFilterFactoryBean : shiro实例化"); // 拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 filterChainDefinitionMap.put("/**", "authc"); filterChainDefinitionMap.put("admin/**", "authc, roles[admin]"); filterChainDefinitionMap.put("docs/**", "authc, perms[document:read]"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * shiro核心安全管理类 * @return */ @Bean public SecurityManager securityManager() { CachingSecurityManager securityManager = new DefaultWebSecurityManager(myShiroRealm()); System.out.println("ShiroConfiguration.securityManager : 实例化"); // 将myShiroRealm注入到securityManager中 // securityManager.setRealm(myShiroRealm()); // 将缓存对象注入到SecurityManager中 // securityManager.setCacheManager(shiroRedisCacheManager); securityManager.setCacheManager(springCacheManagerWrapper); return securityManager; } @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); // 注入凭证匹配器 myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); System.out.println("ShiroConfiguration.myShiroRealm : 实例化"); return myShiroRealm; } /** * 凭证匹配器 * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); System.out.println("ShiroConfiguration.hashedCredentialsMatcher : 实例化"); // 加密算法 hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列的次数,比如散列两次,相当于 md5(md5("")); hashedCredentialsMatcher.setHashIterations(2); return hashedCredentialsMatcher; } /** * 开启shiro aop注解支持.使用代理方式;所以需要开启代码支持;<br/> * 拦截@ RequiresPermissions注释的方法 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); System.out.println("ShiroConfiguration.authorizaionAttributeSourceAdvisor : 实例化"); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
Realm
import me.dengfengdecao.demo.domain.Permission; import me.dengfengdecao.demo.domain.Role; import me.dengfengdecao.demo.domain.User; import me.dengfengdecao.demo.service.UserService; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Resource; import java.util.Iterator; import java.util.Set; /** * 身份校验核心类<br/> * * Created by dengfengdecao on 16/10/1. */ public class MyShiroRealm extends AuthorizingRealm { public static final Logger log = LoggerFactory.getLogger(MyShiroRealm.class); @Resource private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { log.info("######## 执行Shiro权限认证 ########"); String username = (String) principalCollection.getPrimaryPrincipal(); log.info("doGetAuthorizationInfo: username <== " + username); // 获取当前用户信息 User user = userService.findByUsername(username); log.debug("doGetAuthorizationInfo: user <== " + user); if (user != null) { // 权限信息对象authorizationInfo,用来存放查出的用户的所有的角色(role)及权限(permission) SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); // 当前用户的角色集合 Iterator<Role> iterator = user.getRoles().iterator(); while (iterator.hasNext()) { authorizationInfo.addRole(iterator.next().getRole()); } // 当前用户的角色对应的所有权限 Set<Role> roles = user.getRoles(); for (Role role : roles) { Iterator<Permission> iterator1 = role.getPermissions().iterator(); while (iterator1.hasNext()) authorizationInfo.addStringPermission(iterator1.next().getPermission()); } return authorizationInfo; } // 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址 return null; } /** * 用来验证用户身份 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { log.info("######## 执行Shiro身份认证 ########"); // 获取用户登录的用户名 String username = (String) authenticationToken.getPrincipal(); log.debug("doGetAuthenticationInfo: 当前用户username <== " + username); // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 User user = userService.findByUsername(username); log.debug("doGetAuthenticationInfo: 从数据库查出的用户user <== " + user); if (user == null) { return null; } else { //UsernamePasswordToken对象用来存放提交的登录信息 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; log.info("doGetAuthenticationInfo: 验证当前Subject时获取到token为:" + token); // 加密方式 // 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName()); return authenticationInfo; } } }
UserServiceImpl
@Service @Transactional @CacheConfig(cacheNames = {"user"}) public class UserServiceImpl implements UserService { public static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class); @Resource private UserDao userDao; @Cacheable @Override public User findUserById(Long id) { return userDao.findOne(id); } @Cacheable @Override public User findByUsername(String username) { return userDao.findByUsername(username); } @CacheEvict @Override public void delFromCache(Long id) { log.info("delFromCache: 移除缓存"); } @Override public void save(User user) { userDao.save(user); } @Cacheable @Override public List<User> getUsers() { return (List<User>) userDao.findAll(); }
UserController
@Controller public class UserController { public static final Logger log = LoggerFactory.getLogger(UserController.class); @Resource private UserService userService; @RequestMapping(value = "user/{userId}", method = RequestMethod.GET) @ResponseBody public Object findUserById(Model model, @PathVariable("userId")Long userId) { log.info("findUserById: userId : " + userId); User user = userService.findUserById(userId); return user.toString(); } @RequestMapping(value = "user", method = RequestMethod.GET) @ResponseBody public String findUserByUsername(@RequestParam("username")String username){ log.info("findUserByUsername: 执行"); User user = userService.findByUsername(username); return user.toString(); } @RequestMapping(value = "user2", method = RequestMethod.GET) @ResponseBody public String findUserByUsername2(@RequestParam("username")String username){ log.info("findUserByUsername2: 执行"); User user = userService.findByUsername(username); return user.toString(); } @RequestMapping(value = "user/delete") @ResponseBody public String delete(Long id) { log.info("delete: 执行"); userService.delFromCache(id); return "success"; } /** * 用户查询<br/> * @ RequiresPermissions 进行权限管理 * @return */ @RequestMapping("user-list") @RequiresPermissions("user:list") public String userList(ModelMap map){ List<User> users = userService.getUsers(); log.info("userList: users <== " + users); map.put("users", users); return "user/user-list"; } /** * 用户添加 * @return */ @RequestMapping("user-add") @RequiresPermissions("user:add") public String userAdd(){ log.info("userAdd: "); return "user/user-add"; } /** * 用户删除 * @return */ @RequestMapping("user-del") @RequiresPermissions("user:del") public String userDelete(){ log.info("userDelete: "); return "user/user-del"; } }
application.properties
######################################################## ### HTTP encoding (HttpEncodingProperties) ######################################################## # Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly. spring.http.encoding.charset=UTF-8 # Enable http encoding support. spring.http.encoding.enabled=true # Force the encoding to the configured charset on HTTP requests and responses. spring.http.encoding.force=true # Force the encoding to the configured charset on HTTP requests. Defaults to true when "force" has not been specified. spring.http.encoding.force-request=true # Force the encoding to the configured charset on HTTP responses. spring.http.encoding.force-response=true ######################################################## ### spring ######################################################## spring.mvc.view.prefix=/WEB-INF/content/ spring.mvc.view.suffix=.jsp spring.mvc.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8 ######################################################## ### REDIS (RedisProperties) ######################################################## spring.redis.database=0 spring.redis.host=127.0.0.1 spring.redis.pool.max-active=8 spring.redis.pool.max-idle=8 spring.redis.pool.max-wait=-1 spring.redis.pool.min-idle=0 spring.redis.port=6379 ######################################################## ### Java Persistence Api ######################################################## # Specify the DBMS spring.jpa.database=MYSQL # Show or not log for each sql query spring.jpa.show-sql=true # Hibernate ddl auto (create, create-drop, update) spring.jpa.hibernate.ddl-auto=update # Naming strategy # spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.ImprovedNamingStrategy # stripped before adding them to the entity manager spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect spring.jpa.properties.hibernate.format_sql=true # Register OpenEntityManagerInViewInterceptor. # Binds a JPA EntityManager to the thread for the entire processing of the request. spring.jpa.open-in-view=true
请大家帮帮分析原因。
付费偷看金额在0.1-10元之间
一周热门 更多>