共计 18755 个字符,预计需要花费 47 分钟才能阅读完成。
背景
今天师兄和我说,“之叶,你设计一个方案,把目前业务方法中和业务无关的逻辑都抽离出来,让每个方法只关心自己的业务逻辑”。我会心一笑 ????
现有的业务方法
之前代码里每个业务方法几乎都是长这样:
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;
}
// 后面还有若干个类似的业务方法
......
}
很容易可以看出,记录方法开始时间 、 捕获异常并处理 、 打印错误日志 、 记录方法耗时 这些都是和业务没有关系的,业务方法关心的,只应该是 业务逻辑代码 才对。一两个方法这个样子看起来也还好,但是目前项目里面已经有十几个这种样子的代码了,以后还会更多 —— 是的,我也早就看这些业务方法不顺眼了,必须安排!
设计方案
AOP 登场
大家都听过 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);
}
}
启动应用,看一下目前的调用业务方法的情况:
- 调用正常(a=2,b=1)
- 调用出错(a=2,b=0)
编写切面
现在的 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
中,指定一个 处理类,专门用来处理一种类型的业务方法吗?灵感突现:
- 可以将 isMatched、onMismatch、onThrow、returnWhenThrowing,onComplete 这些方法,放到一个方法切面处理器接口中
- 然后不同业务方法的切面处理器,都去实现这个接口,针对自己的业务场景实现处理器的每个方法
- 提供一些方法的默认实现,例如 onMismatch 和 onThrow,这两个方法一般都是记录下日志
实现
首先我们定义方法切面处理器的接口 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
展望
突然我的耳边又响起了一个声音 …