面向切面編程-編寫一個計算方法加載時間的攔截器

什麼是切面(AOP)?

切面的概念

AOP:面向切面編程,相當於OOP面向對象編程。SpringAOP的存在目的就是解耦,AOP可以讓一組類共享相同的行爲。(出自《Spring Boot實戰》)
AOP爲開發人員提供了一種描寫敘述橫切關注點的機制,並可以自己主動將橫切關注點織入到面向對象的軟件系統中。從而實現了橫切關注點的模塊化。
AOP可以將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任。比如事務處理、日誌管理、權限控制等。封裝起來,便於降低系統的反覆代碼,降低模塊間的耦合度,並有利於未來的可操作性和可維護性。

切面的名詞解析

切面(Aspect):由切點和增強組成,既包含了橫切邏輯的定義。也包含了連接點的定義。

建言(Advice):是切面的詳細實現。
以目標方法爲參照點,依據放置的地方不同,可分爲前置通知(Before)、後置返回通知(AfterReturning)、後置異常通知(AfterThrowing)、後置終於通知(After)與圍繞通知(Around)5種。在實際應用中一般是切面類中的一個方法。詳細屬於哪類通知。相同是在配置中指定的。

連接點(Joinpoint):程序運行的某個特定的位置。比方類初始化前,初始化後。方法調用前。方法調用後等等

切入點(Pointcut):用於定義通知應該切入到哪些連接點上。

不同的通知通常須要切入到不同的連接點上,這樣的精準的匹配是由切入點的正則表達式來定義的。

目標對象(Target):增強邏輯的織入目標類。

代理對象(Proxy):將通知應用到目標對象之後被動態創建的對象。

能夠簡單地理解爲,代理對象的功能等於目標對象的核心業務邏輯功能加上共同擁有功能。代理對象對於使用者而言是透明的。是程序執行過程中的產物。

織入(Weaving):將切面應用到目標對象從而創建一個新的代理對象的過程。這個過程能夠發生在編譯期、類裝載期及執行期,當然不同的發生點有着不同的前提條件。
譬如發生在編譯期的話。就要求有一個支持這樣的AOP實現的特殊編譯器;發生在類裝載期,就要求有一個支持AOP實現的特殊類裝載器;僅僅有發生在執行期,則可直接通過Java語言的反射機制與動態代理機制來動態實現。
增強:織入到目標類連接點上的一段代碼

切面的應用場景

此處鏈接: 面向切面編程(AOP)的理解,此博主說明簡潔,生動,便於理解。

代碼實戰

此處實現方法計算執行時間,打印日誌的功能。可能大家會疑惑,我在方法的起止處加上兩行System.currentTimeMillis()就行了,爲何要這麼麻煩?嗯,從單個方法來看是這樣的,然而,每個方法都加上去不就顯得很lower,而且無法保證統一。那聰明的同學可能會提個工具類調用,然而你的方法是不是還得調用。現實開發中我們寫業務代碼也沒見過每個方法寫個調用,日誌自動就輸出時間了,參數等東西的,那是如何做到的?
創建攔截規則

/**
 * 攔截器順序。
 * 
 * @author 芬達
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface InterceptorOrder {
    /**
     * 值越低優先級越高。
     * 
     * @return
     */
    int order() default LOWEST_ORDER;

    int LOWEST_ORDER = Integer.MAX_VALUE;
    int HIGHTEST_ORDER = Integer.MAX_VALUE;

}

構造攔截器

/**
 * 攔截器實體
 * 
 * @author 芬達
 *
 */
public abstract class InspireInterceptor {
    @Getter
    @Setter
    private int order;

    /**
     * 執行完目標方法之前攔截。
     */
    public boolean executrBefore(InspireContext context) {
        return true;
    }

    /**
     * 執行完目標方法之後進行攔截。
     */
    public void executeAfter(InspireContext context) {
    }
}

構造請求的上下文

/**
 * 攔截器上下文類。
 * 
 * @author 芬達
 *
 */
@Data
public class InspireContext {

    private InspireRequest request;

    private InspireResponse response;
}

構造請求參數

/**
 * 請求參數。
 * 
 * @author 芬達
 *
 */
@Data
public class InspireRequest {
    /**
     * 方法名
     */
    private String methodName;

    private Map<String, Object> paramMap;
}

目標方法的執行結果

/**
 * 目標方法的執行結果。
 * 
 * @author 芬達
 *
 * @param <T>
 */
@Data
public class InspireResponse<T> {
    /**
     * 目標方法的執行結果
     */
    private T data;
}

此處使用責任鏈模式作爲攔截器管理,目的是方便之後添加多個攔截器。

/**
 * 攔截器管理。
 * 
 * @author 芬達
 *
 */
@Component
public class InterceptorChainClient {

    @Autowired
    private List<InspireInterceptor> interceptorList;

    @PostConstruct
    public void loadInterceptors() {
        if (!CollectionUtils.isEmpty(interceptorList)) {
            interceptorList.forEach(interceptor -> {
                interceptor.setOrder(resolveOreder(interceptor));
            });
            Collections.sort(interceptorList, (o1, o2) -> o1.getOrder() - o2.getOrder());

        }

    }

