先简单介绍一下自定义注解相关的一些前置知识,完整代码可通过目录直接跳转查看
元注解 的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation(元注解)类型,它们被用来提供对其它 annotation类型作说明
下面对这四个元注解进行详细介绍:
@Target:标明作用范围,取值在java.lang.annotation.ElementType
中进行定义
public enum ElementType {
/** 类,接口(包括注解类型)或枚举的声明 */
TYPE,
/** 属性的声明 */
FIELD,
/** 方法的声明 */
METHOD,
/** 方法形式参数声明 */
PARAMETER,
/** 构造方法的声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注解类型声明 */
ANNOTATION_TYPE,
/** 包的声明 */
PACKAGE
}
@Retention:修饰自定义注解的生命周期,取值在java.lang.annotation.RetentionPolicy
中定义:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
* (注解将被编译器忽略掉)
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
* (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
* (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
@Documented:指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中
@Inherited:是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解,@Inherited注解只对那些@Target被定义为 ElementType.TYPE 的自定义注解起作用
介绍完元注解之后,我们再来看一下自定义注解的实现代码。
AspectJ 是一个基于 Java 语言的 AOP 框架。在 Spring 2.0 以后,新增了对 AspectJ 框架的支持。在 Spring 框架中建议使用 AspectJ 框架开发 AOP,在使用 AspectJ 配置切面时,切面不需要实现一些特定的接口。
Joinpoint(连接点): 类里面可以被增强的方法,这些方法称为连接点,连接点具有以下方法
joinPoint.getTarget(); 获取目标对象
joinPoint.getSignature().getName(); 获取目标方法名
joinPoint.getArgs(); 获取目标方法参数列表
joinPoint.getThis(); 获取代理对象
**Pointcut(切入点): ** 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义,切入点指示符规则稍后详细介绍
方式一:
@Before("within(com.baomidou.mybatisplus.extension.service.IService+)")
public void before(JoinPoint joinPoint) {
System.out.println("进入切面");
}
方式二:
/**
* 使用Pointcut定义切点
*/
@Pointcut("execution(* com.dwp.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut(){}
/**
* 应用切入点函数
*/
@After(value="myPointcut()")
public void after(){
System.out.println("最终通知....");
}
Advice(通知/增强): 所谓通知是指拦截到Joinpoint之后所要做的事情,分为以下五种类型
Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
Target(目标对象): 代理的目标对象(要增强的类)
Weaving(织入): 是把增强应用到目标的过程,把advice 应用到 target的过程
Proxy(代理): 一个类被AOP织入增强后,就产生一个结果代理类
为了方法通知应用到相应过滤的目标方法上,SpringAOP提供了匹配表达式,这些表达式也叫切入点指示符.
a. 通配符
在定义匹配表达式时,通配符几乎随处可见,如*、… 、+ ,它们的含义如下:
… :匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包
//任意返回值,任意名称,任意参数的公共方法
execution(public * *(..))
//匹配com.dwp.dao包及其子包中所有类中的所有方法
within(com.dwp.dao..*)
+ :匹配给定类的任意子类
//匹配实现了DaoUser接口的所有子类的方法
within(com.dwp.dao.DaoUser+)
* :匹配任意数量的字符
//匹配com.dwp.service包及其子包中所有类的所有方法
within(com.dwp.service..*)
//匹配以set开头,参数为int类型,任意返回值的方法
execution(* set*(int))
b. 类型签名表达式
为了方便类型(如接口、类名、包名)过滤方法,Spring AOP 提供了within关键字。其语法格式如下:
within(<type name>)
示例:
//匹配com.dwp.dao包及其子包中所有类中的所有方法
@Pointcut("within(com.dwp.dao..*)")
//匹配UserDaoImpl类中所有方法
@Pointcut("within(com.dwp.dao.UserDaoImpl)")
//匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.dwp.dao.UserDaoImpl+)")
//匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.dwp.dao.UserDao+)")
c. 方法签名表达式
如果想根据方法签名进行过滤,关键字execution可以帮到我们,语法表达式如下
//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
对于给定的作用域、返回值类型、完全限定类名以及参数匹配的方法将会应用切点函数指定的通知,这里给出模型案例:
//匹配UserDaoImpl类中的所有方法
@Pointcut("execution(* com.dwp.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.dwp.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.dwp.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.dwp.dao.UserDaoImpl.*(int , ..))")
d. 其他指示符
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
//匹配名称中带有后缀Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}
this :用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
//匹配了任意实现了UserDao接口的代理对象的方法进行过滤
@Pointcut("this(com.dwp.spring.springAop.dao.UserDao)")
private void myPointcut2(){}
target :用于匹配当前目标对象类型的执行方法;
//匹配了任意实现了UserDao接口的目标对象的方法进行过滤
@Pointcut("target(com.dwp.spring.springAop.dao.UserDao)")
private void myPointcut3(){}
@within:用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行;
//匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("@within(com.dwp.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}
@annotation(com.dwp.spring.MarkerMethodAnnotation) : 根据所应用的注解进行方法过滤
//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.dwp.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}
最后说明一点,切点指示符可以使用运算符语法进行表达式的混编,如and、or、not(或者&&、||、!),如下例:
//匹配了任意实现了UserDao接口的目标对象的方法并且该接口不在com.dwp.dao包及其子包下
@Pointcut("target(com.dwp.spring.springAop.dao.UserDao) !within(com.dwp.dao..*)")
private void myPointcut6(){}
//匹配了任意实现了UserDao接口的目标对象的方法并且该方法名称为addUser
@Pointcut("target(com.dwp.spring.springAop.dao.UserDao)&&execution(* com.dwp.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut7(){}
自定义注解工程:
pom.xml引入依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
Insert.class:
import java.lang.annotation.*;
/**
* 修饰在方法上,用于数据持久化时自动给创建人属性字段赋值
*
* 从Keycloak中获取操作用户信息,如果实体类存在指定名称的属性,则会自动为其赋值
*
* @author deng.weiping
* @since 0.0.1
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Insert {
/**
* 需赋值的字段名称,默认值:createBy
*/
String name() default "createBy";
}
InsertAspect.class:
import com.dwp.sdw.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class InsertAspect {
/**
* 前置通知
*/
@Before("@annotation(an)")
public void before(JoinPoint joinPoint, Insert an) {
invokeMethod(joinPoint, an);
}
/**
* 获取请求参数
*
* @param point
* @return
*/
private static void invokeMethod(JoinPoint point, Insert an) {
Object[] objs = point.getArgs();
if (objs.length > 0) {
for (Object obj : objs) {
ReflectUtil.setSpecifiedFieldValue(obj, an.name());
}
}
}
}
ReflectUtil.class:
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
public class ReflectUtil {
/**
* 查找指定属性并赋值
*
* @param bean 类对象
* @param fieldName 字段名
*/
public static void setSpecifiedFieldValue(Object bean, String fieldName) {
try {
Class clazz = bean.getClass();
while (clazz != Object.class) {
Field[] fields = clazz.getDeclaredFields();
List<Field> results = Arrays.stream(fields).filter(field -> fieldName.equals(field.getName())).collect(Collectors.toList());
if (!results.isEmpty()) {
results.forEach(field -> setFieldValue(bean, field));
//最小匹配
break;
}
//当前类未匹配上指定字段,尝试向上从父类中查找指定字段
clazz = clazz.getSuperclass();
}
} catch (Exception e) {
log.warn("The specified field was not matched");
}
}
/**
* 通过反射给类的属性赋值
*
* @param bean 类对象
* @param field 类属性
*/
public static void setFieldValue(Object bean, Field field) {
field.setAccessible(true);
try {
/**
* 具体值根据实际情况自行获取
* 这里使用Keycloak
*/
field.set(bean, KeycloakUtils.getUsername());
} catch (IllegalAccessException e) {
log.warn("Failed to set a value for the property:{}", e);
}
}
}
项目工程
<dependency>
<groupId>com.dwpgroupId>
<artifactId>sdw-annotationartifactId>
<version>0.0.1version>
dependency>
@Insert
public void save(Student stu) {
studentMapper.insert(stu);
}