在项目中,我们总会或多或少接触过一些注解,如常见的@Override,@Autowired。我们分别点进去看一下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
我们发现它们的类型都是@interface,而且有2个相同的注解@Target和@Retention,这两个注解是我们写新注解时必须要有的注解,它们都是元注解。
元注解
元注解的作用就是注解其他注解,一般我们使用自定义注解时,就需要用元注解来标注我们自己的注解。一共有四个元注解。
@Target
它指定了注解修饰的范围,在哪些作用域上起作用。我们可以看到它里面的参数都是ElementType枚举类。
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
//用于描述类,接口(包括注解类型),或enum声明
TYPE,
/** Field declaration (includes enum constants) */
//用于属性声明(包括enum常量)
FIELD,
/** Method declaration */
//方法声明
METHOD,
/** Formal parameter declaration */
//形式参数声明
PARAMETER,
/** Constructor declaration */
//构造方法声明
CONSTRUCTOR,
/** Local variable declaration */
//局部变量声明(方法内部变量)
LOCAL_VARIABLE,
/** Annotation type declaration */
//注解类型声明,可参见@Taeget注解
ANNOTATION_TYPE,
/** Package declaration */
//包的声明
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Retention
定义了注解的声明周期
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文件中,但在运行时不会被虚拟机保留,这是一个默认的行为)
*/
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.
*
* @see java.lang.reflect.AnnotatedElement
* 运行时保留(注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到)
*/
RUNTIME
}
@Documented
被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
@Inherited
该注解表示子类可以集成加载父类上的注解。
1、自定义注解实现参数校验
我们先新建一个自定义注解的类,然后在继续说明一些注解的知识。
@Target(FIELD)
@Retention(RUNTIME)
@Documented
//@Constraint(validatedBy = AllowValueValidator.class)
public @interface AllowValue {
String message() default "无效值";
String[] allowValues() default {};
/**
*
* @return
*/
Class<?>[] groups() default {};
/**
*
* @return
*/
Class<? extends Payload>[] payload() default {};
}
我们先看前两行,message()说明了错误信息,allowValues()则是我们需要校验的参数,他支持的数据类型有
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
default后面则是指定了默认的参数。
下面两个方法是我们需要对注解进行校验时用到的默认方法,必须存在。
下面我们来编写一个验证器,需要实现ConstraintValidator接口,然后@AllowValue注解需要加上@Constraint(validatedBy = AllowValueValidator.class)。这样我们使用自定义注解时,就会根据isValid方法去校验参数。
public class AllowValueValidator implements ConstraintValidator<AllowValue, Object> {
String[] allowValues;
@Override
public void initialize(AllowValue constraintAnnotation) {
allowValues = constraintAnnotation.allowValues();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
if (allowValues == null || allowValues.length == 0) {
throw new IllegalArgumentException("allowValues can not be null or empty");
}
String valueStr;
if (value instanceof Number) {
valueStr = value.toString();
} else if (value instanceof String) {
valueStr = (String) value;
} else {
throw new IllegalArgumentException("must be Number or String");
}
if(StringUtils.isBlank(valueStr)) {
return true;
}
boolean check = false;
for (String str : allowValues) {
if (StringUtils.isBlank(str)) {
throw new IllegalArgumentException("allowValues can not be null or empty");
}
if (valueStr.equals(str)) {
check = true;
break;
}
}
return check;
}
}
2、自定义注解实现AOP
自定义注解:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLog {
String action() default "";
}
切面类:
@Aspect
@Component
public class RequestLogAspect {
@Pointcut("execution(* com.demo.controller.*.*(..))")
public void controllerAspect(){}
@Before("controllerAspect()")
public void before(){
System.out.println("@Before 打印方法信息前执行");
}
@Around("controllerAspect() && @annotation(requestLog)")
public Object doing(ProceedingJoinPoint joinPoint, RequestLog requestLog){
printMsg(joinPoint, requestLog);
try {
Object result = joinPoint.proceed();
System.out.println("@Around环绕结束");
return result;
} catch (Throwable throwable) {
throw new BaseException(throwable.getMessage());
}
}
@After("controllerAspect()")
public void after(){
System.out.println("@After 打印方法信息后执行");
}
private void printMsg(ProceedingJoinPoint joinPoint, RequestLog requestLog){
String action = requestLog.action();
System.out.println(action);
Class target = joinPoint.getTarget().getClass();
String className = target.getName();
System.out.println(className);
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0){
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
System.out.println(arg);
}
}
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName);
}
}
@Aspect注解表示这是一个切面类,用于切面的类必须有。
@Pointcut是切点,表示需要用到注解的方法。
execution()是方法描述匹配,格式为:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
modifiers-pattern:修饰符模式,如public等,可省略。
ret-type-pattern:返回类型,用*表示任何返回值。
declaring-type-pattern:类型声明,可省略
name-pattern:指定方法名, *代表所有 (上述第一个*表示该包下的所有类,第二个*表示类的方法)。
param-pattern:指定方法参数,(..)代表所有参数,(*)代表一个参数。
throws-pattern:异常声明,可省略。
注意:每个pattern之间都有一个空格,尤其ret-type-pattern用*表达时后面不要忘记
例子中的execution()的表达式格式为execution(ret-type-pattern name-pattern(param-pattern)),含义是controller包下所有类的所有方法。更多可以点击Spring AOP 中pointcut expression表达式解析及配置了解
还可以使用@annotation,表示有这个注解的方法进入这个切面。例
@Pointcut("@annotation(com.demo.validate.annotation.RequestLog)")
@Around可以让我们在拦截方法前后做一些操作。格式里必须有切点的方法,如果我们要用到注解里的参数,则必须加上@annotation(),里面的名字必须和方法中的参数名一致。用joinPoint.proceed()来执行被拦截的方法,如果原来的方法有返回值,则我们必须return joinPoint.proceed()的结果。