一个优雅的流式记录组件

1,109 阅读5分钟

1.背景

在如今的项目中我们可能会遇到很多型型色色的日志记录或方法调用记录等等,在这种环境下我 们如何高效的实现类似的功能并且还要保证和业务代码的耦合度更低以及有更好的扩展性便成为了一个比较有趣的话题,在这种背景下stream-record(点击前往)就应运而生了

2. 框架的设计

stream-record.png

3. 几个核心接口的定义

3.1 记录注解的定义

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Record {


   /**
    * 记录表达式
    * 当 strategy 为 ProcessorStrategy.EXPRESSION_NAME策略时 value 为 表达式
    * 当 strategy 为 ProcessorStrategy.ROUTE 策略时 value 为路由的目标方法
    * @return
    */
   String value();


   /**
    * 是否异步处理
    * @return
    */
   boolean isAsync() default true;


   /**
    * 处理策略
    * @see  ProcessorStrategy
    * @see  Processor
    * @return
    */
   String strategy() default ProcessorStrategy.EXPRESSION_NAME;

   /**
    * 记录生成器
    * @return
    */
   Class<? extends RecordProducer> producerClass() default Void.class;


   /**
    * 管道
    * @return
    */
   Class<? extends Pipeline> pipelineClass() default Void.class;


   /**
    * 拦截方式
    * @return
    */
   Advice advice() default Advice.AFTER;

   /**
    * 空的实现
    */
   class Void implements Pipeline, RecordProducer {


      @Override
      public RecordInfoWrapper doProduce(CurrentContext currentContext) {
         return null;
      }

      @Override
      public void doConsume(RecordInfoWrapper recordInfoWrapper) {

      }
   }

}

主要去标识一个方法的记录动作。

3.2 记录定义

接口定义

public interface RecordDefinition {


   Class<?> getTargetClass();

   /**
    * 被记录的方法
    * @return
    */
   Method getMethod();


   /**
    * 记录模版
    * 当 {@link this#getProcessorStrategy()}为{@link ProcessorStrategy#EXPRESSION_NAME}时 则代表的是el表达式模版
    * 当 {@link this#getProcessorStrategy()}为{@link ProcessorStrategy#ROUTE_NAME}时 则代表路由的目标方法名称
    * @return
    */
   String getTemple();


   /**
    * 是否异步处理
    * @return
    */
   Boolean isAsync();


   /**
    * 处理策略
    * @see  ProcessorStrategy
    * @see  Processor
    * @return
    */
    String getProcessorStrategy();


   /**
    * 记录生成器
    * @return
    */
   Class<? extends RecordProducer> getRecordProducerClass();


   /**
    * 管道参数
    * @return
    */
   Class<? extends Pipeline> getPipelineClass();

   /**
    * 获取定义名称
    * @return
    */
   String getName();


   /**
    * 获取参数列表元数据
    * @return
    */
   List<ParamNode>[] getParams();


   /**
    * 获取记录执行通知
    * @return
    */
   Advice getAdvice();
}

主要用于表示一个记录的元信息,可以理解为被@Record标记的方法所有相关的元数据 如:方法对象,记录模版 ,是否异步,通知类型等等...可以和spring bean definition类比理解

3.3 后置处理定义

public interface RecordPostProcessor {

   /**
    * 前置处理
    * @param currentContext
    */
   void preProcessor(CurrentContext currentContext);

   /**
    * 后置处理
    * @param currentContext
    */
   void postProcessor(CurrentContext currentContext);
}

主要用于记录执行的前后处理钩子函数,这是一个非常有用的功能 如:当是异步进行日志处理的时候,我们需要将请求线程的部分参数同步到子线程中,我们就可以通过CurrentContext#getAttributeAccess()#setAttribute(Strring)方式进行父子线程的参数传递

3.4 模版解析器

@FunctionalInterface
public interface TemplateResolve {

   String doResolve(CurrentContext currentContext);
}

主要用于@Record#value的解析,比如:我需要根据记录模版以spel表达式的形式去生产记录内容更或者我需要单独路由到另一个方法去进行记录内容的生成。这都是一个很有效方式

3.5 组件工厂

public interface ComponentFactory {

   /**
    * 创建组件
    * @param clazz
    * @return
    */
   Object createComponent(Class clazz);
}

主要用于Pipeline,RecordProducer等组件的创建,在spring场景下它是一个很好的和spring 容器集成的粘合剂

3.6 记录生产者

/**
* 记录生成器
* @author : KangNing Hu
*/
@FunctionalInterface
public interface RecordProducer {


   RecordInfoWrapper doProduce(CurrentContext currentContext);


}

主要用于记录实体的创建和组装,如果是自定义实体,这里提供了一个接口进行自定义实体实现,接口定义如下:

public interface RecordInfo extends Serializable {

   // 记录信息属性名称
   String MASSAGE_ATTR = "massage";


   /**
    * 获取唯一标标识别
    * @return
    */
   Object getId();

   /**
    * 设置唯一标识
    * @param id
    */
   void setId(Object id);

   /**
    * 获取操作时间 ,记录的生成时间
    * @return
    */
   Long getOperationTime();

   /**
    * 设置操作时间 ,记录的生成时间
    * @param time
    */
   void setOperationTime(Long time);

