文章目录
起因
系统中工作流使用线程执行,该线程较长,需要分步骤记录日志。由于需要将每次执行工作流的每个步骤展示给用户,所以使用数据库记录日志。而在线程类中将日志入库会造成多段重复代码。
aop记录日志优势:
- 扩展性好:无论是另起一个线程执行记录日志操作还是想要在新的方法上记录该方法的日志操作都较为简单
- 代码耦合度低:aop负责记录日志,主业务类处理自己的业务
- 优化前代码:
- 优化后代码:
问题提出和解决
new出的线程aop失效
要想实现在一个线程中执行aop,你会遇到的第一个问题就是:new出的线程进入不了aop,这是由于aop属于spring。在spring创建bean时会对原类实施增强(增强的功能中包含aop)。可普通的单例bean对象创建的线程池又会担心,在向线程中赋值的时候导致之前已经提交的线程private属性发生变化。
new对象不会被spring管理 使用不了aop,bean对象是个单例,那怎么办呢?我这里采用的方式是使用原型bean,每一次请求时创建一个新的线程对象,放入线程池中执行。
传统方式创建线程
/**
* 利用@Bean默认单例,获取单例Pool
* @return
*/
@Bean
private ExecutorService workflowThreadPool() {
return new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
new ThreadFactoryBuilder().build(),
new ThreadPoolExecutor.AbortPolicy());
}
@Override
public Result<String> execute(ConfigExecuteDTO dto) throws Exception {
WorkflowThread workflowThread = new WorkflowThread(dto);
ExecutorService executorService = workflowThreadPool(); // 单例线程池
executorService.execute(workflowThread);
return Result.success("工作流已启动");
}
创建原型bean线程
serviceImpl.java
/**
* 使用@Lookup创建原型bean,以使用aop和@Resource
* @return
*/
@Lookup
public WorkflowThread getWorkflowThread() {
return null;
}
@Override
public Result<String> execute(ConfigExecuteDTO dto) throws Exception {
// 获取并执行线程
WorkflowThread THREAD = getWorkflowThread();
synchronized (this) {
workflowThread1.setExecuteDto(dto);
ExecutorService EXECUTOR = workflowThreadPool(); // 单例线程池
EXECUTOR.submit(workflowThread1);
}
return Result.success("工作流已启动");
}
WorkflowThread.java
/***
* 工作流线程
* ConfigurableBeanFactory.SCOPE_PROTOTYPE --- 原型bean(使用原型模式,以单例bean为原型创建目标对象)
* ScopedProxyMode.TARGET_CLASS --- 用cglib创建原型目标对象
* cglib创建原型目标对象的方式是使用扩展子类的方式 并把方法中的this编译成super以获取到原对象
* 除了cglib,还有ScopedProxyMode.INTERFACES 是使用jdk使用反射的方式创建目标对象
*
* @Package com.***.server.common.thread
* @author NNWanzi
* @date 2022/12/19 10:49
**/
@Slf4j
@NoArgsConstructor
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class WorkflowThread implements Runnable {
/**
* 执行工作流需要的参数
*/
private ConfigExecuteDTO executeDto;
@Override
public void run(){
// 业务流程(目前只关注创建原型bean)
}
}
原型bean set值无效
如果你像这样使用原型bean,往cglib创建的原型bean中setExecuteDto,将会出现很神奇的事:成功set值后该属性还是为null,通过断点可以看到,在set方法中值是正确传入了,但下一步get值就会发现得到的是null
通过进一步了解cglib工作原理,可以知道:在编译时会输出两个class文件,其中含$符号的class文件就是cglib通过子类继承WorkflowThread创建的(其他帖子上说可以在这个class文件中看到cglib编译后的this变为super的代码,但我没看到),这样就有个说法是我们set的对象是传入到子类对象,而get是获取的父类单例bean对象的属性,当然就是null。
但这种说法很快被攻破了,因为我在set属性时将this返回,再使用返回的this调用get方法,结果任然为null。
再进一步,经过朋友的提醒输出地址值判断是否调用的是同一个对象,我在service层查看了两次bean对象的地址值,set/线程run方法分别执行一次输出地址值,再设立对照组查看普通对象dto的地址值和set方法中的地址值,结果非常的amazing
啊
可以看到,在service层输出的两次地址值是1011250393,而两个执行set方法和run方法中地址值都不一致 并且也和service层的地址值不一致,这说明每次调用workflowThread对象中的方法后执行方法的对象都不一样。并且我通过多次进入service层,发现每次workflowThread对象的地址值都是1011250393。
这说明我们在获取原型bean对象的时候步骤是这样的:
- 项目启动时,创建单例bean
- 调用方法或者使用@Resource时,注入单例bean
- 调用单例bean中的方法时,使用原型模式创建一个目标对象
- 使用目标对象执行方法
执行set() 和 get() 的对象都是使用原型模式新创建的对象,也就能解释为什么会输出null了。那么知道原因了,该怎么解决呢?其实在单例bean对象中可以找到即将创建的目标对象
获取原型bean目标对象Util
package com.***.common.utils;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import java.lang.reflect.Field;
/***
* 动态代理工具类, 原型bean使用原型模式利用单例bean创建新对象,区分cglib和jdk方式创建新对象
* jdk方式通过反射的方式创建新对象
* cglib通过创建子类的方式创建新对象(与aop相关)
* 配置原型bean:
* @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
*
* @Package com.***.common.utils
* @author NNWanzi
* @date 2023/2/6 11:00
**/
public class DynamicProxyUtil {
/**
* 获取 目标对象(spring原型bean根据单例对象创建的新对象)
* @param proxy 代理对象
* @return
* @throws Exception
*/
public static Object getTarget(Object proxy) throws Exception {
if(!AopUtils.isAopProxy(proxy)) {
return proxy;//不是代理对象
}
if(AopUtils.isJdkDynamicProxy(proxy)) {
// jdk
return getJdkDynamicProxyTargetObject(proxy);
} else { //cglib
return getCglibProxyTargetObject(proxy);
}
}
/**
* 获取cglib方式创建的原型bean目标对象
* @param proxy spring单例bean对象
* @return
* @throws Exception
*/
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
return target;
}
/**
* 获取jdk方式创建的原型bean目标对象
* @param proxy spring单例bean对象
* @return
* @throws Exception
*/
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
return target;
}
}
service层中调用Util
@Override
public Result<String> execute(ConfigExecuteDTO dto) throws Exception {
// 获取并执行线程
WorkflowThread THREAD = getWorkflowThread();
WorkflowThread workflowThread = (WorkflowThread) DynamicProxyUtil.getTarget(THREAD);
synchronized (this) {
workflowThread.setExecuteDto(dto);
ExecutorService EXECUTOR = workflowThreadPool(); // 单例线程池
EXECUTOR.submit(workflowThread);
}
return Result.success("工作流已启动");
}
再次查看 bean对象的地址值 和 执行方法的对象的地址值,可以看到使用了util获取到目标对象后,执行目标方法的对象,地址值是一样的了,并且在两次进入service执行run方法的线程对象地址值不一致,完美。
原型bean中aop失效
当进行到这一步,线程就已经由spring创建好了,并且成功的执行,线程中的 @Resource 也可以成功注入bean,但你会发现workflowThread类中的aop任然失效。
失效的Thread
public class WorkflowThread implements Runnable {
/**
* 执行工作流需要的参数
*/
private ConfigExecuteDTO executeDto;
@Override
public void run(){
this.executeRecover(executeDto.getId());
}
@WorkflowLog(WorkflowProcessEnum.RECOVER)
public void executeRecover(Long workflowId) {
// 线程具体工程代码,和无法进入切面无关
}
}
自定义注解
/***
* 更新工作流状态,记录日志
*
* @Package com.***.server.aop
* @author NNWanZi
* @date 2023/1/31 11:00
**/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WorkflowLog {
/**
* 步骤
*/
WorkflowProcessEnum value() default WorkflowProcessEnum.RECOVER;
}
切面类
/**
* 工作流每个步骤切面处理日志和状态
*
* @Package com.***.server.aop
* @author NNWanzi
* @date 2023/1/31 13:10
**/
@Aspect
@Component
@Slf4j
public class WorkflowLogAop {
/**
* 记录日志标签(累积日志string)
*/
@Pointcut("@annotation(WorkflowLogFlag)")
public void flagPointcut() {}
/**
* 前置
* @return
*/
@Before("flagPointcut()")
public void flagBefore(JoinPoint point) {
// aop记录日志相关代码,和是否进入切面无关
}
}
那么,我们是spring通过cglib创建的原型bean,可为什么还是进入不了切面呢?经过详细了解切面后,可以知道:切面指的是两个类之间的分界线。所以controller层调用service层的方法时是会进入切面的,但如果是service层的A方法调用B方法是不会进入切面的。
了解了原因之后,问题该怎么解决呢?我们是在线程里面,如果把执行方法放到另外一个类B中去由spring导入则又需要创建一次原型bean了,不然担心多线程卡在类B中。但原型bean已经很耗资源了,再创建一次将会更耗资源。
所以进一步了解后,可以找到spring有这么一个方法:AopContext.currentProxy(); 他的意思是返回当前aop代理对象,把上方run方法中调用的this改为AopContext.currentProxy();返回的当前代理对象,就可以再一次进入切面。虽然使用@EnableAspectJAutoProxy将代理作为ThreadLocal公开一样也相对昂贵,但也是更好的一种方法了。 注意:AopContext.currentProxy();直接调用的方法应为public方法,private会导致进入方法后所有成员变量变为null的情况
解决调用本类方法进入不了切面
// 注意:该注解表示可以将代理作为ThreadLocal公开,开启后才能使AopContext正常工作
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
public class WorkflowThread implements Runnable {
/**
* 执行工作流需要的参数
*/
private ConfigExecuteDTO executeDto;
/**
* 切面是指"调用其他类时两个类之间的分界线",所以调用本类方法时切面不会生效,需要用该对象
*/
private WorkflowThread workflowThread;
@Override
public void run(){
workflowThread = (WorkflowThread) AopContext.currentProxy();
workflowThread.executeRecover(executeDto.getId());
}
@WorkflowLog(WorkflowProcessEnum.RECOVER)
public void executeRecover(Long workflowId) {
// 线程具体工程代码,和无法进入切面无关
}
}
切面中如何记录日志
进行到这一步,我们已经完成了在线程中使用aop。那么接下来可以全身心的关注日志记录流程了。
-
如何在多线程中保证每个线程的日志相互独立呢?
每个线程一个独立的文件,直接记录到库都是消费很高的做法,那么如何记录到内存中?我们可以采用 ThreadLocal,他其中的原理是在底层创建一个 map<Thread, obj>,线程每次调用时都可以拿到自己的对象,但需要注意ThreadLocal并不会随着线程的结束而清理保存在map中的对象,需要手动清理 -
在切面中如何获取线程类中的参数呢?
在切面中往数据库中记录日志,必然需要一些如:id、name之类的参数,这些参数在切面类中都不存在,需要怎么获取到正确的线程中的参数呢?可以通过JoinPoint.getThis(); 调用到当前正在执行的对象。 -
切面中记录日志只能在进入方法/方法完成/方法异常时进入切面,无法获取到方法内部手写的日志string和入库所需的方法内部的变量,该如何获取?
手写的日志如ssh的返回值,我们需要把它记录到日志中,这种情况可以将日志return,在切面后置增强的代码中获取返回值记录到日志。另外一些自定义变量需要记录到日志可以通过携带参数的自定义异常,抛出到异常增强中获取变量。
完整的切面代码
package com.***.server.aop;
import com.hs.tdss.common.constant.WorkflowLogStateEnum;
import com.hs.tdss.common.constant.WorkflowStateEnum;
import com.hs.tdss.common.constant.WorkflowProcessEnum;
import com.hs.tdss.common.exception.WorkflowStaticException;
import com.hs.tdss.common.utils.DateUtil;
import com.hs.tdss.server.common.thread.WorkflowThread;
import com.hs.tdss.server.system.dto.HsCom;
import com.hs.tdss.server.workflow.dto.ConfigExecuteDTO;
import com.hs.tdss.server.workflow.entity.LogTaskDetailEntity;
import com.hs.tdss.server.workflow.entity.WorkflowStateEntity;
import com.hs.tdss.server.workflow.service.LogTaskDetailService;
import com.hs.tdss.server.workflow.service.WorkflowConfigService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 工作流每个步骤切面处理日志和状态
*
* @Package com.***.server.aop
* @author NNWanzi
* @date 2023/1/31 13:10
**/
@Aspect
@Component
@Slf4j
public class WorkflowLogAop {
@Resource
private LogTaskDetailService logTaskDetailServiceImpl;
@Resource
private WorkflowConfigService workflowConfigServiceImpl;
private static ThreadLocal<StringBuffer> stringBuffer = new ThreadLocal<>();
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 日志记录(日志对象数据记录、入库)
*/
@Pointcut("@annotation(com.***.server.aop.WorkflowLog)")
public void pointcut() {}
/**
* 记录日志标签(累积日志string)
*/
@Pointcut("@annotation(com.***.server.aop.WorkflowLogFlag)")
public void flagPointcut() {}
/**
* 前置记录日志
* @return
*/
@Before("pointcut()")
public void before(JoinPoint point) throws NoSuchFieldException, IllegalAccessException {
// 获得注解,进而获取注解上的参数
WorkflowLog workflowLog = getAnnotationLog(point);
if (workflowLog == null) {
return;
}
WorkflowProcessEnum process = workflowLog.value();
// 调用正在执行的对象,从对象中获取传入线程的参数
WorkflowThread aThis = (WorkflowThread) point.getThis();
HsCom hsCom = aThis.getHsCom();
ConfigExecuteDTO executeDto = aThis.getExecuteDto();
Long batchId = aThis.getBatchId();
//更新状态
WorkflowStateEntity state = new WorkflowStateEntity(executeDto.getId(),hsCom.getId());
state.setState(WorkflowStateEnum.UNDERWAY, process);
state.setUpdateTime(new Date());
workflowConfigServiceImpl.updateState(state);
//工作流阶段节点日志记录
LogTaskDetailEntity logTaskDetailEntity = new LogTaskDetailEntity(
process.getValue(),
WorkflowLogStateEnum.UNDERWAY.getStatic(),
batchId);
long begin = System.currentTimeMillis();
try {
logTaskDetailEntity.setBeginTime(dateFormat.parse(dateFormat.format(begin)));
} catch (ParseException e) {
e.printStackTrace();
}
// 清空日志str,准备记录下一个步骤的日志
int length = WorkflowLogAop.stringBuffer.get().length();
WorkflowLogAop.stringBuffer.get().delete(0,length);
logTaskDetailServiceImpl.updateByBatchIdAndTaskId(logTaskDetailEntity);
}
/**
* 后置记录日志
* @return
*/
@AfterReturning(value = "pointcut()")
public void afterReturning(JoinPoint point) {
// 获得注解,进而获取注解上的参数
WorkflowLog workflowLog = getAnnotationLog(point);
if (workflowLog == null) {
return;
}
WorkflowProcessEnum process = workflowLog.value();
// 调用正在执行的对象,从对象中获取传入线程的参数
WorkflowThread aThis = (WorkflowThread) point.getThis();
HsCom hsCom = aThis.getHsCom();
ConfigExecuteDTO executeDto = aThis.getExecuteDto();
Long batchId = aThis.getBatchId();
//更新状态,3正常结束
WorkflowStateEntity state = new WorkflowStateEntity(executeDto.getId(),hsCom.getId());
state.setState(WorkflowStateEnum.FINISH, process);
state.setUpdateTime(new Date());
workflowConfigServiceImpl.updateState(state);
//日志记录
LogTaskDetailEntity logTaskDetailEntity = new LogTaskDetailEntity(
process.getValue(),
WorkflowLogStateEnum.SUCCEED.getStatic(),
batchId);
long end1 = System.currentTimeMillis();
try {
logTaskDetailEntity.setEndTime(dateFormat.parse(dateFormat.format(end1)));
} catch (ParseException e) {
WorkflowLogAop.stringBuffer.get().append(DateUtil.nowDateTime() "\t" process.getDesc() "结束时日志时间获取失败 " e " <br/>");
e.printStackTrace();
}
//log_detail字段,日志加载日志到字段
WorkflowLogAop.stringBuffer.get();
logTaskDetailEntity.setLogDetail(WorkflowLogAop.stringBuffer.get().toString());
logTaskDetailServiceImpl.updateByBatchIdAndTaskId(logTaskDetailEntity);
}
/**
* 异常时记录日志
* @return
*/
@AfterThrowing(value = "pointcut()",throwing = "throwable")
public void afterThrowing(JoinPoint point, Throwable throwable) throws Exception {
// 获得注解,进而获取注解上的参数
WorkflowLog workflowLog = getAnnotationLog(point);
if (workflowLog == null) {
return;
}
WorkflowProcessEnum process = workflowLog.value();
// 调用正在执行的对象,从对象中获取传入线程的参数
WorkflowThread aThis = (WorkflowThread) point.getThis();
HsCom hsCom = aThis.getHsCom();
ConfigExecuteDTO executeDto = aThis.getExecuteDto();
Long batchId = aThis.getBatchId();
// 如果是需要修改线程状态的自定义异常,则修改为对应的状态
WorkflowStateEnum workflowState = WorkflowStateEnum.ERROR;
WorkflowLogStateEnum workflowLogState = WorkflowLogStateEnum.ERROR;
if (null != throwable && throwable instanceof WorkflowStaticException) {
WorkflowStaticException t = (WorkflowStaticException)throwable;
if (null != t.getWorkflowStateEnum()) workflowState = t.getWorkflowStateEnum();
if (null != t.getWorkflowLogStateEnum()) workflowLogState = t.getWorkflowLogStateEnum();
}
// 更新状态
WorkflowStateEntity state = new WorkflowStateEntity(executeDto.getId(),hsCom.getId());
state.setState(WorkflowStateEnum.UNDERWAY, process);
state.setUpdateTime(new Date());
workflowConfigServiceImpl.updateState(state);
// 更新日志
LogTaskDetailEntity logTaskDetailEntity = new LogTaskDetailEntity(
process.getValue(),
workflowLogState.getStatic(),
batchId);
long end1 = System.currentTimeMillis();
try {
logTaskDetailEntity.setEndTime(dateFormat.parse(dateFormat.format(end1)));
} catch (ParseException e) {
WorkflowLogAop.stringBuffer.get().append(DateUtil.nowDateTime() "\t" process.getDesc() "结束时日志时间获取失败 " e " <br/>");
e.printStackTrace();
}
// log_detail字段,调取日志加载日志到字段
logTaskDetailEntity.setLogDetail(WorkflowLogAop.stringBuffer.get().toString());
logTaskDetailServiceImpl.updateByBatchIdAndTaskId(logTaskDetailEntity);
throw new Exception(throwable);
}
/**
* 是否存在注解,如果存在就获取
*/
private WorkflowLog getAnnotationLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
WorkflowLog annotation = method.getAnnotation(WorkflowLog.class);
return method.getAnnotation(WorkflowLog.class);
}
return null;
}
/**
* 前置
* @return
*/
@Before("flagPointcut()")
public void flagBefore(JoinPoint point) {
// 获得注解
WorkflowLogFlag annotationLog = getFlagAnnotationLog(point);
if (annotationLog == null) {
return;
}
String value = annotationLog.value();
WorkflowLogAop.stringBuffer.get().append(DateUtil.nowDateTime() "\t 开始执行 " value " <br/>");
}
/**
* 后置
* @return
*/
@AfterReturning(value = "flagPointcut()", returning = "res")
public void flagAfterReturning(JoinPoint point, Object res) {
// 获得注解
WorkflowLogFlag annotationLog = getFlagAnnotationLog(point);
if (annotationLog == null) {
return;
}
String value = annotationLog.value();
WorkflowLogAop.stringBuffer.get().append(DateUtil.nowDateTime() "\t 执行完成 " value " <br/>");
// 如果返回值是个string,则向日志中添加这些字符
if (null != res && res instanceof String) {
WorkflowLogAop.stringBuffer.get().append(DateUtil.nowDateTime() "\t " res " <br/>");
}
}
/**
* 异常时
* @return
*/
@AfterThrowing(value = "flagPointcut()",throwing = "t")
public void flagAfterThrowing(JoinPoint point, Throwable t) throws Exception {
// 获得注解
WorkflowLogFlag annotationLog = getFlagAnnotationLog(point);
if (annotationLog == null) {
return;
}
String value = annotationLog.value();
WorkflowLogAop.stringBuffer.get().append(DateUtil.nowDateTime() "\t 执行异常 " value " <br/>" t);
throw new Exception(t);
}
/**
* 初始化日志缓存string
*/
public static void initLogString() {
stringBuffer.set(new StringBuffer());
}
/**
* 避免内存泄露,在线程执行结束后需要调用remove
*/
public static void removeLogString() {
stringBuffer.remove();
}
/**
* 是否存在注解,如果存在就获取
*/
private WorkflowLogFlag getFlagAnnotationLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
WorkflowLogFlag annotation = method.getAnnotation(WorkflowLogFlag.class);
return method.getAnnotation(WorkflowLogFlag.class);
}
return null;
}
}
携带变量的自定义异常
package com.hs.tdss.common.exception;
import com.hs.tdss.common.constant.WorkflowLogStateEnum;
import com.hs.tdss.common.constant.WorkflowStateEnum;
import lombok.Data;
/***
* 携带线程状态信息的异常
*
* @Package com.***.common.exception
* @author NNWanzi
* @date 2023/2/7 14:39
**/
@Data
public class WorkflowStaticException extends Exception{
private WorkflowLogStateEnum workflowLogStateEnum;
private WorkflowStateEnum workflowStateEnum;
public WorkflowStaticException() {
super();
}
public WorkflowStaticException(String message) {
super(message);
}
public WorkflowStaticException(String message, WorkflowLogStateEnum workflowLogStateEnum) {
super(message);
this.workflowLogStateEnum = workflowLogStateEnum;
}
public WorkflowStaticException(String message, WorkflowLogStateEnum workflowLogStateEnum, WorkflowStateEnum workflowStateEnum) {
super(message);
this.workflowLogStateEnum = workflowLogStateEnum;
this.workflowStateEnum = workflowStateEnum;
}
}
线程中抛出的自定义异常的代码块
@WorkflowLogFlag("第二步骤,XXXXX.....")
@WorkflowLog(WorkflowProcessEnum.MASK)
public void executeMask(Long workflowId) throws Exception {
//查询数据,判断审核状态
MetaDataInfoEntity metaDataInfoEntity = metaDataInfoMapper.selectById(workflowId);
if (metaDataInfoEntity == null){
throw new WorkflowStaticException("数据未配置", WorkflowLogStateEnum.LINE_UP, WorkflowStateEnum.STOP);
}else if (!"1".equals(metaDataInfoEntity.getAuditState())){
throw new WorkflowStaticException("数据未审核", WorkflowLogStateEnum.LINE_UP, WorkflowStateEnum.STOP);
}
}
切面类中对两个注解设置两个切点 @WorkflowLog @WorkflowLogFlag 分别表示日志入库和记录日志到stringBuffer,这样可以让记录和入库更加灵活。
总结
- 需要把对象交给spring管理才可以让对象中的aop生效
- 原型bean是在单例bean的基础上每次调用单例bean中的方法时使用原型设计模式创建新的对象
- 原型bean可以使用两种方式创建:jdk方式通过反射创建原型Bean,cglib通过创建子类继承
- 切面是指两个类之间的分界面,所以如果A方法调用同一个类中的B方法时是不会进入aop的
- 要求多线程之间数据独立 可以使用 ThreadLocal,他底层维护了一个map<Thread, obj>