Aspect-Oriented Programming(AOP) 面向導向程式


只看標題應該不知道這在寫什麼,簡單說就是有特定前提下不影響原先撰寫的邏輯在加入其他程式。

我簡單分為

  1. 不用定義@
  2. 要定義@

不用定義@ (原有的@)

目的:JPA要在每次資料新增/更新時需要顯示紀錄
不影響原來使用JPA 在CU時維持原來的結果但會顯示log

先來定義哪些@是我們要注意的

//  for save
    @Pointcut(value = "execution(* org.springframework.data.jpa.repository.JpaRepository+.save(..))))")
    public void saveMethodMatch() {
    }

    @Pointcut(value = "execution(* org.springframework.data.jpa.repository.JpaRepository+.saveAndFlush(..))))")
    public void saveAndFlushMethodMatch() {
    }

    @Pointcut(value = "execution(* org.springframework.data.jpa.repository.JpaRepository+.saveAll(..))))")
    public void saveAllMethodMatch() {
    }

    @Pointcut(value = "execution(* org.springframework.data.jpa.repository.JpaRepository+.saveAllAndFlush(..))))")
    public void saveAllAndFlushMethodMatch() {
    }

當注意到後我們要做什麼事情(前/中/後)

 @Before("saveMethodMatch() ||" +
            "saveAndFlushMethodMatch() ||" +
            "saveAllMethodMatch() ||" +
            "saveAllAndFlushMethodMatch()")
    public void doBeforeSave(JoinPoint joinPoint) {
        Object obj = joinPoint.getArgs()[0];
        String instanceStr = joinPoint.getArgs()[0].getClass().getSimpleName();
        if (obj instanceof List<?>) {
            List<?> list = (List<?>) obj;
            instanceStr = instanceStr + StringUtils.SPACE + list.get(0).getClass().getSimpleName();
        }
        logger.info(("-----" + this.getSignatureMethodName(joinPoint) + " " + instanceStr + "開始執行..."));
    }

@Around("saveMethodMatch() ||" +
            "saveAndFlushMethodMatch() ||" +
            "saveAllMethodMatch() ||" +
            "saveAllAndFlushMethodMatch()")
    public Object onAroundSave(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] params = new Object[1];
        long start = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String repositoryName = joinPoint.getSignature().getDeclaringType().getSimpleName();
        Object[] args = joinPoint.getArgs(); // change the args if you want to

        if (args[0] instanceof List) {
            params[0] = this.transformArgs(args);
        } else {
            params = (Object[]) this.transformArgs(args);
        }

        Object retVal = joinPoint.proceed(params); // run the actual method (or don't)
        long executionTime = System.currentTimeMillis() - start;
        logger.info(String.join(" ", repositoryName + ":" + method + " executed in " + executionTime + "ms"));
        return retVal;
    }

@Around("method1() || method2()") 可以放多個被注意的@要行什麼行為
transformArgs自己寫的轉換參數就不特別寫了

@AfterReturning("saveMethodMatch() ||" +
            "saveAndFlushMethodMatch() ||" +
            "saveAllMethodMatch() ||" +
            "saveAllAndFlushMethodMatch()")
    public void doAfterSave(JoinPoint joinPoint) {
        logger.info("-----" + this.getSignatureMethodName(joinPoint) + "Save執行完畢!");
    }

以上就可以達到當有人呼叫JPA 相關SAVE 都會顯示相關紀錄並不影響或未來異動程式

定義@

目的:需要重新刷新資料庫物件時 repository.flush 使用後無效可以使用entityManger去刷新

@名稱與參數

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FlushAfterOp {

// 無須參數則空白
}

定義注意到後要做什麼


@Component
@Aspect
public class FlushAspect {

    /*
    如果用H2 則不適用
    多DB資料庫來源就需指定哪一個entityManager
    為了方便說明我這邊就直接注入不另外抽成Service亦不加入 交易事務
    */
    @Autowired
    private EntityManager entityManager;


    @Pointcut("@annotation(FlushAfterQuery)")
    public void flushAfterQuery() {
    }

    @Transactional
    @Around("flushAfterQuery()")
    public Object onAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();
        Object waitForProceeding = result;
        // 若使用JPA提供的Naming query 回傳Optional<?> 或自行使用該型別
        // entityManager.refresh 是不接受此型別因此要特別處理
        Optional<?> resultFromOpt;
        if (result instanceof Optional) {
            resultFromOpt = ((Optional<?>) result);
            waitForProceeding = resultFromOpt.isPresent()?resultFromOpt.get() : null;
        }
        if (waitForProceeding instanceof List) {
            refreshList(waitForProceeding);
        } else {
            entityManager.refresh(waitForProceeding);
        }
        return result;
    }

    private Object refreshList(Object objList) {
        List<?> list = (List) objList;
            // 這邊使用遍歷方式
        for (Object record : list) {
            entityService.refresh(record);
        }
        return list;
    }


}

以上為此兩種AOP 方式介紹

未來有機會在和大家說明他的特性

這不是一個給新手的一個教學過程,也寫的不是很完整
希望大家多多包涵囉~

主要是給自己的一個紀錄,也分享給有需要的夥伴

這是一個心血來潮,產生的文章

若有喜歡或交流的部分都歡迎在下方留言,多多關照。

#aop #java







你可能感興趣的文章

Day03 : HTML 混合 JS-JSX

Day03 : HTML 混合 JS-JSX

使用vue-google-maps套件實作地圖(一)

使用vue-google-maps套件實作地圖(一)

我要成為前端工程師的學習筆記:HTML & CSS 篇 - CSS Reset、display Day5

我要成為前端工程師的學習筆記:HTML & CSS 篇 - CSS Reset、display Day5






留言討論