Spring publish-event 机制

监听者模式包含了一个监听者 Listener 与之对应的事件 Event,还有一个事件发布者 EventPublish。
过程就是 EventPublish 发布一个事件,被监听者捕获到,然后执行事件相应的方法。 1. 发布事件 private final ApplicationEventPublisher eventPublisher; eventPublisher.publishEvent(new LogEvent(this, name, 0, ld)); 2. 事件 public class LogEvent extends ApplicationEvent { private final LogParam logParam; public LogEvent(Object source, LogParam logParam) {
//事件源(发布事件的对象)
super(source); this.logParam = logParam;
} public LogEvent(Object source, String logKey, LogType logType, String content) {
this(source, new LogParam(logKey, logType, content));
} public LogParam getLogParam() {
return logParam;
}
} 3. 监听器 @Component
public class LogEventListener { private final LogService logService; public LogEventListener(LogService logService) {
this.logService = logService;
} //将方法标记为应用程序事件侦听器
@EventListener
//异步
@Async
public void onApplicationEvent(LogEvent event) {
//转换成 Entity
Log logToCreate = event.getLogParam().convertTo();
//保存日志
logService.create(logToCreate);
}
}

缓存模块

自定义注解并使用

/**
* 缓存锁注解(在具有该注解的方法上:加锁,执行目标方法,释放锁)
*/
@Target(ElementType.METHOD) //用于描述注解的使用范围
@Retention(RetentionPolicy.RUNTIME) //用于描述注解的生命周期
@Documented //表示该注解是否可以生成到 API 文档中
@Inherited //具备继承性(A被注解了,那么继承了A的B够继承到A中的注解)
public @interface CacheLock { /** 缓存锁key前缀(默认"")*/
@AliasFor("value")
String prefix() default ""; /** 缓存锁key前缀(默认"")*/
@AliasFor("prefix")
String value() default ""; /** 过期时间 */
long expired() default 5; /** 时间单位(默认 s)*/
TimeUnit timeUnit() default TimeUnit.SECONDS; /** 分隔符(默认 :)*/
String delimiter() default ":"; /** 方法调用后是否删除缓存 */
boolean autoDelete() default true; /** 是否跟踪请求信息(将请求IP添加到缓存key上)*/
boolean traceRequest() default false;
} /**
* 缓存参数注释
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheParam {
} Controller: @PostMapping("/login")
@CacheLock(autoDelete = false) //登陆操作加锁,防止重复登陆
public AuthToken auth(@RequestBody @Valid LoginParam loginParam) {
return adminService.authenticate(loginParam);
}

用于缓存锁注解的拦截器(AOP代理模式)

@Aspect
@Configuration
public class CacheLockInterceptor { //缓存锁key前缀:cache_lock_
private final static String CACHE_LOCK_PREFOX = "cache_lock_"; //缓存锁value:locked(被锁定状态)
private final static String CACHE_LOCK_VALUE = "locked"; //缓存池
private final StringCacheStore cacheStore; public CacheLockInterceptor(StringCacheStore cacheStore) {
this.cacheStore = cacheStore;
} //具有 @annotation(run.halo.app.cache.lock.CacheLock) 注解的方法都会触发拦截器
@Around("@annotation(run.halo.app.cache.lock.CacheLock)")
public Object interceptCacheLock(ProceedingJoinPoint joinPoint) throws Throwable {
//获取方法签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); //获取注解类
CacheLock cacheLock = methodSignature.getMethod().getAnnotation(CacheLock.class); //建立缓存锁key
String cacheLockKey = buildCacheLockKey(cacheLock, joinPoint); try {
//放入缓存(key是cacheLockKey,value是locked)
//如果缓存中没有此缓存锁key才会放入(返回true),否则会放入失败(返回false)
Boolean cacheResult = cacheStore.putIfAbsent(cacheLockKey, CACHE_LOCK_VALUE, cacheLock.expired(), cacheLock.timeUnit()); if (!cacheResult) {
throw new FrequentAccessException("访问过于频繁,请稍后再试!").setErrorData(cacheLockKey);
} //处理被注解的方法,获取返回值
return joinPoint.proceed();
} finally {
if (cacheLock.autoDelete()) {
//注解标注调用方法后删除缓存
cacheStore.delete(cacheLockKey);
}
}
} /**
* 建立缓存锁key(包括:cache_lock_cacheLock.prefix/Method:arg:IP)
*/
private String buildCacheLockKey(CacheLock cacheLock,ProceedingJoinPoint joinPoint) { //获取方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); //建立缓存锁key
StringBuilder cacheKeyBuilder = new StringBuilder(CACHE_LOCK_PREFOX); //分隔符(:)
String delimiter = cacheLock.delimiter(); if (StringUtils.isNotBlank(cacheLock.prefix())) {
cacheKeyBuilder.append(cacheLock.prefix());
} else {
cacheKeyBuilder.append(methodSignature.getMethod().toString());
} //获取方法上的参数注解数组(一个方法可以有多个参数,一个参数可以有多个注解)
Annotation[][] parameterAnnotations = methodSignature.getMethod().getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
for (int j = 0; j < parameterAnnotations[i].length; j++) {
//获取注解
Annotation annotation = parameterAnnotations[i][j];
//如果被 @CacheParam 注解,则获取参数,添加到缓存锁key上
if (annotation instanceof CacheParam) {
//获取参数
Object arg = joinPoint.getArgs()[i];
//将参数添加到缓存key上
cacheKeyBuilder.append(delimiter).append(arg.toString());
}
}
} if (cacheLock.traceRequest()) {
//添加 Request 客户端 IP
cacheKeyBuilder.append(delimiter).append(ServletUtils.getRequestIp());
}
return cacheKeyBuilder.toString();
}
}

