注:作者水平有限,有错误请大佬及时评论纠正,谢谢!
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-09-04 12:34:51.550 ERROR 8692 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Field cacheManager in com.springbootdemo.common.service.common.EcacheService required a bean of type 'net.sf.ehcache.CacheManager' that could not be found.
Action:
Consider defining a bean of type 'net.sf.ehcache.CacheManager' in your configuration.
Disconnected from the target VM, address: '127.0.0.1:61743', transport: 'socket'
Process finished with exit code 1
@EnableCaching注解是spring framework中的注解驱动的缓存管理功能。自spring版本3.1起加入了该注解。如果你使用了这个注解,那么你就不需要在XML文件中配置cache manager了。
当你在配置类(@Configuration)上使用@EnableCaching注解时,会触发一个post processor,这会扫描每一个spring bean,查看是否已经存在注解对应的缓存。如果找到了,就会自动创建一个代理拦截方法调用,使用缓存的bean执行处理。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="java.io.tmpdir/springbootdemo"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
eternal="false"
maxElementsInMemory="16834"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="0"
timeToLiveSeconds="60"
diskExpireThreadIntervalSeconds ="120"
/>
<cache
name="userLoginInfo"
eternal="false"
maxElementsInMemory="16834"
overflowToDisk="true"
diskPersistent="true"
timeToIdleSeconds="43200"
timeToLiveSeconds="0"
maxElementsOnDisk = "0"
/>
</ehcache>
Caused by: org.xml.sax.SAXException: null:39: Element <defaultCache> does not allow attribute "diskExpireThreadIntervalSeconds".
at net.sf.ehcache.config.BeanHandler.setAttribute(BeanHandler.java:281) ~[ehcache-core-2.1.0.jar:na]
at net.sf.ehcache.config.BeanHandler.startElement(BeanHandler.java:112) ~[ehcache-core-2.1.0.jar:na]
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source) ~[na:1.8.0_144]
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(Unknown Source) ~[na:1.8.0_144]
at javax.xml.parsers.SAXParser.parse(Unknown Source) ~[na:1.8.0_144]
at net.sf.ehcache.config.ConfigurationFactory.parseConfiguration(ConfigurationFactory.java:155) ~[ehcache-core-2.1.0.jar:na]
... 96 common frames omitted
Disconnected from the target VM, address: '127.0.0.1:61936', transport: 'socket'
Process finished with exit code 1
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>com.googlecode.ehcache-spring-annotations</groupId>
<artifactId>ehcache-spring-annotations</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
package com.springbootdemo.common.service.common;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
public class EcacheService {
@Autowired
private CacheManager cacheManager;
public Object get(String names,Object key){
Element element = cacheManager.getCache(names).get(key);
if(element == null){
return null;
}
return element.getObjectValue();
}
public String getString(String names,Object key){
Element element = cacheManager.getCache(names).get(key);
if(element == null){
return null;
}
return element.getObjectValue().toString();
}
public <T> T get(String names,Object key,Class<T> clazz){
Element element = cacheManager.getCache(names).get(key);
if(element == null){
return null;
}
return (T)element.getObjectValue();
}
public void set(String names,Object key, Object value){
cacheManager.getCache(names).put(new Element(key,value));
}
}
package com.springbootdemo.common.shiro;
import com.springbootdemo.common.base.Result;
import com.springbootdemo.common.constant.CacheKeys;
import com.springbootdemo.common.enums.ResultCode;
import com.springbootdemo.common.exception.ApiShiroException;
import com.springbootdemo.common.exception.CommonException;
import com.springbootdemo.common.pojo.po.SysUserDO;
import com.springbootdemo.common.service.common.EcacheService;
import com.springbootdemo.common.util.JwtUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Set;
public class ApiShiroRealm extends AuthorizingRealm {
protected static final Logger logger = LoggerFactory.getLogger(ApiShiroRealm.class);
public static Set<String> ALL_PERMISSIONS_SET;
public static final String[] DEFAULT_PERMISSIONS_ARRAY = {"add","update","view","opt","delete"};
@Autowired
private EcacheService ecacheService;
@Override
public String getName(){
return "Realm";
}
@Override
public boolean supports(AuthenticationToken token){
return token != null && ApiShiroToken.class.isAssignableFrom(token.getClass());
}
/**
* 登录认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
ApiShiroToken token = (ApiShiroToken) authenticationToken;
try {
if(StringUtils.isBlank(token.getToken())){
throw new ApiShiroException(Result.fail(ResultCode.CODE_101,"token错误"));
}
Integer userId = JwtUtils.getUserId(token.getToken());
if(userId == null){
throw new ApiShiroException(Result.fail(ResultCode.CODE_101,"token解析错误"));
}
SimpleAuthenticationInfo authenticationInfo = (SimpleAuthenticationInfo) ecacheService.get(CacheKeys.USER_LOGIN_INFO,userId);
if(authenticationInfo == null){
throw new ApiShiroException(Result.fail(ResultCode.CODE_101,"token已过期"));
}
return authenticationInfo;
}catch (Exception e){
throw new CommonException(Result.fail(""),e);
}
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
return ((ApiShiroUser)principal.getPrimaryPrincipal()).getAuthorizationInfo();
}
/**
* api shiro 登录
* @return
*/
public ApiShiroUser apiShiroLogin(SysUserDO user, ApiShiroToken apiShiroToken, Set<String> roles, Set<String> permissions){
//权限设置
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
//用户信息生成,token创建
ApiShiroUser apiShiroUser = new ApiShiroUser();
apiShiroUser.setUserId(user.getId());
apiShiroUser.setUsername(user.getUsername());
apiShiroUser.setRealName(user.getRealName());
apiShiroUser.setAuthorizationInfo(authorizationInfo);
apiShiroUser.setToken(JwtUtils.createToken(user.getId()));
//shiro认证信息存储
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(apiShiroUser, apiShiroUser.getToken(), this.getName());
ecacheService.set(CacheKeys.USER_LOGIN_INFO, user.getId(), authenticationInfo);
return apiShiroUser;
}
}