开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
前言:
作为一个CRUD的'资深'coder,我们修改、删除、查看详情接口我们需要做的第一件事就是先校验前端传来的主键id是否有效,我们一般会这样操作,获取到入参的id,然后查询数据库此id对应的数据是否存在,不存在抛出异常,id有效继续执行下面的业务操作,其实我们可以发现这些接口中的这个操作都是一样的业务逻辑,只是查询的数据库表和入参id对应的属性名不同,这个时候我们就可以使用切面来处理这个统一的业务逻辑。下面我们用Spring的aop实现这个切面。
二丶分析
我们分析一下难点:
- 如何获取到入参?
- 如何知道这个业务id应该查询哪个数据库表?
1. 如何获取到入参?
这个我们可以通过spel表达式来实现,通过运行期间执行的表达式
将值装配到我们的属性或构造函数当中,也可以调用JDK中提供的静态常量,获取外部Properties文件中的的配置。
2. 如何知道这个业务id应该查询哪个数据库表?
我们可以在注解中定义一个参数通过参数指定是哪个表,但是由于我们项目中用到了mybatis-plus我们每个实体类上都会使用一个@TableName注解,会指定当前实体类对应的数据库表,所以我这里是在注解中定义一个类属性,然后去拿到类上的@TableName注解中填写的实体类名。
一丶代码
定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface CheckId {
String id();
String idAlias() default "id";
boolean isDeletedField() default true;
Class<?> clazz();
String errorMessage() default "Id is invaild.";
}
定义aspect
@Aspect
@Component
public class CheckIdAspect {
private static final String SQL = "select count(*) from %s where %s = %s";
private static final String SQL_DELETED = "select count(*) from %s where %s = %s and deleted = 0";
@Autowired
private JdbcTemplate jdbcTemplate;
@Pointcut("@annotation(com.zzzwww.aspect.annotation.CheckId)")
public void pointcut(){}
@Before("pointcut()")
@SuppressWarnings("all")
public void checkIdAspect(JoinPoint jp) {
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
CheckId checkId = method.getAnnotation(CheckId.class);
String id = this.buildExternalIdId(checkId, jp, method);
String idAlias = checkId.idAlias();
Class<?> clzss = checkId.clazz();
TableName annotation = clzss.getAnnotation(TableName.class);
String tableName = annotation.value();
long count;
String sql;
if (BooleanUtil.isTrue(checkId.isDeletedField())) {
sql = String.format(SQL_DELETED, tableName ,idAlias, id);
} else {
sql = String.format(SQL, tableName ,idAlias, id);
}
count = jdbcTemplate.queryForObject(sql, Long.class);
Assert.isTrue(count > 0, checkId.errorMessage());
}
private String buildExternalIdId(CheckId anno, JoinPoint pjp, Method method) {
ExpressionParser parser = new SpelExpressionParser();
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(method);
Object[] args = pjp.getArgs();
EvaluationContext context = new StandardEvaluationContext();
for (int len = 0; len < params.length; len++) {
context.setVariable(params[len], args[len]);
}
Expression expression = parser.parseExpression(anno.id());
return expression.getValue(context, String.class);
}
}
总结
Aop其实还有很多使用场景,这只是其中一个使用场景,我会在后续文章中分享更多的aop对于其他场景的具体实现案例。