缓存包装器(将缓存数据封装成对象)

@Data
@NoArgsConstructor
@AllArgsConstructor
class CacheWrapper<V> implements Serializable { /** 缓存数据 */
private V data; /** 过期时间 */
private Date expireAt; /** 创造时间 */
private Date createAt;
}

缓存池接口

public interface CacheStore<K, V> {

    /** 通过key得到缓存 */
Optional<V> get(@NonNull K key); /** 放入一个会过期的缓存 */
void put(@NonNull K key, @NonNull V value, long timeout, @NonNull TimeUnit timeUnit); /** 缓存池中不存在key才放入 */
Boolean putIfAbsent(@NonNull K key, @NonNull V value, long timeout, @NonNull TimeUnit timeUnit); /** 放入一个不会过期的缓存 */
void put(@NonNull K key, @NonNull V value); /** 删除缓存 */
void delete(@NonNull K key);
}

缓存池抽象类

public abstract class AbstractCacheStore<K, V> implements CacheStore<K, V> {

    /** 通过key获取缓存包装器 */
abstract Optional<CacheWrapper<V>> getInternal(@NonNull K key); /** 放入缓存包装器 */
abstract void putInternal(@NonNull K key, @NonNull CacheWrapper<V> cacheWrapper); /** 如果key不存在才放入缓存包装器 */
abstract Boolean putInternalIfAbsent(@NonNull K key, @NonNull CacheWrapper<V> cacheWrapper); /** 通过key获取value */
@Override
public Optional<V> get(K key) {
return getInternal(key).map(cacheWrapper -> {
//判断是否过期(过期时间不是null,并且在当前时间之前,表示已经过期)
if (cacheWrapper.getExpireAt() != null && cacheWrapper.getExpireAt().before(run.halo.app.utils.DateUtils.now())) {
//删除缓存
delete(key);
return null;
}
return cacheWrapper.getData();
});
} /** 放入带过期时间的缓存 */
@Override
public void put(K key, V value, long timeout, TimeUnit timeUnit) {
putInternal(key, buildCacheWrapper(value, timeout, timeUnit));
} /** key不存在才放入缓存 */
@Override
public Boolean putIfAbsent(K key, V value, long timeout, TimeUnit timeUnit) {
return putInternalIfAbsent(key, buildCacheWrapper(value, timeout, timeUnit));
} /** 放入不过期的缓存 */
@Override
public void put(K key, V value) {
putInternal(key, buildCacheWrapper(value, 0, null));
} /** 构建缓存包装器 */
@NonNull
private CacheWrapper<V> buildCacheWrapper(@NonNull V value, long timeout, @Nullable TimeUnit timeUnit) {
//过期时间必须>=0
Assert.isTrue(timeout >= 0, "Cache expiration timeout must not be less than 0"); Date now = run.halo.app.utils.DateUtils.now(); Date expireAt = null; //如果设置了过期时间,则构造过期时间
if (timeout > 0 && timeUnit != null) {
expireAt = DateUtils.add(now, timeout, timeUnit);
} //构建缓存包装器
CacheWrapper<V> cacheWrapper = new CacheWrapper<>();
cacheWrapper.setCreateAt(now);
cacheWrapper.setExpireAt(expireAt);
cacheWrapper.setData(value); return cacheWrapper;
}
}

