业务场景:开发一个计算接口,接口需要从FTP中下载报表文件进行解析,返回计算数据,同时生成检测报告上传至指定ftp。
此业务中包含以下几个动作:
1.登录FTP下载文件到本地;
2.解析文件;
3.生成自检报告并上传到指定ftp,并以邮件形式通知客户;
4.重置服务本地文件夹信息,防止数据混乱.
5.返回解析数据结果;
最初版本是线性同步作业:1—>2—>3—>4—>5,正常情况下除了接口响应速度慢,功能基本没有问题,但是在邮件发送或重置文件夹信息抛出异常时,接口无法正常返回数据;
版本2,将自检报告上传、邮件发送、重置文件夹信息提交到线程池,进行异步执行,提高了接口响应速度,解决了邮件发送或重置文件夹信息抛出异常时,接口无法正常返回数据的问题。但是实际生产中出现了因文件格式异常产生计算错误,导致服务抛出异常,第4步重置文件夹动作未执行,后续再次请求时数据混乱的问题;
版本3:使用自定义注解,配合AOP,在接口执行后拦截(后置通知增强),当方法执行完后(无论是否抛出异常都会执行),发布自定义计算完成事件,在监听器中执行重置本地文件夹方法,这样解耦更彻底,即使计算方法出错,文件夹相关信息也会被清除,不会造成下一次计算数据混乱。
1.自定义注解
package com.weige.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) // 表示该注解只能用来修饰在方法上
public @interface initFileInfo {
Class businessType() default Object.class;//类类型属性
String value() default "重置文件信息";
}
2.切面配置
package com.weige.aspect;
import com.weige.config.SBLCompleteEvent;
import com.weige.vo.SBLRequestParam;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author
* @version 1.0
* @description: 日志切面
* @date 2021/9/27 15:52
*/
@Aspect
@Component
public class InitFileInfoAspect {
Logger logger = LoggerFactory.getLogger(InitFileInfoAspect.class);
@Autowired
ApplicationContext applicationContext;
//定义切点
@Pointcut("execution(public * com.weige.controller.*.*(..))")
public void webLog(){}
@Before("webLog()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
}
//后置异常通知
@AfterThrowing("webLog()")
public void throwss(JoinPoint jp){
// System.out.println("方法异常时执行啦,.....");
}
//这里使用后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
@After("webLog()")
public void after(JoinPoint jp){
Object obj=jp.getArgs()[0];
if(obj instanceof SBLRequestParam){
SBLRequestParam sblRequestParam=(SBLRequestParam)obj;
String lotid=sblRequestParam.getLotId();
//事件发布(如果不配置异步支持,流程会阻塞到这里,
//监听事件完成后流程才会往下走)
applicationContext.publishEvent(new SBLCompleteEvent(this, lotid));
}
}
}
3.自定义事件
package com.weige.config;
import org.springframework.context.ApplicationEvent;
/**
* @author
* @version 1.0
* @description: 自定义事件:SBL计算完成
* @date 2021/12/2 13:51
*/
public class SBLCompleteEvent extends ApplicationEvent {
private String lotId;
public String getLotId() {
return lotId;
}
public void setLotId(String lotId) {
this.lotId = lotId;
}
public SBLCompleteEvent(Object source,String lotId) {
super(source);
this.setLotId(lotId);
}
}
4.自定义监听器,监听指定事件
package com.weige.config;
import com.weige.service.business.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
/**
* @author
* @version 1.0
* @description: 自定义事件监听器
* @date 2021/7/28 15:16
*/
@Component
@Async
public class SblCompleteListener {
@Value("${localDirPath}")
private String localDirPath;
@Autowired
FileService fileService;
/**
* 注册监听实现方法
* 只需要让监听类被Spring管理即可,@EventListener注解会根据方法内配置的事件完成监听。
* 接下来可启动项目来测试事件发布时是否被监听者所感知
*
* @param
*/
@EventListener
@Order(1)
public void initDir(SBLCompleteEvent sblCompleteEvent) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
System.out.println("我监听到SBLP计算完了,准备删除对应文件:"+sblCompleteEvent.getLotId());
// 获取注册用户对象
String lotId = sblCompleteEvent.getLotId();
//执行删除动作
fileService.deleteFile(new File(localDirPath),lotId);
System.out.println("终于删除成功了。。。。");
}
}
以上是工作过程中处理问题的一些思路,特此记录。