今天师兄和我说,“之叶,你设计一个方案,把目前业务方法中和业务无关的逻辑都抽离出来,让每个方法只关心自己的业务逻辑”。我会心一笑 :point_down:
之前代码里每个业务方法几乎都是长这样:
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; } // 后面还有若干个类似的业务方法 ...... }
很容易可以看出, 记录方法开始时间 、 捕获异常并处理 、 打印错误日志 、 记录方法耗时 这些都是和业务没有关系的,业务方法关心的,只应该是 业务逻辑代码 才对。一两个方法这个样子看起来也还好,但是目前项目里面已经有十几个这种样子的代码了,以后还会更多 —— 是的,我也早就看这些业务方法不顺眼了,必须安排!
大家都听过 Spring 有两大神器 —— IOC 和 AOP —— 了解 AOP 的人,都知道 AOP 是 Aspect Oriented Programming,即面向切面编程:通过预编译方式(CGLib)或者运行期动态代理(JDK Proxy)实现程序功能的代理的技术。此时的情况,就完美匹配 AOP 的应用场景。我们可以定义一个注解, @ServiceMethodAspectAnno
,然后对被 @ServiceMethodAspectAnno
注解的方法,进行增强(Advice)处理:在方法 调用前 、 调用后 或者 抛出异常时 ,进行额外的处理。
为了方便说明,首先我们建立一个简单的 SpringBoot 项目,并添加示例的 Service 和 Controller(文末有 github 链接):
DemoService.java
public interface DemoService { /** * 除法运算 * * @param request 除法运算请求 * @return 除法运算结果 */ DivisionResponse divide(DivisionRequest request); }
DemoServiceImpl.java
@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.java
@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); } }
启动应用,看一下目前的调用业务方法的情况:
现在的 Java Web 应用,使用注解来进行配置和做 AOP 已经是主流 —— 因为相比 XML,注解更简单而且更好用。所以我们先定义一个 @ServiceMethodAspectAnno
:
@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(); } // 记下开始执行的时间 long startTime = System.currentTimeMillis(); // 方法返回值 Object result; try { // 执行目标方法 result = point.proceed(); // 正常返回 onReturn(point, result); } catch (Throwable e) { // 处理异常 onThrow(point, e); // 抛出异常的情况下,则构造一个返回值的实例,用于业务服务方法的返回 result = returnWhenThrowing(point, e); } // 切面结束 onComplete(point, startTime, 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 result 方法返回的结果 */ private void onReturn(ProceedingJoinPoint point, Object result) { String logTag = getLogTag(point); Logger logger = getLogger(point); ((BaseResponse)result).setSuccess(true); logger.info("{} 正常调用", logTag); } /** * 抛出异常时,执行的动作 * * @param point 方法的连接点 * @param e 抛出的异常 */ private void onThrow(ProceedingJoinPoint point, Throwable e) { Logger logger = getLogger(point); String logTag = getLogTag(point); logger.error("{} 调用出错", logTag, e); } /** * 构建抛出异常时的返回值<br/> *(不知道这个方法起什么名字才好,如果大家有好的建议,欢迎留言) * * @param point 方法的连接点 * @param e 抛出的异常 * @return 抛出异常时的返回值 */ @SuppressWarnings("unchecked") private BaseResponse returnWhenThrowing(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 result 执行获得的结果 */ private void onComplete(ProceedingJoinPoint point, long startTime, 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 以及其实现,用于验证切面能够正确筛选出要处理的方法:
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>
:
/** * 方法切面处理器 * * @param <R> 方法返回值的类型 */ public interface MethodAspectProcessor<R> { /** * 是否是匹配的方法 * * @param point 方法的连接点 * @return 是可以处理的方法返回 true,否则返回 false */ boolean isMatched(ProceedingJoinPoint point); /** * 如果是不要处理的方法,执行的动作 * * @param point 方法的连接点 */ default void onMismatch(ProceedingJoinPoint point) { } // 下面的方法,只在 isMatched 返回 true 时有效 /** * 执行之前的动作 * * @param point 方法的连接点 */ default void onBefore(ProceedingJoinPoint point) { } /** * 正常返回时,执行的动作 * * @param point 方法的连接点 * @param result 方法返回的结果 */ default void onReturn(ProceedingJoinPoint point, R result) { } /** * 抛出异常时,执行的动作 * * @param point 方法的连接点 * @param e 抛出的异常 */ void onThrow(ProceedingJoinPoint point, Throwable e); /** * 构建抛出异常时的返回值 *(不知道这个方法起什么名字才好,如果大家有好的建议,欢迎留言) * * @param point 方法的连接点 * @param e 抛出的异常 * @return 抛出异常时的返回值 */ R returnWhenThrowing(ProceedingJoinPoint point, Throwable e); /** * 切面完成时,执行的动作 * * @param point 方法的连接点 * @param startTime 执行的开始时间 * @param result 执行获得的结果 */ void onComplete(ProceedingJoinPoint point, long startTime, R result); }
接着我们改造下 @ServiceMethodAspectAnno
,因为我们现在应该是在做一个通用的方法处理器了,所以先给它改名叫 @MethodAspectAnno
,然后加入表示方法切面处理器的字段:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MethodAspectAnno { Class<? extends MethodAspectProcessor> value(); }
然后提供一个 MethodAspectProcessor
抽象类 AbstractMethodAspectProcessor<R>
,包括了 onMismatch
和 onThrow
的默认实现:
/** * 提供默认的(1)方法不匹配时记录日志、(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(); // 如果是接口或者抽象类 if (processorType.isInterface() || Modifier.isAbstract(processorType.getModifiers())) { logger.warn("{} 需要指定具体的切面处理器,因为 {} 是接口或者抽象类", logTag, processorType.getSimpleName()); return; } logger.warn("{} 不是 {} 可以处理的方法", logTag, processorType.getSimpleName()); } @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 提供默认实现 */ public class MismatchMethodAspectProcessor<R> extends AbstractMethodAspectProcessor<R> { @Override public boolean isMatched(ProceedingJoinPoint point) { return false; } @Override public R returnWhenThrowing(ProceedingJoinPoint point, Throwable e) { // 不会被调用 return null; } @Override public void onComplete(ProceedingJoinPoint point, long startTime, R result) { // 不会被调用 } }
此时我们再定义 DemoService
方法的专用方法切面处理器 DemoServiceMethodAspectProcessor
,把之前方案中的代码拿过来就行:
public class DemoServiceMethodAspectProcessor 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; } /** * 如果是不匹配的方法,执行的动作 * * @param point 方法的连接点 */ @Override public void onMismatch(ProceedingJoinPoint point) { Logger logger = getLogger(point); String logTag = getLogTag(point); logger.warn("{} 不是 @{} 可以处理的方法", logTag, MethodAspectAnno.class.getSimpleName()); } /** * 正常返回时,执行的动作 * * @param point 方法的连接点 * @param result 方法返回的结果 */ @Override public void onReturn(ProceedingJoinPoint point, BaseResponse result) { String logTag = getLogTag(point); Logger logger = getLogger(point); result.setSuccess(true); logger.info("{} 正常调用", logTag); } /** * 抛出异常时,执行的动作 * * @param point 方法的连接点 * @param e 抛出的异常 */ @Override public void onThrow(ProceedingJoinPoint point, Throwable e) { Logger logger = getLogger(point); String logTag = getLogTag(point); logger.error("{} 调用出错", logTag, e); } /** * 构建抛出异常时的返回值<br/> * 不知道起什么名字好,如果大家有好的建议,欢迎留言 * * @param point 方法的连接点 * @param e 抛出的异常 * @return 抛出异常时的返回值 */ @Override @SuppressWarnings("unchecked") public BaseResponse returnWhenThrowing(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, BaseResponse 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 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 get(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.get(anno); // 方法不匹配,即不是要处理的业务方法 if (!processor.isMatched(point)) { // 方法不匹配时的执行动作 processor.onMismatch(point); // 直接执行该方法并返回结果 return point.proceed(); } // 记下开始执行的时间 long startTime = System.currentTimeMillis(); // 方法返回值 Object result; try { // 执行目标方法 result = point.proceed(); // 正常返回 processor.onReturn(point, result); } catch (Throwable e) { // 处理异常 processor.onThrow(point, e); // 抛出异常的情况下,则构造一个返回值的实例,用于业务服务方法的返回 result = processor.returnWhenThrowing(point, e); } // 切面结束 processor.onComplete(point, startTime, result); return result; } }
最后在 DemoServiceImpl
的业务方法上,应用 @MethodAspectAnno
,指定处理方法的方法切面处理器:
@MethodAspectAnno(DemoServiceMethodAspectProcessor.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(DemoServiceMethodAspectProcessor.class)
:
@Service public class NumberServiceImpl implements NumberService { @Override @MethodAspectAnno(DemoServiceMethodAspectProcessor.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
还是 DemoServiceMethodAspectProcessor
,或者将来定义的一些其他的 MethodAspectProcessor
,它们因为没有定义变量或者没有与其他类分享变量,所以它们是线程安全的,没必要每次在执行切面调用时,都去新建一个对应的方法切面处理器。
于是想到了 Netty 里面的 @Sharable
,用来标记一个 ChannelHandler
是可共享的。所以我们也可以先定义一个 @Sharble
注解,用来标记一个 MethodAspectProcessor
是可共享的,即线程安全的。然后对被 @Sharable
注解的方法处理器,进行缓存 —— 缓存的键就是方法切面处理器的 Class
,值就是方法处理器的实例。定义 @Sharable
注解:
/** * 标记一个类可共享 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Sharable { }
然后修改 MethodAspectProcessor
中从注解获取方法切面处理器的 get 方法:
public interface MethodAspectProcessor<R> { /** * 用于缓存被 @Sharable 注解的 MethodAspectProcessor(即线程安全可共享的) */ Map<Class, MethodAspectProcessor> PROCESSOR_CACHE = new ConcurrentHashMap<>(); ...... /** * 获取 和被注解方法匹配的 切面处理器 * * @param anno 注解 * @return 匹配的切面处理器 * @throws Exception 反射创建切面处理器时的异常 */ static MethodAspectProcessor get(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,完美~
本文最终 AOP 方案的代码链接: aop-method
突然我的耳边又响起了一个声音...