字符串缓存池抽象类(将缓存 Data 数据转换成 Json 字符串)

public abstract class StringCacheStore extends AbstractCacheStore<String, String> {

    /** 放入值 */
public <T> void putAny(String key, T value) {
try {
//JsonUtils.objectToJson(value):对象转成Json
put(key, JsonUtils.objectToJson(value));
} catch (JsonProcessingException e) {
throw new ServiceException("Failed to convert " + value + " to json", e);
}
} public <T> void putAny(@NonNull String key, @NonNull T value, long timeout, @NonNull TimeUnit timeUnit) {
try {
put(key, JsonUtils.objectToJson(value), timeout, timeUnit);
} catch (JsonProcessingException e) {
throw new ServiceException("Failed to convert " + value + " to json", e);
}
} /** 获取值 */
public <T> Optional<T> getAny(String key, Class<T> type) {
return get(key).map(value -> {
try {
//JsonUtils.jsonToObject(value, type):Json转换成对象
return JsonUtils.jsonToObject(value, type);
} catch (IOException e) {
return null;
}
});
}
}

内存缓存池(字符串缓存池)

public class InMemoryCacheStore extends StringCacheStore {

    /** 清理计划周期 */
private final static long PERIOD = 60 * 1000; //一分钟 /** 缓存容器 */
private final static ConcurrentHashMap<String, CacheWrapper<String>> CACHE_CONTAINER = new ConcurrentHashMap<>(); //定时任务
private final Timer timer; public InMemoryCacheStore() {
//定时清理缓存(1分钟)
timer = new Timer();
//开启定时任务,延迟0,周期一分钟
timer.scheduleAtFixedRate(new CacheExpiryCleaner(), 0, PERIOD);
} /** 获取值 */
@Override
Optional<CacheWrapper<String>> getInternal(String key) {
//获取value,并放入允许null的Optional对象中
return Optional.ofNullable(CACHE_CONTAINER.get(key));
} /** 放入值 */
@Override
void putInternal(String key, CacheWrapper<String> cacheWrapper) {
//返回原始值
CacheWrapper<String> oldCacheWrapper = CACHE_CONTAINER.put(key, cacheWrapper);
} /** 不存在才放入值 */
@Override
Boolean putInternalIfAbsent(String key, CacheWrapper<String> cacheWrapper) {
CacheWrapper<String> stringCacheWrapper = CACHE_CONTAINER.putIfAbsent(key, cacheWrapper);
if(stringCacheWrapper==null)return true;
return false;
} /** 删除缓存 */
@Override
public void delete(String key) {
CACHE_CONTAINER.remove(key);
} /** 清空缓存前取消所有定时任务 */
@PreDestroy //销毁Bean之前的操作
public void preDestroy() {
//取消所有定时任务
timer.cancel();
} /** 缓存超时清理任务 */
private class CacheExpiryCleaner extends TimerTask {
//get()中会判断缓存是否过期,过期会删除缓存,并返回null
@Override
public void run() {
CACHE_CONTAINER.keySet().forEach(key -> {
//类名.this表示类名所代表类的对象
if (!InMemoryCacheStore.this.get(key).isPresent()) {
log.info("删除的过期缓存key:[{}]", key);
}
});
}
}
}

