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. Facebook发布Tweaks:让微调iOS应用变得更简单

    假设,你正在开发一款iOS应用. 你的iOS应用有很多动画效果,而你(或你的设计师)希望让那些动画效果的持续时间恰到好处.那华丽的抽屉特效是应该耗时半秒钟,还是四分之三秒呢? 通常情况下,开发者会对合 ...

  2. SQLite多线程下的并发操作

    标签: sqlite多线程数据库跨平台嵌入式class 2011-04-14 13:29 26939人阅读 评论(2) 收藏 举报 这两天一直在捣鼓SQLite数据库,基本的操作就不说了,比较简单,打 ...

  3. ORA-01578: ORACLE 数据块损坏 (文件号 10, 块号 57896)ORA-01110: 数据文件 10: '/data/oradata/prod35.dbf'

    https://community.oracle.com/thread/3540795 概述 ------------- 数据库坏块(corruption) 的类型可以按照坏块所属对象的不同,分为用户 ...

  4. Unity 指定参数

    构造函数参数初始化 InjectionConstructor IContainer.RegisterType<T, Class>(new InjectionConstructor(&quo ...

  5. WPF样式统一之DevExpress设置窗体,控件为Office风格

    DevExpress相信不少人用过,虽然人家不是免费的,但是用过的应该都知道,确实是花了心血的C#插件,下面来介绍下在DevExpress下如何统一设置自己的WPF程序为经典Windows风格. 窗体 ...

  6. 精讲 org.w3c.dom(java dom)解析XML文档

    org.w3c.dom(java dom)解析XML文档 位于org.w3c.dom操作XML会比较简单,就是将XML看做是一颗树,DOM就是对这颗树的一个数据结构的描述,但对大型XML文件效果可能会 ...

  7. 应用安全-Web安全-漏洞修复方案整理

    通过HTTP头部字段防御措施整理 X-Frame-Options #反劫持 X-XSS-Protection #开启浏览器防XSS功能 Set X-Frame-Options  CSP X-Conte ...

  8. jmeter对响应数据做断言

    单独校验某个接口中的某个字段时,断言就相当于检查点 添加http请求,输入路径url

  9. python3入门之基础语法

    Python 是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言.Python 的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比其他语言更有特色语法 ...

  10. Netty之大动脉Pipeline

    Pipeline 设计原理 Channel 与ChannelPipeline: 相信大家都已经知道,在Netty 中每个Channel 都有且仅有一个ChannelPipeline 与之对应,它们的组 ...