LOADING

加载过慢请开启缓存 浏览器默认开启

聊天室项目

2025/4/20 项目
项目背景

旨在为校园内的学生提供一个实时交流的平台,主要有以下几项功能:联系人列表,建群,单聊群聊,成员列表等,支持图片,文字,语音,表情包等消息类型,支持回复与跳转,艾特成员。

切面是什么?

分布式注解锁

我写了个分布式锁的工具类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线程池的线程工厂。