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. [CSP-S模拟测试]:reverse(数位DP)

    题目描述 我们定义: $\overline{d_k...d_2d_1}=\sum \limits_{i=1}^kd_i\times {10}^{i-1}=n(d_i\in [0,9]\ and\ d_ ...

  2. 破解Revealapp的试用时间限制

    转载自:http://jingwei6.me/2014/02/28/reveal_crack.html Revealapp作为分析iOS app UI结构的利器,还是非常称手的,89刀的价格也是物有所 ...

  3. 简易的Web自动化链接测试(Xenu)

    1.理解链接需要测试的测试点: [1] 要测试的链接页面是否存在 [2] 确定存在链接页面,然后就考虑跳转后的页面是不是对应需求的页面[3] 保证Web系统上没有孤立的页面(没有链接指向该页面) 2. ...

  4. P1058立体图(面对代码解释)

    传送 样例: 输出样例: (洛谷上面的那个太丑了就不放了) 大佬博客在此 乍一看好像真的没有什么思路 所以我们结合ybr大佬的代码进行分析 疑点都将在代码下面进行分析(面对代码做题模式开始) #inc ...

  5. MIME TYPE是什么?

    首先,我们要了解浏览器是如何处理内容的.在浏览器中显示的内容有 HTML.有 XML.有 GIF.还有 Flash ……那么,浏览器是如何区分它们,决定什么内容用什么形式来显示呢?答案是 MIME T ...

  6. Delphi 文件转换Base64

    uses EncdDecd; function FileToBase64(FileName: string): string; var  MemoryStream: TMemoryStream;beg ...

  7. quick BI 修改列名备注

    有些列名太长了,所以造成些影响.注意修改即可.

  8. Vagrant 手册之网络 - 概述及基本用法

    原文地址 - 概述 原文地址 - 基本用法 为了访问创建的 Vagrant 环境,Vagrant 为端口转发.连接公共网络.创建私有网络等功能暴露了一些高层网络选项. 高层网络选项用于提供可以跨 pr ...

  9. python 按二维数组的某行或列排序 (numpy lexsort)

    lexsort支持对数组按指定行或列的顺序排序:是间接排序,lexsort不修改原数组,返回索引. (对应lexsort 一维数组的是argsort a.argsort()这么使用就可以:argsor ...

  10. mybatis配置mapper.xml 的基本操作

    XML 映射文件 本文参考mybatis中文官网进行学习总结:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html MyBatis 的真正强大在于它的 ...