今天師兄和我說,「之葉,你設計一個方案,把目前業務方法中和業務無關的邏輯都抽離出來,讓每一個方法只關心本身的業務邏輯」。我會心一笑 👇(由於咱們早應該作這件事情了)java
以前代碼裏每一個業務方法幾乎都是長這樣:git
public class XxxServiceImpl implements XxxService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public XxxResponse<...> queryXxx(XxxRequest request) { // 記錄方法開始時間 long startTime = System.currentTimeMillis(); // 構造響應 XxxResponse<PagedData> response = new XxxResponse(); // 設置調用機器 response.setHost(ServiceUtils.getHost()); // 設置方法開始執行時間 response.setSysTime(startTime); try { // 業務邏輯代碼 ...... response.setData(pagedData); } catch(Throwable e) { // 拋出異常時候執行 logger.error(...); response.failBizInfo(ServiceBizError.UNKNOWN_ERROR); } finally { // 設置方法耗時 long costTime = System.currentTimeMillis() - startTime; response.setCostTime(costTime); // 記錄調用信息 logger.info(...); } // 返回響應 return response; } // 後面還有若干個相似的業務方法 ...... }
很容易能夠看出,記錄方法開始時間、捕獲異常並處理、打印錯誤日誌、記錄方法耗時 這些都是和業務沒有關係的,業務方法關心的,只應該是 業務邏輯代碼 纔對。一兩個方法這個樣子看起來也還好,可是目前項目裏面已經有十幾個這種樣子的方法了,並且之後還會更多,重複代碼對咱們簡直不能忍 —— 是的,我也早就看這些業務方法不順眼了,安排!github
你們都聽過 Spring 有兩大神器 —— IoC 和 AOP —— 瞭解 AOP 的人,都知道 AOP 是 Aspect Oriented Programming,即面向切面編程:經過預編譯方式(CGLib)或者運行期動態代理(JDK Proxy)來實現程序功能代理的技術。此時的狀況,就完美匹配 AOP 的應用場景。咱們能夠定義一個切點(PointCut,也叫鏈接點),而後對和 切點匹配的方法,織入(Weaving)切面(Aspect),進行加強(Advice)處理:即在方法 調用前、調用後 或者 拋出異常時,進行額外的處理。編程
爲了方便示例,首先咱們創建一個簡單的 SpringBoot 項目,並添加示例的 Service 和 Controller:緩存
加入一個 DemoService:安全
public interface DemoService { /** * 除法運算 * * @param request 除法運算請求 * @return 除法運算結果 */ DivisionResponse divide(DivisionRequest request); }
DemoService 的實現:服務器
@Service public class DemoServiceImpl implements DemoService { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public DivisionResponse divide(DivisionRequest request) { long startTime = System.currentTimeMillis(); DivisionResponse response = new DivisionResponse(); // 設置方法調用的時間 response.setSysTime(startTime); // 設置方法調用的機器 response.setHost(getHost()); // 請求參數 int dividend = request.getDividend(); int divisor = request.getDivisor(); try { // 模擬檢查業務參數 // ...檢查業務參數... TimeUnit.MILLISECONDS.sleep(300); // 模擬執行業務 int result = dividend / divisor; // 設置業務執行結果 response.setData(result); // 調用正常 response.setSuccess(true); } catch (Throwable e) { // 調用出錯 response.setSuccess(false); // 記錄執行錯誤 logger.error("DemoServiceImpl.divide 執行出錯", e); response.setPrompt(e.getMessage()); } finally { // 設置方法調用耗時 response.setCostTime(System.currentTimeMillis() - startTime); // 記錄方法調用信息 logger.info("DemoServiceImpl.divide request={}, response={}", request, response); } return response; } /** * 模擬得到服務器名稱 */ private String getHost() { return UUID.randomUUID().toString().substring(0, 8); } }
再加入一個 DemoController:app
@RestController public class DemoController { @Resource private DemoService demoService; @GetMapping("division.do") public DivisionResponse doDivision(@RequestParam int a, @RequestParam int b) { // 構建請求 DivisionRequest request = new DivisionRequest(); request.setDividend(a); request.setDivisor(b); // 執行 return demoService.divide(request); } }
啓動應用,看一下目前調用業務方法時的狀況:dom
如今的 Java Web 應用,使用註解來進行配置和作 AOP 已是主流 —— 由於相比 XML,註解更簡單並且更好用。因此咱們先定義一個 @ServiceMethodAspectAnno
:ide
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ServiceMethodAspectAnno { }
這個註解的目標類型是 方法,而且在 運行期 保留。而後咱們就能夠來定義切面了,這個切面會攔截全部被 @ServiceMethodAspectAnno
註解的方法,並作織入處理:
@Component @Aspect // @Aspect 告訴 Spring 這是一個切面 public class ServiceMethodAspect { /** * 方法鏈接點(處理被 @ServiceMethodAspectAnno 註解的方法) */ @Pointcut("@annotation(org.mizhou.aop.aspect.anno.ServiceMethodAspectAnno)") public void methodPointcut() { } /** * 切入被 @ServiceMethodAspectAnno 註解的方法 * * @param point 鏈接點 * * @return 方法返回值 * @throws Throwable 可能拋出的異常 */ @Around("methodPointcut()") public Object doAround(ProceedingJoinPoint point) throws Throwable { // 方法不匹配,即不是要處理的業務方法 if (!isMatched(point)) { // 方法不匹配時的執行動做 onMismatch(point); // 直接執行該方法並返回結果 return point.proceed(); } // 方法返回值 Object result; // 是否拋出異常 boolean thrown = false; // 記下開始執行的時間 long startTime = System.currentTimeMillis(); try { // 執行目標方法 result = point.proceed(); } catch (Throwable e) { // 記錄拋出了異常 thrown = true; // 處理異常 onThrow(point, e); // 拋出異常的狀況下,則構造一個返回值的實例,用於業務服務方法的返回 result = getOnThrown(point, e); } // 切面結束 onComplete(point, startTime, thrown, result); return result; } /** * 是不是匹配的方法<br/> * 限定方法類型入參匹配 BaseRequest,返回值匹配 BaseResponse * * @param point 方法的鏈接點 * @return 是能夠處理的方法返回 true,不然返回 false */ private boolean isMatched(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); Class returnType = signature.getReturnType(); // returnType 是 BaseResponse 或其子類型 if (BaseResponse.class.isAssignableFrom(returnType)) { Class[] parameterTypes = signature.getParameterTypes(); // 參數必須是 BaseRequest 或其子類型 return parameterTypes.length == 1 && BaseRequest.class.isAssignableFrom(parameterTypes[0]); } return false; } /** * 若是是不要處理的方法,執行的動做 * * @param point 方法的鏈接點 */ private void onMismatch(ProceedingJoinPoint point) { Logger logger = getLogger(point); String logTag = getLogTag(point); logger.warn("{} 不是 @{} 能夠處理的方法", logTag, ServiceMethodAspectAnno.class.getSimpleName()); } /** * 拋出異常時,執行的動做 * * @param point 方法的鏈接點 * @param e 拋出的異常 */ private void onThrow(ProceedingJoinPoint point, Throwable e) { Logger logger = getLogger(point); String logTag = getLogTag(point); logger.error("{} 調用出錯", logTag, e); } /** * 構建拋出異常時的返回值 * * @param point 方法的鏈接點 * @param e 拋出的異常 * @return 拋出異常時的返回值 */ @SuppressWarnings("unchecked") private BaseResponse getOnThrown(ProceedingJoinPoint point, Throwable e) throws Exception { MethodSignature signature = (MethodSignature) point.getSignature(); Class<? extends BaseResponse> returnType = signature.getReturnType(); BaseResponse response = returnType.newInstance(); response.setPrompt(e.getMessage()); response.setSuccess(false); return response; } /** * 切面完成時,執行的動做 * * @param point 方法的鏈接點 * @param startTime 執行的開始時間 * @param thrown 是否拋出異常 * @param result 執行得到的結果 */ private void onComplete(ProceedingJoinPoint point, long startTime, boolean thrown, Object result) { BaseResponse response = (BaseResponse) result; // 設置方法調用的時間 response.setSysTime(startTime); // 設置方法調用的機器 response.setHost(getHost()); // 設置方法調用耗時 response.setCostTime(System.currentTimeMillis() - startTime); Logger logger = getLogger(point); // point.getArgs() 得到方法調用入參 Object request = point.getArgs()[0]; // 記錄方法調用信息 logger.info("{}, request={}, response={}", getLogTag(point), request, response); } /** * 模擬得到服務器名稱 */ private String getHost() { return UUID.randomUUID().toString().substring(0, 8); } /** * 得到被代理對象的 Logger * * @param point 鏈接點 * @return 被代理對象的 Logger */ private Logger getLogger(ProceedingJoinPoint point) { // 得到被代理對象 Object target = point.getTarget(); return LoggerFactory.getLogger(target.getClass()); } /** * LogTag = 類名.方法名 * * @param point 鏈接點 * @return 目標類名.執行方法名 */ private String getLogTag(ProceedingJoinPoint point) { Object target = point.getTarget(); String className = target.getClass().getSimpleName(); MethodSignature signature = (MethodSignature) point.getSignature(); String methodName = signature.getName(); return className + "." + methodName; } }
最後咱們就能夠簡化咱們的業務方法了:
@ServiceMethodAspectAnno public DivisionResponse divide(DivisionRequest request) throws Exception { DivisionResponse response = new DivisionResponse(); // 請求參數 int dividend = request.getDividend(); int divisor = request.getDivisor(); // 模擬檢查業務參數 // ...檢查業務參數... TimeUnit.MILLISECONDS.sleep(300); // 模擬執行業務 int result = dividend / divisor; // 設置業務執行結果 response.setData(result); return response; }
能夠看到,目前業務方法只保留了業務相關的邏輯,而且方法上使用了 @ServiceMethodAspectAnno
進行註解。原來的 記錄方法開始時間、捕獲異常並處理、打印錯誤日誌、記錄方法耗時 等功能,都被放到了切面當中。
如今來驗證下此時切面是否能夠按預期工做。先加入一個新的 Service 以及其實現,用於驗證切面ServiceMethodAspect
是否可以正確篩選出要處理的方法。
NumberService.java
public interface NumberService { /** * 除法運算 * * @param dividend 被除數 * @param divisor 除數 * @return 商 * @throws Exception 可能產生的異常(切面會捕獲) */ int divide(int dividend, int divisor) throws Exception; }
NumberServiceImpl.java
@Service public class NumberServiceImpl implements NumberService { @Override @ServiceMethodAspectAnno // 測試切面可以篩選方法 public int divide(int dividend, int divisor) throws Exception { // 模擬檢查業務參數 // ...檢查業務參數... TimeUnit.MILLISECONDS.sleep(300); // 模擬執行業務 int result = dividend / divisor; return result; } }
由於咱們限定了能夠被織入的方法必須參數爲 BaseRequest
,且返回值爲 BaseResponse
—— 顯然 NumberService.divide 由於返回的是 int
不知足這一點。
在 DemoController
中再增長一個處理請求的方法:
@RestController public class DemoController { ...... @Resource private NumberService numberService; @GetMapping("another.do") public Integer doAnotherDivision(@RequestParam int a, @RequestParam int b) throws Exception { return numberService.divide(a, b); } }
重啓 SpringBoot 應用:
調用正常時(http://localhost:8080/division.do?a=2&b=1):
調用出錯時(http://localhost:8080/division.do?a=2&b=0):
測試與註解不匹配的方法(http://localhost:8080/another.do?a=2&b=1):
滿意~ 這下再加入新的業務方法,就不用再在每一個方法中寫那些與業務無關的功能代碼了,直接一個註解搞定~
原本開開心心能夠收工了,也不知道是誰忽然在我腦子裏發出了一個聲音:若是下次其餘方面的業務,入參不是 BaseRequest
,返回值不是 BaseResponse
,或者要在 onThrow 時記錄不一樣的日誌 —— 那麼使用上面的方案,是否是要編寫一個新的切面?
也是, isMatched、onMismatch、onThrow、onComplete 這些方法,是每一個切面都會有的。而且對於不一樣的業務,可能會有不一樣的實現,因此應該由一個更加通用的方案,方便未來進行擴展。
咱們通常用的註解,像下面這樣子的:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)
都是能夠指定參數的。那麼咱們不也能夠在 @ServiceMethodAspectAnno
中,指定一個 處理類,專門用來處理一種類型的業務方法嗎?靈感突現:
首先咱們定義方法切面處理器的接口 MethodAspectProcessor<R>
:
/** * 方法切面處理器 */ public interface MethodAspectProcessor<R> { /** * 是不是要處理的方法 * * @param point 方法的鏈接點 * @return 是要處理的方法返回 true,不然返回 false */ boolean isMatched(ProceedingJoinPoint point); /** * 若是是不要處理的方法,執行的動做 * * @param point 方法的鏈接點 */ default void onMismatch(ProceedingJoinPoint point) { } // 下面的方法,只在 isMatched 返回 true 時有效 /** * 執行以前的動做<br> * * @param point 方法的鏈接點 * @return 返回 true 則表示繼續向下執行;返回 false 則表示禁止調用目標方法, * 方法切面處理會此時會先調用 getOnForbid 方法得到被禁止執行時的返回值,而後調用 onComplete 方法結束切面 */ default boolean onBefore(ProceedingJoinPoint point) { return true; } /** * 禁止調用目標方法時(onBefore 返回 false 時),執行該方法構建返回值 * * @param point 方法的鏈接點 * @return 禁止調用目標方法時的返回值 */ default R getOnForbid(ProceedingJoinPoint point) { return null; } /** * 拋出異常時,執行的動做 * * @param point 方法的鏈接點 * @param e 拋出的異常 */ void onThrow(ProceedingJoinPoint point, Throwable e); /** * 構建拋出異常時的返回值 * * @param point 方法的鏈接點 * @param e 拋出的異常 * @return 拋出異常時的返回值 */ R getOnThrow(ProceedingJoinPoint point, Throwable e); /** * 切面完成時,執行的動做 * * @param point 方法的鏈接點 * @param startTime 執行的開始時間 * @param forbidden 目標方法是否被禁止執行 * @param thrown 目標方法執行時是否拋出異常 * @param result 執行得到的結果 */ default void onComplete(ProceedingJoinPoint point, long startTime, boolean forbidden, boolean thrown, R result) { } }
接着咱們改造下 @ServiceMethodAspectAnno
,由於咱們如今應該是在作一個通用的方法處理器了,因此先給它更名叫 @MethodAspectAnno
,而後加入表示方法切面處理器的字段:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MethodAspectAnno { Class<? extends MethodAspectProcessor> value(); }
而後提供一個 MethodAspectProcessor
抽象類 AbstractMethodAspectProcessor<R>
,包括了 onMismatch
和 onThrow
的默認實現:
/** * 提供默認的兩個功能:<br/> * (1)方法不匹配時記錄日誌<br/> * (2)目標方法拋出異常時記錄日誌 */ public abstract class AbstractMethodAspectProcessor<R> implements MethodAspectProcessor<R> { @Override public void onMismatch(ProceedingJoinPoint point) { Logger logger = getLogger(point); String logTag = getLogTag(point); // 得到方法簽名 MethodSignature signature = (MethodSignature) point.getSignature(); // 得到方法 Method method = signature.getMethod(); // 得到方法的 @MethodAspectAnno 註解 MethodAspectAnno anno = method.getAnnotation(MethodAspectAnno.class); // 得到方法切面處理器的 Class Class<? extends MethodAspectProcessor> processorType = anno.value(); String processorName = processorType.getSimpleName(); // 若是是接口或者抽象類 if (processorType.isInterface() || Modifier.isAbstract(processorType.getModifiers())) { logger.warn("{} 須要指定具體的切面處理器,由於 {} 是接口或者抽象類", logTag, processorName); return; } logger.warn("{} 不是 {} 能夠處理的方法,或者 {} 在 Spring 容器中不存在", logTag, processorName, processorName); } @Override public void onThrow(ProceedingJoinPoint point, Throwable e) { Logger logger = getLogger(point); String logTag = getLogTag(point); logger.error("{} 執行時出錯", logTag, e); } /** * 得到被代理類的 Logger * * @param point 鏈接點 * @return 被代理類的 Logger */ protected Logger getLogger(ProceedingJoinPoint point) { Object target = point.getTarget(); return LoggerFactory.getLogger(target.getClass()); } /** * LogTag = 類名.方法名 * * @param point 鏈接點 * @return 目標類名.執行方法名 */ protected String getLogTag(ProceedingJoinPoint point) { Object target = point.getTarget(); String className = target.getClass().getSimpleName(); MethodSignature signature = (MethodSignature) point.getSignature(); String methodName = signature.getName(); return className + "." + methodName; } }
再提供一個方法不匹配時的實現 MismatchMethodAspectProcessor<R>
,做爲接口的默認實現:
/** * 方法不匹配時的方法切面處理器<br/> * isMatched 方法返回 false,即不會對任何方法作處理<br/> * 方法執行以前,會調用 onMismatch 方法,該方法在 AbstractMethodAspectProcessor 提供默認實現 */ @Component public class MismatchMethodAspectProcessor<R> extends AbstractMethodAspectProcessor<R> { @Override public boolean isMatched(ProceedingJoinPoint point) { return false; } @Override public R getOnThrow(ProceedingJoinPoint point, Throwable e) { // 不會被調用 return null; } }
此時咱們再定義 DemoService
中方法的專用方法切面處理器 ServiceMethodProcessor
,把以前方案中的代碼拿過來就行:
/** * 業務方法切面處理器 */ @Component public class ServiceMethodProcessor extends AbstractMethodAspectProcessor<BaseResponse> { /** * 是不是要處理的方法<br/> * 限定方法類型入參匹配 BaseRequest,返回值匹配 BaseResponse * * @param point 方法的鏈接點 * @return 是要處理的方法返回 true,不然返回 false */ @Override public boolean isMatched(ProceedingJoinPoint point) { MethodSignature signature = (MethodSignature) point.getSignature(); Class returnType = signature.getReturnType(); // returnType 是 BaseResponse 或其子類型 if (BaseResponse.class.isAssignableFrom(returnType)) { Class[] parameterTypes = signature.getParameterTypes(); // 參數必須是 BaseRequest 或其子類型 return parameterTypes.length == 1 && BaseRequest.class.isAssignableFrom(parameterTypes[0]); } return false; } /** * 構建拋出異常時的返回值<br/> * * @param point 方法的鏈接點 * @param e 拋出的異常 * @return 拋出異常時的返回值 */ @Override @SuppressWarnings("unchecked") public BaseResponse getOnThrow(ProceedingJoinPoint point, Throwable e) { MethodSignature signature = (MethodSignature) point.getSignature(); Class<? extends BaseResponse> returnType = signature.getReturnType(); // 構造拋出異常時的返回值 BaseResponse response = newInstance(returnType); response.setPrompt(e.getMessage()); response.setSuccess(false); return response; } /** * 切面完成時,執行的動做 * * @param point 方法的鏈接點 * @param startTime 執行的開始時間 * @param result 執行得到的結果 */ @Override public void onComplete(ProceedingJoinPoint point, long startTime, boolean forbidden, boolean thrown, BaseResponse result) { // 設置方法調用的時間 result.setSysTime(startTime); // 設置方法調用的機器 result.setHost(getHost()); // 設置方法調用耗時 result.setCostTime(System.currentTimeMillis() - startTime); Logger logger = getLogger(point); // point.getArgs() 得到方法調用入參 Object request = point.getArgs()[0]; // 記錄方法調用信息 logger.info("{}, request={}, response={}", getLogTag(point), request, result); } private BaseResponse newInstance(Class<? extends BaseResponse> type) { try { return type.newInstance(); } catch (InstantiationException | IllegalAccessException e) { return new CommonResponse(); } } /** * 模擬得到服務器名稱 */ private String getHost() { return UUID.randomUUID().toString().substring(0, 8); } }
咱們還須要一個方法,來經過註解獲取 和被註解方法匹配的 方法切面處理器,在 MethodAspectProcessor
加入一個靜態方法:
/** * 經過註解獲取 和被註解方法匹配的 切面處理器 * * @param anno 註解 * @return 匹配的切面處理器 * @throws Exception 反射建立切面處理器時的異常 */ static MethodAspectProcessor from(MethodAspectAnno anno) throws Exception { Class<? extends MethodAspectProcessor> processorType = anno.value(); // 若是指定的是接口或者抽象類(即便用方非要搞事情) if (processorType.isInterface() || Modifier.isAbstract(processorType.getModifiers())) { processorType = MismatchMethodAspectProcessor.class; } return processorType.newInstance(); }
修改下以前的方法切面,一樣的,由於該方法切面不只僅是能夠處理 Service 方法了,因而更名叫 MethodAspect
。經過在 @Around
中加入 @annotation(anno)
,能夠將註解實例注入到參數中:
@Aspect @Component public class MethodAspect { /** * 方法鏈接點(處理被 @MethodAspectAnno 註解的方法) */ @Pointcut("@annotation(org.mizhou.aop.aspect.anno.MethodAspectAnno)") public void methodPointcut() { } /** * 切入被 @MethodAspectAnno 註解的方法 * * @param point 鏈接點 * @param anno 註解 * * @return 方法返回值 * @throws Throwable 可能拋出的異常 */ @Around("methodPointcut() && @annotation(anno)") public Object doAround(ProceedingJoinPoint point, MethodAspectAnno anno) throws Throwable { // 經過註解獲取處理器 MethodAspectProcessor processor = MethodAspectProcessor.from(anno); // 方法不匹配,即不是要處理的業務方法 if (!processor.isMatched(point)) { // 方法不匹配時的執行動做 processor.onMismatch(point); // 直接執行該方法並返回結果 return point.proceed(); } // 執行以前 boolean permitted = processor.onBefore(point); // 開始執行的時間 long startTime = System.currentTimeMillis(); // 方法返回值 Object result; // 是否拋出了異常 boolean thrown = false; // 目標方法被容許執行 if (permitted) { try { // 執行目標方法 result = point.proceed(); } catch (Throwable e) { // 拋出異常 thrown = true; // 處理異常 processor.onThrow(point, e); // 拋出異常的狀況下,則構造一個返回值的實例,用於業務服務方法的返回 result = processor.getOnThrow(point, e); } } // 目標方法被禁止執行 else { // 禁止執行時的返回值 result = processor.getOnForbid(point); } // 切面結束 processor.onComplete(point, startTime, !permitted, thrown, result); return result; } }
最後在 DemoServiceImpl
的業務方法上,應用 @MethodAspectAnno
,並指定處理方法的方法切面處理器:
@MethodAspectAnno(ServiceMethodProcessor.class) public DivisionResponse divide(DivisionRequest request) throws Exception { DivisionResponse response = new DivisionResponse(); // 請求參數 int dividend = request.getDividend(); int divisor = request.getDivisor(); // 模擬檢查業務參數 // ...檢查業務參數... TimeUnit.MILLISECONDS.sleep(300); // 模擬執行業務 int result = dividend / divisor; // 設置業務執行結果 response.setData(result); return response; }
以及在不匹配的方法上,應用 @MethodAspectAnno(ServiceMethodProcessor.class)
:
@Service public class NumberServiceImpl implements NumberService { @Override // 不匹配的方法處理器 @MethodAspectAnno(ServiceMethodProcessor.class) public int divide(int dividend, int divisor) throws Exception { // 模擬檢查業務參數 // ...檢查業務參數... TimeUnit.MILLISECONDS.sleep(300); // 模擬執行業務 int result = dividend / divisor; return result; } }
大功告成,來測試一下:
正常調用(http://localhost:8080/division.do?a=2&b=1):
調用出錯(http://localhost:8080/division.do?a=2&b=0):
測試與切面處理器不匹配的方法(http://localhost:8080/another.do?a=2&b=1):
此時個人耳邊又響起了一個聲音(爲何我想的老是這麼多...):
不論是 MismatchMethodAspectProcessor
仍是用於業務方法的 ServiceMethodProcessor
,或者未來定義的一些其餘的 MethodAspectProcessor
,它們由於沒有定義變量或者沒有與其餘類分享變量,因此它們是線程安全的,不必每次在執行切面調用時,都去新建一個對應的方法切面處理器。
因而想到了 Netty 裏面的 @Sharable
,用來標記一個 ChannelHandler
是可共享的。因此咱們也能夠先定義一個 @Sharble
註解,用來標記一個 MethodAspectProcessor
是可共享的,即線程安全的。而後對被 @Sharable
註解的方法處理器,進行緩存 —— 緩存的鍵就是方法切面處理器的 Class
,值就是方法處理器的實例。定義 @Sharable
註解:
/** * 標記一個類可共享 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Sharable { }
而後修改 MethodAspectProcessor
中從註解獲取方法切面處理器的 from 方法:
public interface MethodAspectProcessor<R> { /** * 用於緩存被 @Sharable 註解的 MethodAspectProcessor(即線程安全可共享的) */ Map<Class, MethodAspectProcessor> PROCESSOR_CACHE = new ConcurrentHashMap<>(); ...... /** * 獲取 和被註解方法匹配的 切面處理器 * * @param anno 註解 * @return 匹配的切面處理器 * @throws Exception 反射建立切面處理器時的異常 */ static MethodAspectProcessor from(MethodAspectAnno anno) throws Exception { // 獲取方法切面處理器的類型 Class<? extends MethodAspectProcessor> processorType = anno.value(); Sharable sharableAnno = processorType.getAnnotation(Sharable.class); // processorType 上存在 @Sharable 註解,方法處理器可共享 if (sharableAnno != null) { // 嘗試先從緩存中獲取 MethodAspectProcessor processor = PROCESSOR_CACHE.get(processorType); // 緩存中存在對應的方法處理器 if (processor != null) { return processor; } } // 若是指定的處理器類是接口或者抽象類 if (processorType.isInterface() || Modifier.isAbstract(processorType.getModifiers())) { processorType = MismatchMethodAspectProcessor.class; } // 建立切面處理器 MethodAspectProcessor processor = processorType.newInstance(); // 處理器可共享 if (sharableAnno != null) { // 對 方法處理器 進行緩存 PROCESSOR_CACHE.put(processorType, processor); } return processor; } }
OK,完美,很是滿意~
在最近的實踐中,發現咱們的 MethodAspectProcessor
許多時候都不能脫離 Spring 容器,即須要讓 MethodAspectProcessor
成爲 Spring 容器中的 Bean,從而結合 Spring 容器中的其餘 Bean,完成更加複雜的功能。例如某個方法須要實現 3 秒內防重複調用,咱們便須要使用到緩存,而緩存相關的 Bean 是由 Spring 來管理的。因此咱們如今改造咱們的 AOP 方法,讓全部的 MethodAspectProcessor
都交給 Spring 管理。首先咱們修改各個 MethodAspectProcessor
,使用 @Component
註解讓其成爲 Spring 容器中的 Bean:
@Component public class MismatchMethodAspectProcessor<R> extends AbstractMethodAspectProcessor<R>
@Component public class ServiceMethodProcessor extends AbstractMethodAspectProcessor<BaseResponse>
修改 MethodAspect
,讓其從 Spring 容器中獲取方法切面處理器:
@Aspect @Component public class MethodAspect implements ApplicationContextAware { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private ApplicationContext appContext; /** * 方法鏈接點(處理被 @MethodAspectAnno 註解的方法) */ @Pointcut("@annotation(xyz.mizhoux.aop.aspect.anno.MethodAspectAnno)") public void methodPointcut() { } /** * 切入被 @MethodAspectAnno 註解的方法 * * @param point 鏈接點 * @param anno 註解 * @return 方法返回值 * @throws Throwable 可能拋出的異常 */ @Around("methodPointcut() && @annotation(anno)") public Object doAround(ProceedingJoinPoint point, MethodAspectAnno anno) throws Throwable { // 經過註解獲取處理器 MethodAspectProcessor processor = getProcessor(anno); ....... } private MethodAspectProcessor getProcessor(MethodAspectAnno anno) { Class<? extends MethodAspectProcessor> processorType = anno.value(); try { return appContext.getBean(processorType); } catch (BeansException ex) { logger.error("{} 在 Spring 容器中不存在", processorType.getName()); } return appContext.getBean(MismatchMethodAspectProcessor.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { appContext = applicationContext; } }
本文最終方案的代碼可見:aop-method