   /**
    * 获取记录消息
    * @return
    */
   String getMessage();

   /**
    * 设置记录信息
    * @param message
    */
   void setMessage(String message);
}

3.7 管道

@FunctionalInterface
public interface Pipeline {

   void doConsume(RecordInfoWrapper recordInfoWrapper);
}

主要用于集中消费指定的记录实体(如:将其持久或者推送消息队列),如果每个记录定义没有显示指定管道记录定义产生的记录实体都将由全局的管道处理,同理上述日志生产者也一样

4 记录定义注册器定义与注册

注册器定义

/**
 * record 定义注册器
 * @see  RecordDefinition
 * @see DefaultRecordDefinitionRegistry
 * @author hukangning
 */
public interface RecordDefinitionRegistry {

   /**
    * 注册record定义
    * @param recordDefinition record 定义
    */
   void register(RecordDefinition recordDefinition);


   /**
    * 返回所有record定义
    * @return 返回当前注册表中的所有record定义
    */
   List<RecordDefinition> getRecordDefinitions();


   /**
    * 返回指定record定义
    * @param method 被记录的方法
    * @return 被记录方法的record定义
    */
   RecordDefinition getRecordDefinition(Class clazz , Method method );

}

主要负责记录定义的注册和获取

注册实现

/**
 * 日志定义自动注册器
 *
 * @author : KangNing Hu
 */
public class RecordDefinitionAutoRegistry extends RecordPointcutAdvisor implements InstantiationAwareBeanPostProcessor ,
          Ordered {

   private Logger logger = LoggerFactory.getLogger(RecordDefinitionAutoRegistry.class.getName());



   @Override
   public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
      Class<?> beanClass = bean.getClass();
      if (AopUtils.isAopProxy(bean)){
          beanClass = AopUtils.getTargetClass(bean);
      }
      //判断是否为record definition class
      if (isNotRecordDefinitionClass(beanClass)){
         return true;
      }
      Class targetClass = beanClass;
      //注册record definition
      do {
         final Class finalTargetClass = targetClass;
         ReflectionUtils.doWithMethods(beanClass, method -> {
            if (method.isAnnotationPresent(Record.class)) {
               RecordDefinition recordDefinition = buildRecordDefinition(finalTargetClass, method);
               logger.info("注册记录定义 -> {}", recordDefinition.toString());
               this.register(recordDefinition);
            }
         });
         //遍历父类方法
         targetClass = beanClass.getSuperclass();
      } while (targetClass != null  && targetClass != Object.class);
      return true;
   }






   /**
    * 构建record 定义
    *
    * @param beanClass
    * @param method
    * @return
    */
   private RecordDefinition buildRecordDefinition(Class<?> beanClass, Method method) {
      RecordDefinitionBuilder recordDefinitionBuilder = new RecordDefinitionBuilder(method, beanClass);
      return recordDefinitionBuilder.getGenericRecordDefinition();
   }


   /**
    * 校验是否为record definition的class
    * @param beanClass
    * @return
    */
   private boolean isNotRecordDefinitionClass(Class<?> beanClass) {
      return !beanClass.isAnnotationPresent(RecordService.class);
   }




   @Override
   public int getOrder() {
      return Ordered.LOWEST_PRECEDENCE;
   }
}

基于spring bean的生命周期在每个bean的实例化之后阶段对bean的class对象进行定义扫描和注册

5 spring aop在其中扮演的角色

public class RecordPointcutAdvisor  extends StaticMethodMatcherPointcutAdvisor implements ComponentRegistry {

   private RecordContext recordContext;


   public RecordPointcutAdvisor(){
      this.recordContext = new RecordContext();
   }

   // 返回通知实现
   @Override
   public Advice getAdvice() {
      return recordContext;
   }
     ...
 
     // 方法拦截匹配条件 
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
       return method.isAnnotationPresent(Record.class);
    }

}

声明一个通知者,一个基于方法匹配的通知者,它返回一个通知实现供spring aop的通知链调用 ,通知的实现如下:

public class RecordContext implements ComponentRegistry, MethodInterceptor, ComponentFactory {

    ...
   
   // spring aop通知链会调用此方法来进行方法前后的处理,
   // 这里 也是记录组件处理的核心所在
   @Override
   public Object invoke(MethodInvocation invocation) throws Throwable {
      //校验上下文状态
      checkState();
      InterceptMethodWrapper interceptMethodWrapper = new InterceptMethodWrapper(invocation);
      //创建当前上下文
      CurrentContext currentContext = createCurrentContext(interceptMethodWrapper, new AttributeAccess());
      //解析recordDefinition
      currentContext.parseRecordDefinition(this);
      //解析参数
      paramParse.parse(currentContext);

      //前置处理
      applyPreProcessors(currentContext);
      //执行结果
      Object object = processorStrategyProxy.process(currentContext);
      //后置处理
      applyPostProcessors(currentContext);
      return object;
   }
}

记录组件的核心处理部分,整个组件内部调度都是这里进行处理的

6. 整篇文章只是大致讲述了一个组件的诞生背景和设计思路,具体的细节代码可以前往gitee进行查看

7. 欢迎大家指正和交流