Search code examples
javamultithreadingconcurrencyaop

Spring AOP logging thread method


Is there any way to implement AOP logging to public method of class that implements Runnable and ran by ExecutorService?

Thread class

@Component
@Scope("prototype")
public class FileProcessor implements Runnable {

  private final LinkedBlockingQueue<File> filesQueue;
  private final GiftCertificateMapper certificateMapper;
  private final File errorFolder;
  private static final ReentrantLock LOCK = new ReentrantLock();

  private static final Logger LOGGER = LoggerFactory.getLogger(FileProcessor.class);

  public FileProcessor(LinkedBlockingQueue<File> filesQueue, GiftCertificateMapper certificateMapper,
      File errorFolder) {
    this.filesQueue = filesQueue;
    this.certificateMapper = certificateMapper;
    this.errorFolder = errorFolder;
  }

  @Override
  public void run() {
    File file = null;
    try {
      while ((file = filesQueue.poll(100, TimeUnit.MILLISECONDS)) != null) {
        processFile(file);
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      LOGGER.warn("File thread was interrupted");
    } catch (IOException e) {
      LOGGER.error("Error processing file {} \n{}", file.getAbsolutePath(), e);
    }
  }

  public void processFile(File file) throws IOException {
    if (file != null) {
      try {
        ObjectMapper objectMapper = new ObjectMapper();
        List<GiftCertificate> certificates = Arrays.asList(objectMapper.readValue(file, GiftCertificate[].class));
        certificateMapper.insertList(certificates);
        file.delete();
      } catch (JsonParseException | UnrecognizedPropertyException | InvalidFormatException | DataIntegrityViolationException e) {
        moveFileToErrorFolder(file);
      }
    }
  }

  private void moveFileToErrorFolder(File file) throws IOException {
    try {
      LOCK.lock();
      Files.move(Paths.get(file.getAbsolutePath()), getPathForMovingFile(file), StandardCopyOption.ATOMIC_MOVE);
    } finally {
      LOCK.unlock();
    }
  }

  private Path getPathForMovingFile(File fileForMove) {
    File fileList[] = errorFolder.listFiles();
    int filesWithSameNameCounter = 0;
    if (fileList != null && fileList.length > 0) {
      for (File file : fileList) {
        if (file.getName().contains(fileForMove.getName())) {
          filesWithSameNameCounter++;
        }
      }
    }
    return filesWithSameNameCounter > 0 ?
        Paths.get(errorFolder.getAbsolutePath(), "(" + filesWithSameNameCounter + ")" + fileForMove.getName()) :
        Paths.get(errorFolder.getAbsolutePath(), fileForMove.getName());
  }
}

Aspect

@Aspect
@Component
@ConditionalOnProperty(
    value = "file-processing.logging.enabled",
    havingValue = "true",
    matchIfMissing = true)
public class FileProcessingLoggingAspect {

  private static final Logger LOGGER = LoggerFactory.getLogger(FileProcessingLoggingAspect.class);

  @Pointcut("execution(* com.epam.esm.processor.FileProcessor.processFile(java.io.File))")
  public void processFilePointcut() {
  }

  @Around("processFilePointcut()")
  public Object logFileProcessing(ProceedingJoinPoint joinPoint) throws Throwable {
//    File file = (File) joinPoint.getArgs()[0];
//    long time = System.currentTimeMillis();
    Object object = joinPoint.proceed();
//    long resultTime = System.currentTimeMillis() - time;
    LOGGER.info("Processing of file took  milliseconds");
    return object;
  }
}

Solution

  • In Spring AOP , internal method calls cannot be intercepted.

    In the code shared , even though the method processFile() is public , it gets called from run(). This is a self reference / internal method call , which cannot be intercepted.

    Details can be read in the documentation

    Due to the proxy-based nature of Spring’s AOP framework, calls within the target object are, by definition, not intercepted. For JDK proxies, only public interface method calls on the proxy can be intercepted

    A pointcut expression to intercept all external method calls to a class implementing Runnable would be as follows

    @Around("this(java.lang.Runnable) && within(com.epam.esm.processor..*)")
    public Object logFileProcessing(ProceedingJoinPoint pjp) throws Throwable {
    
        try {
            return pjp.proceed();
        } finally {
            //log
            System.out.println("****Logged");
        }
    }
    

    Scoping designator within() limits the scope to apply the advice.

    The point cut @Pointcut("execution(* com.epam.esm.processor.FileProcessor.processFile(java.io.File))") is valid and would work if an external method call happens to it.

    Hope this helps.