springboot整合ehcache和redis實(shí)現(xiàn)多級緩存實(shí)戰(zhàn)案例
一、概述
在實(shí)際的工作中,我們通常會使用多級緩存機(jī)制,將本地緩存和分布式緩存結(jié)合起來,從而提高系統(tǒng)性能和響應(yīng)速度。本文通過springboot整合ehcache和redis實(shí)現(xiàn)多級緩存案例實(shí)戰(zhàn),從源碼角度分析下多級緩存實(shí)現(xiàn)原理。
二、實(shí)戰(zhàn)案例
1、pom依賴(注意引入cache和ehcache組件依賴)
<?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>cache-demo</artifactId> ? ? <version>1.0-SNAPSHOT</version> ? ? <properties> ? ? ? ? <maven.compiler.source>8</maven.compiler.source> ? ? ? ? <maven.compiler.target>8</maven.compiler.target> ? ? </properties> ? ? <parent> ? ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? ? <artifactId>spring-boot-starter-parent</artifactId> ? ? ? ? <version>2.5.0</version> ? ? </parent> ? ? <dependencies> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? ? ? ? <artifactId>spring-boot-starter-web</artifactId> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>junit</groupId> ? ? ? ? ? ? <artifactId>junit</artifactId> ? ? ? ? ? ? <version>4.12</version> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? ? ? ? <artifactId>spring-boot-starter-test</artifactId> ? ? ? ? ? ? <scope>test</scope> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>com.baomidou</groupId> ? ? ? ? ? ? <artifactId>mybatis-plus-boot-starter</artifactId> ? ? ? ? ? ? <version>3.4.3</version> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>mysql</groupId> ? ? ? ? ? ? <artifactId>mysql-connector-java</artifactId> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>com.alibaba</groupId> ? ? ? ? ? ? <artifactId>druid-spring-boot-starter</artifactId> ? ? ? ? ? ? <version>1.2.1</version> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.projectlombok</groupId> ? ? ? ? ? ? <artifactId>lombok</artifactId> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>com.alibaba</groupId> ? ? ? ? ? ? <artifactId>fastjson</artifactId> ? ? ? ? ? ? <version>1.2.76</version> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>com.alibaba</groupId> ? ? ? ? ? ? <artifactId>druid</artifactId> ? ? ? ? ? ? <version>1.1.23</version> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>com.google.guava</groupId> ? ? ? ? ? ? <artifactId>guava</artifactId> ? ? ? ? ? ? <version>23.0</version> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? ? ? ? <artifactId>spring-boot-starter-data-redis</artifactId> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? ? ? ? <artifactId>spring-boot-starter-cache</artifactId> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>net.sf.ehcache</groupId> ? ? ? ? ? ? <artifactId>ehcache</artifactId> ? ? ? ? ? ? <version>2.10.8</version> ? ? ? ? </dependency> ? ? ? ? <dependency> ? ? ? ? ? ? <groupId>org.springframework.boot</groupId> ? ? ? ? ? ? <artifactId>spring-boot-starter-aop</artifactId> ? ? ? ? </dependency> ? ? </dependencies> </project>
2、application.properties(啟動類加上:@EnableCaching注解)
server.port = 7001 spring.application.name = cache-demo #log config logging.config = classpath:log/logback.xml debug = false #mp config mybatis-plus.mapper-locations = classpath*:mapper/*.xml mybatis-plus.configuration.log-impl = org.apache.ibatis.logging.stdout.StdOutImpl spring.datasource.type = com.alibaba.druid.pool.DruidDataSource spring.datasource.druid.driver-class-name = com.mysql.cj.jdbc.Driver spring.datasource.url = jdbc:mysql://localhost:3306/數(shù)據(jù)庫?characterEncoding=utf-8 spring.datasource.username = 數(shù)據(jù)庫賬號 spring.datasource.password = 數(shù)據(jù)庫密碼 #redis config spring.redis.host = redis主機(jī) spring.redis.port = 6379 spring.redis.password=redis密碼,沒有就刪掉該配置 # ehcache config spring.cache.type = ehcache spring.cache.ehcache.config = classpath:ehcache.xml
3、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" ? ? ? ? ?updateCheck="false"> ? ? <diskStore path="D:\ehcache"/> ? ? <!--默認(rèn)緩存策略 --> ? ? <!-- external:是否永久存在,設(shè)置為true則不會被清除,此時與timeout沖突,通常設(shè)置為false--> ? ? <!-- diskPersistent:是否啟用磁盤持久化--> ? ? <!-- maxElementsInMemory:最大緩存數(shù)量--> ? ? <!-- overflowToDisk:超過最大緩存數(shù)量是否持久化到磁盤--> ? ? <!-- timeToIdleSeconds:最大不活動間隔,設(shè)置過長緩存容易溢出,設(shè)置過短無效果,單位:秒--> ? ? <!-- timeToLiveSeconds:最大存活時間,單位:秒--> ? ? <!-- memoryStoreEvictionPolicy:緩存清除策略--> ? ? <defaultCache ? ? ? ? ? ? eternal="false" ? ? ? ? ? ? diskPersistent="false" ? ? ? ? ? ? maxElementsInMemory="1000" ? ? ? ? ? ? overflowToDisk="false" ? ? ? ? ? ? timeToIdleSeconds="60" ? ? ? ? ? ? timeToLiveSeconds="60" ? ? ? ? ? ? memoryStoreEvictionPolicy="LRU"/> ? ? <cache ? ? ? ? ? ? name="studentCache" ? ? ? ? ? ? eternal="false" ? ? ? ? ? ? diskPersistent="false" ? ? ? ? ? ? maxElementsInMemory="1000" ? ? ? ? ? ? overflowToDisk="false" ? ? ? ? ? ? timeToIdleSeconds="100" ? ? ? ? ? ? timeToLiveSeconds="100" ? ? ? ? ? ? memoryStoreEvictionPolicy="LRU"/> </ehcache>
4、MybatisPlusConfig類(注意:@MapperScan注解,也可加在啟動類上)
@Configuration
@MapperScan("com.cache.demo.mapper")
public class MybatisPlusConfig {
? ? @Bean
? ? public MybatisPlusInterceptor mybatisPlusInterceptor() {
? ? ? ? //分頁插件
? ? ? ? MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
? ? ? ? mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
? ? ? ? return mybatisPlusInterceptor;
? ? }
}5、測試demo
這里可以將一級緩存、二級緩存時效設(shè)置短一些,方便進(jìn)行測試。
@Slf4j
@RestController
@RequestMapping("/cache")
public class CacheController {
? ? @Resource
? ? private StudentMapper studentMapper;
? ? @Autowired
? ? private StringRedisTemplate stringRedisTemplate;
? ?? ?// 添加緩存注解(一級緩存:ehcache)
? ? @Cacheable(value = "studentCache", key = "#id+'getStudentById'")
? ? @GetMapping("/getStudentById")
? ? public String getStudentById(Integer id) {
? ? ? ? String key = "student:" + id;
? ? ? ?? ?// 一級緩存中不存在,則從二級緩存:redis中查找
? ? ? ? String studentRedis = stringRedisTemplate.opsForValue().get(key);
? ? ? ? if (StringUtils.isNotBlank(studentRedis)) {
? ? ? ? ? ? return JSON.toJSONString(JSON.parseObject(studentRedis, Student.class));
? ? ? ? }
? ? ? ? // 二級緩存中不存在則查詢數(shù)據(jù)庫,并更新二級緩存、一級緩存
? ? ? ? Student student = studentMapper.selectStudentById(id);
? ? ? ? if (null != student) {
? ? ? ? ? ? stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(student));
? ? ? ? }
? ? ? ? return JSON.toJSONString(student);
? ? }
}6、啟動類上的:@EnableCaching注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
?? ?boolean proxyTargetClass() default false;
?? ?AdviceMode mode() default AdviceMode.PROXY;
? int order() default Ordered.LOWEST_PRECEDENCE;
}7、導(dǎo)入的:
CachingConfigurationSelector類:
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
?? ?@Override
?? ?public String[] selectImports(AdviceMode adviceMode) {
?? ??? ?switch (adviceMode) {
?? ??? ??? ?case PROXY:
? ? ? ? // 此處走的是:PROXY
?? ??? ??? ??? ?return getProxyImports();
?? ??? ??? ?case ASPECTJ:
?? ??? ??? ??? ?return getAspectJImports();
?? ??? ??? ?default:
?? ??? ??? ??? ?return null;
?? ??? ?}
?? ?}
?? ?private String[] getProxyImports() {
?? ??? ?List<String> result = new ArrayList<>(3);
? ? // 導(dǎo)入了AutoProxyRegistrar類和ProxyCachingConfiguration類
?? ??? ?result.add(AutoProxyRegistrar.class.getName());
?? ??? ?result.add(ProxyCachingConfiguration.class.getName());
?? ??? ?if (jsr107Present && jcacheImplPresent) {
?? ??? ??? ?result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
?? ??? ?}
?? ??? ?return StringUtils.toStringArray(result);
?? ?}
}8、AutoProxyRegistrar類(代碼有所簡化):
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
?? ?private final Log logger = LogFactory.getLog(getClass());
?? ?@Override
?? ?public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
?? ??? ??? ??? ?// 最終注冊了:InfrastructureAdvisorAutoProxyCreator(BeanPostProcessor接口實(shí)現(xiàn)類)
? ? ?? ??? ?// 通過重寫postProcessAfterInitialization接口創(chuàng)建代理對象
? ? ? ?? ?AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
?? ?}
}
@Nullable
?? ?public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
?? ??? ?return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
?? ?}9、導(dǎo)入的第一個類看完了,接著看導(dǎo)入的第二個類:ProxyCachingConfiguration
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
?? ?@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
?? ?@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
?? ?public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
?? ??? ?// ?構(gòu)建BeanFactoryCacheOperationSourceAdvisor
? ? BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
?? ??? ?// 設(shè)置緩存注解解析器
? ? advisor.setCacheOperationSource(cacheOperationSource);
?? ??? ?// 設(shè)置緩存攔截器:cacheInterceptor
? ? advisor.setAdvice(cacheInterceptor);
?? ??? ?if (this.enableCaching != null) {
?? ??? ??? ?advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
?? ??? ?}
?? ??? ?return advisor;
?? ?}
?? ?@Bean
?? ?@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
?? ?public CacheOperationSource cacheOperationSource() {
? ? // 緩存注解解析器
?? ??? ?return new AnnotationCacheOperationSource();
?? ?}
?? ?@Bean
?? ?@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
?? ?public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
?? ??? ?// 緩存攔截器
? ? CacheInterceptor interceptor = new CacheInterceptor();
?? ??? ?interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
?? ??? ?interceptor.setCacheOperationSource(cacheOperationSource);
?? ??? ?return interceptor;
?? ?}
}10、繼續(xù)看下CacheInterceptor類(重要):
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
?? ?@Override
?? ?@Nullable
?? ?public Object invoke(final MethodInvocation invocation) throws Throwable {
?? ??? ?Method method = invocation.getMethod();
?? ??? ?CacheOperationInvoker aopAllianceInvoker = () -> {
?? ??? ??? ?try {
?? ??? ??? ??? ?return invocation.proceed();
?? ??? ??? ?}
?? ??? ??? ?catch (Throwable ex) {
?? ??? ??? ??? ?throw new CacheOperationInvoker.ThrowableWrapper(ex);
?? ??? ??? ?}
?? ??? ?};
?? ??? ?Object target = invocation.getThis();
?? ??? ?Assert.state(target != null, "Target must not be null");
?? ??? ?try {
? ? ? // 緩存執(zhí)行邏輯
?? ??? ??? ?return execute(aopAllianceInvoker, target, method, invocation.getArguments());
?? ??? ?}
?? ??? ?catch (CacheOperationInvoker.ThrowableWrapper th) {
?? ??? ??? ?throw th.getOriginal();
?? ??? ?}
?? ?}
}
@Nullable
?? ?protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
?? ??? ?if (this.initialized) {
?? ??? ??? ?Class<?> targetClass = getTargetClass(target);
?? ??? ??? ?CacheOperationSource cacheOperationSource = getCacheOperationSource();
?? ??? ??? ?if (cacheOperationSource != null) {
? ? ? ? // 解析緩存相關(guān)注解,返回CacheOperation
? ? ? ? // 每個緩存注解對應(yīng)一種不同的解析處理操作
? ? ? ? // CacheEvictOperation、CachePutOperation、CacheableOperation等
?? ??? ??? ??? ?Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
?? ??? ??? ??? ?if (!CollectionUtils.isEmpty(operations)) {
? ? ? ? ? // 執(zhí)行緩存邏輯
?? ??? ??? ??? ??? ?return execute(invoker, method,
?? ??? ??? ??? ??? ??? ??? ?new CacheOperationContexts(operations, method, args, target, targetClass));
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ?}
?? ??? ?return invoker.invoke();
?? ?}
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
?? ??? ?// 解析處理@CacheEvict注解
? ? processCacheEvicts(contexts.get(CacheEvictOperation.class), true,?? ?CacheOperationExpressionEvaluator.NO_RESULT);
?? ??? ?// 解析處理@Cacheable注解
?? ??? ?Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
?? ??? ?List<CachePutRequest> cachePutRequests = new ArrayList<>();
?? ??? ?if (cacheHit == null) {
?? ??? ??? ?collectPutRequests(contexts.get(CacheableOperation.class),?? ?CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
?? ??? ?}
?? ??? ?Object cacheValue;
?? ??? ?Object returnValue;
?? ??? ?if (cacheHit != null && !hasCachePut(contexts)) {
?? ??? ??? ?// 命中緩存,則從緩存中獲取數(shù)據(jù)
?? ??? ??? ?cacheValue = cacheHit.get();
?? ??? ??? ?returnValue = wrapCacheValue(method, cacheValue);
?? ??? ?} else {
?? ??? ??? ?// 未命中緩存,則通過反射執(zhí)行目標(biāo)方法
?? ??? ??? ?returnValue = invokeOperation(invoker);
?? ??? ??? ?cacheValue = unwrapReturnValue(returnValue);
?? ??? ?}
?? ??? ?// 解析處理@CachePut注解
?? ??? ?collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
?? ??? ?// 未命中緩存時,會封裝一個cachePutRequests
? ?? ?// 然后通過反射執(zhí)行目標(biāo)方法后,執(zhí)行該方法,最終調(diào)用EhCacheCache.put方法將數(shù)據(jù)寫入緩存中
?? ??? ?for (CachePutRequest cachePutRequest : cachePutRequests) {
?? ??? ??? ?cachePutRequest.apply(cacheValue);
?? ??? ?}
?? ??? ?// 解析處理@CacheEvict注解,和上面的方法相同,只不過第二個參數(shù)不同
?? ??? ?processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
?? ??? ?return returnValue;
?? ?}11、接著看下findCachedItem方法
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
?? ??? ?Object result = CacheOperationExpressionEvaluator.NO_RESULT;
?? ??? ?for (CacheOperationContext context : contexts) {
?? ??? ??? ?if (isConditionPassing(context, result)) {
? ? ? ? // 生成key策略:解析@Cacheable注解中的key屬性
? ? ? ? // 若未配置則默認(rèn)使用SimpleKeyGenerator#generateKey方法生成key
?? ??? ??? ??? ?Object key = generateKey(context, result);
?? ??? ??? ??? ?Cache.ValueWrapper cached = findInCaches(context, key);
?? ??? ??? ??? ?if (cached != null) {
?? ??? ??? ??? ??? ?return cached;
?? ??? ??? ??? ?}?? ?else {
?? ??? ??? ??? ??? ?if (logger.isTraceEnabled()) {
?? ??? ??? ??? ??? ??? ?logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
?? ??? ??? ??? ??? ?}
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ?}
?? ??? ?return null;
?? ?}
// SimpleKeyGenerator#generateKey
public static Object generateKey(Object... params) {
? ?? ?// 方法沒有參數(shù),則返回空的SimpleKey
?? ??? ?if (params.length == 0) {
?? ??? ??? ?return SimpleKey.EMPTY;
?? ??? ?}
? ?? ?// 方法參數(shù)只有一個,則返回該參數(shù)
?? ??? ?if (params.length == 1) {
?? ??? ??? ?Object param = params[0];
?? ??? ??? ?if (param != null && !param.getClass().isArray()) {
?? ??? ??? ??? ?return param;
?? ??? ??? ?}
?? ??? ?}
? ?? ?// 否則將方法參數(shù)進(jìn)行封裝,返回SimpleKey
?? ??? ?return new SimpleKey(params);
?? ?}
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
?? ??? ?for (Cache cache : context.getCaches()) {
? ? ? // 從一級緩存中獲取數(shù)據(jù)
?? ??? ??? ?Cache.ValueWrapper wrapper = doGet(cache, key);
?? ??? ??? ?if (wrapper != null) {
?? ??? ??? ??? ?if (logger.isTraceEnabled()) {
?? ??? ??? ??? ??? ?logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
?? ??? ??? ??? ?}
?? ??? ??? ??? ?return wrapper;
?? ??? ??? ?}
?? ??? ?}
?? ??? ?return null;
?? ?}
protected Cache.ValueWrapper doGet(Cache cache, Object key) {
?? ??? ?try {
? ? ? // 這里我們使用的是:EhCacheCache,所以最終會調(diào)用EhCacheCache.get方法獲取緩存中的數(shù)據(jù)
?? ??? ??? ?return cache.get(key);
?? ??? ?}
?? ??? ?catch (RuntimeException ex) {
?? ??? ??? ?getErrorHandler().handleCacheGetError(ex, cache, key);
?? ??? ??? ?return null;
?? ??? ?}
?? ?}三、總結(jié)
@EnableCaching和@Transactional等實(shí)現(xiàn)邏輯大體相同,看的多了,則一通百通。
到此這篇關(guān)于springboot整合ehcache和redis實(shí)現(xiàn)多級緩存實(shí)戰(zhàn)案例的文章就介紹到這了,更多相關(guān)springboot多級緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 中的 @PutMapping 注解原理及使用小結(jié)
在本文中,我們介紹了 Spring Boot 中的 @PutMapping 注解,它可以將 HTTP PUT 請求映射到指定的處理方法上,我們還介紹了 @PutMapping 注解的原理以及如何在 Spring Boot 中使用它,感興趣的朋友跟隨小編一起看看吧2023-12-12
Java類中this關(guān)鍵字與static關(guān)鍵字的用法解析
這篇文章主要介紹了Java類中this關(guān)鍵字與static關(guān)鍵字的用法解析,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09
SpringMvc響應(yīng)數(shù)據(jù)及結(jié)果視圖實(shí)現(xiàn)代碼
這篇文章主要介紹了SpringMvc響應(yīng)數(shù)據(jù)及結(jié)果視圖實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-08-08
基于SpringBoot + Redis實(shí)現(xiàn)密碼暴力破解防護(hù)
在現(xiàn)代應(yīng)用程序中,保護(hù)用戶密碼的安全性是至關(guān)重要的,密碼暴力破解是指通過嘗試多個密碼組合來非法獲取用戶賬戶的密碼,為了保護(hù)用戶密碼不被暴力破解,我們可以使用Spring Boot和Redis來實(shí)現(xiàn)一些防護(hù)措施,本文將介紹如何利用這些技術(shù)來防止密碼暴力破解攻擊2023-06-06
Struts2數(shù)據(jù)輸入驗(yàn)證教程詳解
這篇文章主要介紹了Struts2數(shù)據(jù)輸入驗(yàn)證教程詳解的相關(guān)資料,輸入數(shù)據(jù)驗(yàn)證的方法有兩種,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2016-10-10

