项目背景
旨在为校园内的学生提供一个实时交流的平台,主要有以下几项功能:联系人列表,建群,单聊群聊,成员列表等,支持图片,文字,语音,表情包等消息类型,支持回复与跳转,艾特成员。
切面是什么?
分布式注解锁
我写了个分布式锁的工具类lockService,把加锁,业务代码,解锁封装到一个executeWithLock方法中,用的时候只需要调用这个方法就行,业务代码用在supplier写就行,supplier他不传参但是返回参数.
后来做了优化,用注解来实现分布式锁,创建注解@RedissonLock,这样方便多了,只需要在方法上加注解就行,注解里面可以指定锁的key,过期时间等
实现切面RedissonLockAspect,用环绕通知方式,在方法上面加around注解,切点为这个@RedissonLock注解的地址.
获取被代理的方法对象,从方法上获取 RedissonLock 注解实例,获取锁的key,最后调用编写的lockService 的 executeWithLock方法,使用锁执行被代理的方法
@Slf4j
@Aspect
@Component
@Order(0)//确保比事务注解先执行,分布式锁在事务外
public class RedissonLockAspect {
@Autowired
private LockService lockService;
@Around("@annotation(com.abin.mallchat.common.common.annotation.RedissonLock)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
RedissonLock redissonLock = method.getAnnotation(RedissonLock.class);
String prefix = StrUtil.isBlank(redissonLock.prefixKey()) ? SpElUtils.getMethodKey(method) : redissonLock.prefixKey();//默认方法限定名+注解排名(可能多个)
String key = SpElUtils.parseSpEl(method, joinPoint.getArgs(), redissonLock.key());
return lockService.executeWithLockThrows(prefix + ":" + key, redissonLock.waitTime(), redissonLock.unit(), joinPoint::proceed);
}
}
统一管理项目的线程池
频繁的创建、销毁线程和线程池,会给系统带来额外的开销。未经池化及统一管理的线程,则会导致系统内线程数上限不可控。
因此,在项目中,应当统一管理项目的线程池,并在必要时进行线程池的扩容和缩容。
我定义了一个ThreadPoolConfig类,创建我们项目统一的线程池,并交给spring管理。一个是项目共用线程池, 一个是websocket通信线程池.
同时实现AsyncConfigurer,设置了@async注解也使用我们的统一线程池,这样方便统一管理。
我们的线程池没有用Excutors快速创建。是因为Excutors创建的线程池用的无界队列,有oom的风险
public ThreadPoolTaskExecutor mallchatExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("mallchat-executor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//满了调用线程执行,认为重要任务
executor.initialize();
return executor;
}
用的是ThreadPoolTaskExecutor,spring中的线程池,因为他能帮助我们等队列里任务执行完再停机。保证任务不丢失.
异常捕获
我们的项目的线程池,就是线程运行抛异常了,要处理.传统模式下线程,如果一个异常未被捕获,从线程中抛了出来。JVM会回调一个方法dispatchUncaughtException,这个方法打印异常信息,然后退出线程。
我们要做的就是给线程添加一个异常捕获处理器,以后抛了异常,就给它转成error日志。这样才能及时发现问题。
但是我们用的是线程池.这种就用不了,线程池是用线程工厂创建的线程,我们无法直接给线程添加异常处理器.
看了线程工厂的源码,发现线程池创建线程时,会调用线程工厂的newThread方法,这个方法可以返回一个线程,我们可以写一个线程工厂的类,继承线程工厂重写newThread这个方法,在线程创建后,给线程添加异常处理器.但是我们要写很多创建线程的逻辑
所以我们可以用装饰者模式,在线程池创建线程时,用装饰者模式包装线程工厂,调用它的线程创建后,再扩展设置我们的异常捕获
public class MyThreadFactory implements ThreadFactory {
private ThreadFactory original;
@Override
public Thread newThread(Runnable r) {
Thread thread = original.newThread(r);
thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());//异常捕获
return thread;
}
}
第二步,替换spring线程池的线程工厂。