Redis結(jié)合AOP與自定義注解實(shí)現(xiàn)分布式緩存流程詳解
1、背景
項(xiàng)目中如果查詢數(shù)據(jù)是直接到MySQL數(shù)據(jù)庫中查詢的話,會(huì)查磁盤走IO,效率會(huì)比較低,所以現(xiàn)在一般項(xiàng)目中都會(huì)使用緩存,目的就是提高查詢數(shù)據(jù)的速度,將數(shù)據(jù)存入緩存中,也就是內(nèi)存中,這樣查詢效率大大提高
分布式緩存方案

優(yōu)點(diǎn):
- 使用Redis作為共享緩存 ,解決緩存不同步問題
- Redis是獨(dú)立的服務(wù),緩存不用占應(yīng)用本身的內(nèi)存空間
什么樣的數(shù)據(jù)適合放到緩存中呢?
同時(shí)滿足下面兩個(gè)條件的數(shù)據(jù)就適合放緩存:
- 經(jīng)常要查詢的數(shù)據(jù)
- 不經(jīng)常改變的數(shù)據(jù)
接下來我們使用 AOP技術(shù) 來實(shí)現(xiàn)分布式緩存,這樣做的好處是避免重復(fù)代碼,極大減少了工作量
2、目標(biāo)
我們希望分布式緩存能幫我們達(dá)到這樣的目標(biāo):
- 對(duì)業(yè)務(wù)代碼無侵入(或侵入性較小)
- 使用起來非常方便,最好是打一個(gè)注解就可以了,可插拔式的
- 對(duì)性能影響盡可能的小
- 要便于后期維護(hù)
3、方案
此處我們選擇的方案就是:AOP+自定義注解+Redis
- 自定義一個(gè)注解,需要做緩存的接口打上這個(gè)注解即可
- 使用Spring AOP的環(huán)繞通知增強(qiáng)被自定義注解修飾的方法,把緩存的存儲(chǔ)和刪除都放這里統(tǒng)一處理
- 那么需要用到分布式鎖的接口,只需要打一個(gè)注解即可,這樣才夠靈活優(yōu)雅
4、實(shí)戰(zhàn)編碼
4.1、環(huán)境準(zhǔn)備
首先我們需要一個(gè)簡(jiǎn)單的SpringBoot項(xiàng)目環(huán)境,這里我寫了一個(gè)基礎(chǔ)Demo版本,地址如下:
https://gitee.com/colinWu_java/spring-boot-base.git
大家可以先下載下來,本文就是基于這份主干代碼進(jìn)行修改的
4.2、pom依賴
pom.xml中需要新增以下依賴:
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.5.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.1</version>
</dependency>4.3、自定義注解
添加緩存的注解
package org.wujiangbo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @desc 自定義注解:向緩存中添加數(shù)據(jù)
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCache {
String cacheNames() default "";
String key() default "";
//緩存時(shí)間(單位:秒,默認(rèn)是無限期)
int time() default -1;
}刪除緩存注解:
package org.wujiangbo.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @desc 自定義注解:從緩存中刪除數(shù)據(jù)
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCacheEvict {
String cacheNames() default "";
String key() default "";
}4.4、切面處理類
下面兩個(gè)切面類實(shí)際上是可以寫在一個(gè)類中的,但是為了方便理解和觀看,我分開寫了
package org.wujiangbo.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.wujiangbo.annotation.MyCache;
import org.wujiangbo.service.RedisService;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* @desc 切面類,處理分布式緩存添加功能
*/
@Aspect
@Component
@Slf4j
public class MyCacheAop {
@Resource
private RedisService redisService;
/**
* 定義切點(diǎn)
*/
@Pointcut("@annotation(myCache)")
public void pointCut(MyCache myCache){
}
/**
* 環(huán)繞通知
*/
@Around("pointCut(myCache)")
public Object around(ProceedingJoinPoint joinPoint, MyCache myCache) {
String cacheNames = myCache.cacheNames();
String key = myCache.key();
int time = myCache.time();
/**
* 思路:
* 1、拼裝redis中存緩存的key值
* 2、看redis中是否存在該key
* 3、如果存在,直接取出來返回即可,不需要執(zhí)行目標(biāo)方法了
* 4、如果不存在,就執(zhí)行目標(biāo)方法,然后將緩存放一份到redis中
*/
String redisKey = new StringBuilder(cacheNames).append(":").append(key).toString();
String methodPath = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
Object result ;
if (redisService.exists(redisKey)){
log.info("訪問接口:[{}],直接從緩存獲取數(shù)據(jù)", methodPath);
return redisService.getCacheObject(redisKey);
}
try {
//執(zhí)行接口
result = joinPoint.proceed();
//接口返回結(jié)果存Redis
redisService.setCacheObject(redisKey, result, time, TimeUnit.SECONDS);
log.info("訪問接口:[{}],返回值存入緩存成功", methodPath);
} catch (Throwable e) {
log.error("發(fā)生異常:{}", e);
throw new RuntimeException(e);
}
return result;
}
}
還有一個(gè):
package org.wujiangbo.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.wujiangbo.annotation.MyCacheEvict;
import org.wujiangbo.service.RedisService;
import javax.annotation.Resource;
/**
* @desc 切面類,處理分布式緩存刪除功能
*/
@Aspect
@Component
@Slf4j
public class MyCacheEvictAop {
@Resource
private RedisService redisService;
/**
* 定義切點(diǎn)
*/
@Pointcut("@annotation(myCache)")
public void pointCut(MyCacheEvict myCache){
}
/**
* 環(huán)繞通知
*/
@Around("pointCut(myCache)")
public Object around(ProceedingJoinPoint joinPoint, MyCacheEvict myCache) {
String cacheNames = myCache.cacheNames();
String key = myCache.key();
/**
* 思路:
* 1、拼裝redis中存緩存的key值
* 2、刪除緩存
* 3、執(zhí)行目標(biāo)接口業(yè)務(wù)代碼
* 4、再刪除緩存
*/
String redisKey = new StringBuilder(cacheNames).append(":").append(key).toString();
String methodPath = joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
Object result ;
//刪除緩存
redisService.deleteObject(redisKey);
try {
//執(zhí)行接口
result = joinPoint.proceed();
//刪除緩存
redisService.deleteObject(redisKey);
log.info("訪問接口:[{}],緩存刪除成功", methodPath);
} catch (Throwable e) {
log.error("發(fā)生異常:{}", e);
throw new RuntimeException(e);
}
return result;
}
}4.5、工具類
Redis的工具類:
package org.wujiangbo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @desc Redis工具類
*/
@Component //交給Spring來管理 的自定義組件
public class RedisService {
@Autowired
public RedisTemplate redisTemplate;
/**
* 查看key是否存在
*/
public boolean exists(String key)
{
return redisTemplate.hasKey(key);
}
/**
* 清空Redis所有緩存數(shù)據(jù)
*/
public void clearAllRedisData()
{
Set<String> keys = redisTemplate.keys("*");
redisTemplate.delete(keys);
}
/**
* 緩存基本的對(duì)象,Integer、String、實(shí)體類等
*
* @param key 緩存的鍵值
* @param value 緩存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 緩存基本的對(duì)象,Integer、String、實(shí)體類等
*
* @param key 緩存的鍵值
* @param value 緩存的值
* @param timeout 時(shí)間
* @param timeUnit 時(shí)間顆粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
if(timeout == -1){
//永久有效
redisTemplate.opsForValue().set(key, value);
}
else{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
}
/**
* 設(shè)置有效時(shí)間
*
* @param key Redis鍵
* @param timeout 超時(shí)時(shí)間
* @return true=設(shè)置成功;false=設(shè)置失敗
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 設(shè)置有效時(shí)間
*
* @param key Redis鍵
* @param timeout 超時(shí)時(shí)間
* @param unit 時(shí)間單位
* @return true=設(shè)置成功;false=設(shè)置失敗
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 獲得緩存的基本對(duì)象。
*
* @param key 緩存鍵值
* @return 緩存鍵值對(duì)應(yīng)的數(shù)據(jù)
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 刪除單個(gè)對(duì)象
*
* @param key
*/
public boolean deleteObject(final String key)
{
if(exists(key)){
redisTemplate.delete(key);
}
return true;
}
/**
* 刪除集合對(duì)象
*
* @param collection 多個(gè)對(duì)象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 緩存List數(shù)據(jù)
*
* @param key 緩存的鍵值
* @param dataList 待緩存的List數(shù)據(jù)
* @return 緩存的對(duì)象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 獲得緩存的list對(duì)象
*
* @param key 緩存的鍵值
* @return 緩存鍵值對(duì)應(yīng)的數(shù)據(jù)
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 緩存Set
*
* @param key 緩存鍵值
* @param dataSet 緩存的數(shù)據(jù)
* @return 緩存數(shù)據(jù)的對(duì)象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
/**
* 獲得緩存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 緩存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 獲得緩存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入數(shù)據(jù)
*
* @param key Redis鍵
* @param hKey Hash鍵
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 獲取Hash中的數(shù)據(jù)
*
* @param key Redis鍵
* @param hKey Hash鍵
* @return Hash中的對(duì)象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 獲取多個(gè)Hash中的數(shù)據(jù)
*
* @param key Redis鍵
* @param hKeys Hash鍵集合
* @return Hash對(duì)象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 獲得緩存的基本對(duì)象列表
*
* @param pattern 字符串前綴
* @return 對(duì)象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}4.6、配置類
package org.wujiangbo.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import javax.annotation.Resource;
/**
* @desc redis配置類
*/
@Configuration
public class RedisSerializableConfig extends CachingConfigurerSupport {
@Resource
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<Object, Object> redisTemplate()
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(mapper);
// 使用StringRedisSerializer來序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean
public DefaultRedisScript<Long> limitScript()
{
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText());
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* 限流腳本
*/
private String limitScriptText()
{
return "local key = KEYS[1]\n" +
"local count = tonumber(ARGV[1])\n" +
"local time = tonumber(ARGV[2])\n" +
"local current = redis.call('get', key);\n" +
"if current and tonumber(current) > count then\n" +
" return tonumber(current);\n" +
"end\n" +
"current = redis.call('incr', key)\n" +
"if tonumber(current) == 1 then\n" +
" redis.call('expire', key, time)\n" +
"end\n" +
"return tonumber(current);";
}
}FastJson2JsonRedisSerializer類:
package org.wujiangbo.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
/**
* @desc Redis使用FastJson序列化
*/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
@SuppressWarnings("unused")
private ObjectMapper objectMapper = new ObjectMapper();
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static
{
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
}
public FastJson2JsonRedisSerializer(Class<T> clazz)
{
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException
{
if (t == null)
{
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException
{
if (bytes == null || bytes.length <= 0)
{
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
public void setObjectMapper(ObjectMapper objectMapper)
{
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
}
protected JavaType getJavaType(Class<?> clazz)
{
return TypeFactory.defaultInstance().constructType(clazz);
}
}4.7、yml配置
server:
port: 8001
undertow:
# 設(shè)置IO線程數(shù), 它主要執(zhí)行非阻塞的任務(wù),它們會(huì)負(fù)責(zé)多個(gè)連接, 默認(rèn)設(shè)置每個(gè)CPU核心一個(gè)線程
# 不要設(shè)置過大,如果過大,啟動(dòng)項(xiàng)目會(huì)報(bào)錯(cuò):打開文件數(shù)過多(CPU有幾核,就填寫幾)
io-threads: 6
# 阻塞任務(wù)線程池, 當(dāng)執(zhí)行類似servlet請(qǐng)求阻塞IO操作, undertow會(huì)從這個(gè)線程池中取得線程
# 它的值設(shè)置取決于系統(tǒng)線程執(zhí)行任務(wù)的阻塞系數(shù),默認(rèn)值是:io-threads * 8
worker-threads: 48
# 以下的配置會(huì)影響buffer,這些buffer會(huì)用于服務(wù)器連接的IO操作,有點(diǎn)類似netty的池化內(nèi)存管理
# 每塊buffer的空間大小,越小的空間被利用越充分,不要設(shè)置太大,以免影響其他應(yīng)用,合適即可
buffer-size: 1024
# 每個(gè)區(qū)分配的buffer數(shù)量 , 所以pool的大小是buffer-size * buffers-per-region
buffers-per-region: 1024
# 是否分配的直接內(nèi)存(NIO直接分配的堆外內(nèi)存)
direct-buffers: true
spring:
#配置數(shù)據(jù)庫鏈接信息
datasource:
url: jdbc:mysql://127.0.0.1:3306/test1?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&rewriteBatchedStatements=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
application:
name: springboot #服務(wù)名
#redis配置
redis:
# 數(shù)據(jù)庫索引
database: 0
# 地址
host: 127.0.0.1
# 端口,默認(rèn)為6379
port: 6379
# 密碼
password: 123456
# 連接超時(shí)時(shí)間
timeout: 10000#MyBatis-Plus相關(guān)配置
mybatis-plus:
#指定Mapper.xml路徑,如果與Mapper路徑相同的話,可省略
mapper-locations: classpath:org/wujiangbo/mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true #開啟駝峰大小寫自動(dòng)轉(zhuǎn)換
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #開啟控制臺(tái)sql輸出
4.8、使用
Controller中寫兩個(gè)接口分別測(cè)試一下緩存的新增和刪除
package org.wujiangbo.controller;
import lombok.extern.slf4j.Slf4j;
import org.wujiangbo.annotation.CheckPermission;
import org.wujiangbo.annotation.MyCache;
import org.wujiangbo.annotation.MyCacheEvict;
import org.wujiangbo.result.JSONResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @desc 測(cè)試接口類
*/
@RestController
@Slf4j
public class TestController {
//測(cè)試刪除緩存
@GetMapping("/deleteCache")
@MyCacheEvict(cacheNames = "cacheTest", key = "userData")
public JSONResult deleteCache(){
System.out.println("deleteCache success");
return JSONResult.success("deleteCache success");
}
//測(cè)試新增緩存
@GetMapping("/addCache")
@MyCache(cacheNames = "cacheTest", key = "userData")
public JSONResult addCache(){
System.out.println("addCache success");
return JSONResult.success("addCache success");
}
}4.9、測(cè)試
瀏覽器先訪問:http://localhost:8001/addCache
然后再通過工具查看Redis中是不是添加了緩存數(shù)據(jù),正確情況應(yīng)該是緩存添加進(jìn)去了
然后再訪問:http://localhost:8001/deleteCache
再通過工具查看Redis,緩存應(yīng)該是被刪除了,沒有了
到此完全符合預(yù)期,測(cè)試成功
總結(jié)
本文主要是介紹了分布式緩存利用AOP+注解的方式處理,方便使用和擴(kuò)展希望對(duì)大家有所幫助
最后本案例代碼已全部提交到gitee中了,地址如下:
https://gitee.com/colinWu_java/spring-boot-base.git
本文新增的代碼在【RedisDistributedCache】分支中
到此這篇關(guān)于Redis結(jié)合AOP與自定義注解實(shí)現(xiàn)分布式緩存流程詳解的文章就介紹到這了,更多相關(guān)Redis分布式緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot隨機(jī)端口啟動(dòng)的實(shí)現(xiàn)
本文主要介紹了SpringBoot隨機(jī)端口啟動(dòng)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
Java的NIO之并發(fā)環(huán)境下非阻塞IO技術(shù)詳解
這篇文章主要介紹了Java的NIO之并發(fā)環(huán)境下非阻塞IO技術(shù)詳解,Java NIO(New IO)是Java平臺(tái)提供的一種用于高效處理I/O操作的API,它引入了一組新的類和概念,以提供更好的性能和可擴(kuò)展性,需要的朋友可以參考下2023-09-09
SpringIOC refresh()初始化代碼實(shí)例
這篇文章主要介紹了SpringIOC refresh()初始化代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
淺談java實(shí)現(xiàn)redis的發(fā)布訂閱(簡(jiǎn)單易懂)
本篇文章主要介紹了淺談java實(shí)現(xiàn) redis的發(fā)布訂閱(簡(jiǎn)單易懂),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03
Spring MVC Controller返回值及異常的統(tǒng)一處理方法
這篇文章主要給大家介紹了關(guān)于Spring MVC Controller返回值及異常的統(tǒng)一處理方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Spring MVC具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
利用Spring Cloud Zuul實(shí)現(xiàn)動(dòng)態(tài)路由示例代碼
Spring Cloud Zuul路由是微服務(wù)架構(gòu)的不可或缺的一部分,提供動(dòng)態(tài)路由,監(jiān)控,彈性,安全等的邊緣服務(wù)。下面這篇文章主要給大家介紹了關(guān)于利用Spring Cloud Zuul實(shí)現(xiàn)動(dòng)態(tài)路由的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。2017-09-09

