不得了,看到自定义aop,这下终于知道它有啥作用了,这玩意确实好用
做sky-takeout的时候,没有几集就涉及到公共字段的自动赋值,例如createUser、createTime之类的。很容易可以想到,利用类似‘拦截器’的东西就可以实现,但是具体怎么做呢?所以先介绍下通用的流程步骤:
1.声明自定义注解
1.1 元注解 - 注解的注解
@Target 注解可声明位置
-
ElementType.PACKAGE:该注解只能声明在一个包名前。
-
ElementType.ANNOTATION_TYPE:该注解只能声明在一个注解类型前。
-
ElementType.TYPE:该注解只能声明在一个类前。
-
ElementType.CONSTRUCTOR:该注解只能声明在一个类的构造方法前。
-
ElementType.LOCAL_VARIABLE:该注解只能声明在一个局部变量前。
-
ElementType.METHOD:该注解只能声明在一个类的方法前。
-
ElementType.PARAMETER:该注解只能声明在一个方法参数前。
-
ElementType.FIELD:该注解只能声明在一个类的字段前。
@Retention保留/注解声明周期
-
RetentionPolicy.SOURCE : 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
-
RetentionPolicy.CLASS : 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;
-
RetentionPolicy.RUNTIME : 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
对于@Target我没有意见,但是对于@Retention我不明白,Source和Class级别的声明周期有什么用呢?
看到一个回答:‘源代码级别的注解有两个作用,一是作为文档的补充,给人看的,比如@Override,二是作为源代码生成器的材料,比如ButterKnife框架(不懂)’
懂了一些了,所以目前对于业务级别的开发,应该一般就是Runtime界别的
1.2 示例:
/**
* 自定义注解,用于标识某个方法需要进行公共字段自动填充处理
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();
}
2.声明自定义切面
2.1 注解介绍
@Aspect:作用是把当前类标识为一个切面供容器读取
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行
2.2 切入点注解语法
/**
* 1、使用within表达式匹配
* 下面示例表示匹配com.leo.controller包下所有的类的方法
*/
@Pointcut("within(com.leo.controller..*)")
public void pointcutWithin(){
}
/**
* 2、this匹配目标指定的方法,此处就是HelloController的方法
*/
@Pointcut("this(com.leo.controller.HelloController)")
public void pointcutThis(){
}
/**
* 3、target匹配实现UserInfoService接口的目标对象
*/
@Pointcut("target(com.leo.service.UserInfoService)")
public void pointcutTarge(){
}
/**
* 4、bean匹配所有以Service结尾的bean里面的方法,
* 注意:使用自动注入的时候默认实现类首字母小写为bean的id
*/
@Pointcut("bean(*ServiceImpl)")
public void pointcutBean(){
}
/**
* 5、args匹配第一个入参是String类型的方法
*/
@Pointcut("args(String, ..)")
public void pointcutArgs(){
}
/**
* 6、@annotation匹配是@Controller类型的方法
*/
@Pointcut("@annotation(org.springframework.stereotype.Controller)")
public void pointcutAnnocation(){
}
/**
* 7、@within匹配@Controller注解下的方法,要求注解的@Controller级别为@Retention(RetentionPolicy.CLASS)
*/
@Pointcut("@within(org.springframework.stereotype.Controller)")
public void pointcutWithinAnno(){
}
/**
* 8、@target匹配的是@Controller的类下面的方法,要求注解的@Controller级别为@Retention(RetentionPolicy.RUNTIME)
*/
@Pointcut("@target(org.springframework.stereotype.Controller)")
public void pointcutTargetAnno(){
}
/**
* 9、@args匹配参数中标注为@Sevice的注解的方法
*/
@Pointcut("@args(org.springframework.stereotype.Service)")
public void pointcutArgsAnno(){
}
/**
* 10、使用excution表达式
* execution(
* modifier-pattern? //用于匹配public、private等访问修饰符
* ret-type-pattern //用于匹配返回值类型,不可省略
* declaring-type-pattern? //用于匹配包类型
* name-pattern(param-pattern) //用于匹配类中的方法,不可省略
* throws-pattern? //用于匹配抛出异常的方法
* )
*
* 下面的表达式解释为:匹配com.leo.controller.HelloController类中以hello开头的修饰符为public返回类型任意的方法
*/
@Pointcut(value = "execution(public * com.leo.controller.HelloController.hello*(..))")
public void pointCut() {
}
2.3 示例:
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");
//获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
//获取到当前被拦截的方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
Object entity = args[0];
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if(operationType == OperationType.INSERT){
//为4个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3.添加注解
...
/**
* 新增分类
* @param category
*/
@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user) " +
"VALUES " +
"(#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
@AutoFill(value = OperationType.INSERT)
void insert(Category category);
...
标签:void,entity,class,Pointcut,diy,aop,注解,public
From: https://www.cnblogs.com/yuqiu2004/p/18421216