    /**
     * 獲取攔截器優先級
     * 
     * @param interceptor
     * @return
     */
    private int resolveOreder(InspireInterceptor interceptor) {
        if (!interceptor.getClass().isAnnotationPresent(InterceptorOrder.class)) {
            return InterceptorOrder.LOWEST_ORDER;
        } else {
            return interceptor.getClass().getAnnotation(InterceptorOrder.class).order();
        }
    }

    public boolean processBefore(InspireContext context) {
        for (InspireInterceptor interceptor : interceptorList) {
            boolean isPass = interceptor.executrBefore(context);
            if (!isPass) {
                return false;
            }
        }
        return true;

    }

    public void processAfter(InspireContext context) {
        for (InspireInterceptor interceptor : interceptorList) {
            interceptor.executeAfter(context);
        }

    }
}

方法調用時間的攔截器

/**
 * 方法調用的時間
 * 
 * @author 芬達
 *
 */
@Slf4j
@Component
@InterceptorOrder(order = 20)
public class CostTimeInterceptor extends InspireInterceptor {

    /**
     * 開始時間。
     */
    private Long start;
    /**
     * 結束時間。
     */
    private Long end;

    @Override
    public boolean executrBefore(InspireContext context) {
        start = System.currentTimeMillis();
        return super.executrBefore(context);
    }

    @Override
    public void executeAfter(InspireContext context) {
        end = System.currentTimeMillis();
        long costTime = end - start;
        log.info("invoke method:{},costTime:{}", context.getRequest().getMethodName(), costTime);
    }
}

AOP攔截


@Slf4j
@Aspect
@Component
public class MethodAspect {

    @Autowired
    private InterceptorChainClient interceptorChainClient;

    @Pointcut("execution(* com.example.demo.rpc.service.*.*(..))")
    private void doLogPointcut() {
    };

    @Around("doLogPointcut()")
    public Object doAfterReturning(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("===============invole  aop start=====================");
        InspireContext inspireContext = buildInspireContext(joinPoint);
        // 目標方法執行之前
        interceptorChainClient.processBefore(inspireContext);

        RpcResult result = (RpcResult) joinPoint.proceed();
        inspireContext.getResponse().setData(result);
        // 目標方法執行之後
        interceptorChainClient.processAfter(inspireContext);

        System.out.println("===============invole  aop end=====================");
        return result;
    }

    private InspireContext buildInspireContext(ProceedingJoinPoint joinPoint) {
        InspireRequest inspireRequest = new InspireRequest();
        // 方法名
        inspireRequest.setMethodName(joinPoint.getSignature().getName());
        // 參數名
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] paramsNames = methodSignature.getParameterNames();
        log.info("invoke class:{}", signature.getDeclaringType());
        // 參數值
        Object[] paramsValue = joinPoint.getArgs();
        Map<String, Object> paramsMap = new HashMap<>();
        for (int i = 0; i < paramsNames.length; i++) {
            paramsMap.put(paramsNames[i], paramsValue[i]);

        }
        inspireRequest.setParamMap(paramsMap);
        InspireContext inspireContext = new InspireContext();
        inspireContext.setRequest(inspireRequest);
        inspireContext.setResponse(new InspireResponse());
        return inspireContext;
    };

}

遠程調用結果

/**
 * 統一的結果參數
 * 
 * @author 芬達
 *
 * @param <T>
 */
@Data
public class RpcResult<T> implements Serializable {

    /**
     * 序列化
     */
    private static final long serialVersionUID = 1L;
    private Integer code;
    private boolean success;
    private String msg;
    private T data;
}

測試類

@Controller
@RequestMapping("order")
public class RefundController {

    @Autowired
    private RefundService refundService;

    @RequestMapping("/refund")
    public String refund(@Param("orderId") String orderId, @Param("reason") String reason) {
        refundService.refund(orderId, reason);
        return "index";
    }
}

/**
 * 接口測試類。
 * 
 * @author 芬達
 *
 */
public interface RefundService {
    RpcResult<Boolean> refund(String orderId, String reason);
}

@Service("refundService")
public class RefundServiceImpl implements RefundService {

    @Override
    public RpcResult<Boolean> refund(String orderId, String reason) {
        if ("1001".equals(orderId)) {
            return RpcResultBuilder.buildSuccess(true);
        } else {
            return RpcResultBuilder.buildFail(false);

        }
    }

}

/**
 * 結果處理
 * 
 * @author 芬達
 *
 */
public class RpcResultBuilder {

    public static <T> RpcResult<T> buildSuccess(T data) {
        RpcResult<T> result = new RpcResult<T>();
        result.setSuccess(true);
        result.setCode(200);
        result.setMsg("success");
        result.setData(data);
        return result;
    }

    public static <T> RpcResult<T> buildFail(T data) {
        RpcResult<T> result = new RpcResult<T>();
        result.setSuccess(false);
        result.setCode(500);
        result.setMsg("fail");
        result.setData(data);
        return result;
    }
}

目錄層級
層級結構
調用接口模式
http://localhost:9002/order/refund?orderId=1001&reason=true
結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章