Halo(六)的更多相关文章

  1. Halo 开源项目学习(六):事件监听机制

    基本介绍 Halo 项目中,当用户或博主执行某些操作时,服务器会发布相应的事件,例如博主登录管理员后台时发布 "日志记录" 事件,用户浏览文章时发布 "访问文章" ...

  2. 使用Docker快速搭建Halo个人博客到阿里云服务器上[附加主题和使用域名访问]

    一.前言 小编买了一个服务器也是一直想整个网站,一直在摸索,看了能够快速搭建博客系统的教程.总结了有以下几种方式,大家按照自己喜欢的去搭建: halo wordpress hexo vuepress ...

  3. 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文

    阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...

  4. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  5. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

  6. CRL快速开发框架系列教程六(分布式缓存解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  7. 【微信小程序开发•系列文章六】生命周期和路由

    这篇文章理论的知识比较多一些,都是个人观点,描述有失妥当的地方希望读者指出. [微信小程序开发•系列文章一]入门 [微信小程序开发•系列文章二]视图层 [微信小程序开发•系列文章三]数据层 [微信小程 ...

  8. 我的MYSQL学习心得(六) 函数

    我的MYSQL学习心得(六) 函数 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类 ...

  9. 我的MYSQL学习心得(十六) 优化

    我的MYSQL学习心得(十六) 优化 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据 ...

随机推荐

  1. rownum的用法oracle

    SELECT * FROM T WHERE ROWNUM=1 可以查询出来数据, 而SELECT * FROM T WHERE ROWNUM=2不可以查询出来数据. in the case of wh ...

  2. SSL异常javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

    jdk 7 http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html jdk 8 http: ...

  3. P1199三国游戏

    众所周知,三国题材的游戏很多,小涵遇到了其中之一 传送 这个题显然用贪心做,但是怎么贪心? 首先我们只知道计算机的策略,但我们不知道小涵的策略.所以我们要想小涵是怎么挑的. 计算机的策略是拆掉你每次选 ...

  4. 【CDN+】 CDN项目的两大核心--缓存与回源

    前言 项目中碰到CDN专用名词: 回源, 然后不知道什么意思,反过来查询了一下CDN相关的一些基本术语,特做记录 CDN基础概念 CDN (Content Delivery Network,即内容分发 ...

  5. leaflet-加载天地图-解决纬度偏移特别大

    这几天学习 leaflet 在加载天地图时将以前的接口拿来用结果偏差了特别大(差不多是 90 度),中国纬度到了 100 多,试了改变投影和 y 轴翻转的配置都不好使,最后上网搜索到了Leaflet. ...

  6. Iterator 和 ListIterator 对比

    Iterator 的方法 //是否还有下一个 boolean hasNext(); //返回下一个 E next(); //移除返回的下一个 void remove(); ListIterator 的 ...

  7. 第 13 章 python并发编程之io模型

    一.IO模型介绍 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问 ...

  8. Altium Designer chapter1总结

    第一章操作基础中有以下几点需要注意: (1)随着DSP.ARM.FPGA等高速逻辑元件的应用,PCB的信号完整性与抗干扰性能显得尤为重要. (2)Altium Designer的发展史:Protel ...

  9. maven 配置阿里云中央仓库

    一.修改maven根目录下的conf文件夹中的setting.xml文件 <mirror> <id>alimaven</id> <name>aliyun ...

  10. Day3---Python的time库的一些简单函数以及用法

    time库的一些函数 time.time () :   获取当前时间戳,即计算机内部时间值,浮点数 >>>import time >>> time.time() 1 ...