Spring的源码满是注解,理解注解是怎么回事,有助于接下来的理解。
创建自定义注解
使用IDEA,new Java Class会出现如下对话框,选择Annotation
,输入文件名,就创建好了一个自定义注解。
public @interface MyLog {
}
然后我们再创建一个Car类,用于使用咱们创建的注解。
import lombok.extern.slf4j.Slf4j;
@Slf4j
@MyLog
public class Car {
public int chang;
public void didi(){
log.info("didi");
}
}
很简单,它有一个chang的属性和一个didi的方法,可以看到类上面,使用了一个lombok提供的 @Slf4j
注解,这个注解就是输出日志用的,可以看到didi方法中有一个log.info(),哎,这就是这个注解提供的能力。
另外,类上面的 @MyLog
,就是咱们自己创建的注解啦。
注解的值
对比咱们日常使用到的@Service("carService")
你会发现,这个@Service注解带了一个值,咱们能不能带?能。
但是,很明显,报错了
此时,只需要在注解里写上String value() default "";
变成如下这样,就可以了。
public @interface MyLog {
String value() default "";
}
我想有多个值,例如再增加一个text,String text() default "";
又报错了。这是因为value是个特殊值,如果只有它自己,是可以省略不写的,但是如果有多个值,那么必须要这么写:
ok,现在能正常使用了,那么又如何去获取到里面的这两个值呐?
咱们再来创建一个测试类,其中有个方法isAnnotationPresent()
,就是看B类型的注解是否在A类上。
@Slf4j
public class Test {
public static void main(String[] args) {
Class cls = Car.class;
if (cls.isAnnotationPresent(MyLog.class)) {
MyLog myLog = (MyLog) cls.getAnnotation(MyLog.class);
log.info( "value:"+myLog.value());
log.info( "text:"+myLog.text());
}
}
}
这样就能获取到value值和text值,好,我们执行看一下:
Process finished with exit code 0
啥也没有
很明显,没有进入到if语句里。为啥?接着往下看
元注解@Retention
啥是元注解?注解的注解。这样想,我是个注解,我去修饰别的类,那再有个注解修饰我不过分吧,哎,这就是元注解。
元注解就限定了注解的一些东西,比如 @Retention
的作用就是规定了注解的保留策略。
咱们都知道,咱们写的.java文件是源代码,还有编译后的.class文件,然后是虚拟机加载class文件,这就对应了三个不同的时期。
那么注解,也是这样。
策略就有SOURCE、CLASS、RUNTIME
,分别对应了上面说的三种时期。
为啥要这样?因为有些注解只是需要在源代码里出现,编译后就用不着了,这类比如说有 @Override
,它的作用只是一个辅助开发人员检查的作用,当我们重写方法写错了,就会报错。
还有就是前边提到的@Slf4j,可以看下编译后的Car.class文件,没了。
但是多了一行private static final Logger log = LoggerFactory.getLogger(Car.class);
,这是lombok插件提供的能力。
package com.example.demo.spring5;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@MyLog(
value = "car",
text = "xxxx"
)
public class Car {
private static final Logger log = LoggerFactory.getLogger(Car.class);
public int chang;
public Car() {
}
public void didi() {
log.info("didi");
}
}
通过源码,我们就可以看到注解@Slf4j使用的就是SOURCE策略
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface Slf4j {
String topic() default "";
}
@Retention的默认值
但是咱们自己创建的注解,啥也没写啊,那它肯定有个默认值。
再看Car.class文件,@MyLog注解是有的,源代码也是有的,就是运行没有,那么我们可以推断,默认策略就是CLASS。这一点从Retention源代码的注解中也可以看到。
If no Retention annotation is present on an annotation type declaration, the retention policy defaults to {@code RetentionPolicy.CLASS}.
好,那咱们换上RUNTIME试一试
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
String value() default "";
String text() default "";
}
14:19:20.596 [main] INFO com.example.demo.spring5.Test - value:car
14:19:20.607 [main] INFO com.example.demo.spring5.Test - text:xxxx
Process finished with exit code 0
终于获取到值了
突然又发现,咱现在的注解只用在类上面,那用在方法、属性上行不行呐?
好,现在对Car类进行改造
@Slf4j
@MyLog(value="car",text="xxxx")
public class Car {
@MyLog(value="100",text="长度")
public int chang;
@MyLog(value="didi",text="打印内容")
public void didi(){
log.info("didi");
}
}
测试类也改造下
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@Slf4j
public class Test {
public static void main(String[] args) {
Class cls = Car.class;
try {
//获取方法上的注解
Method didi = cls.getDeclaredMethod("didi");
if (didi.isAnnotationPresent(MyLog.class)) {
MyLog annotation = didi.getAnnotation(MyLog.class);
log.info("方法{}上的value值:{}",didi.getName(),annotation.value());
log.info("方法{}上的text值:{}",didi.getName(),annotation.text());
}
//获取属性上的注解
Field chang = cls.getDeclaredField("chang");
if (chang.isAnnotationPresent(MyLog.class)) {
MyLog annotation = chang.getAnnotation(MyLog.class);
log.info("属性{}上的value值:{}",chang.getName(),annotation.value());
log.info("属性{}上的text值:{}",chang.getName(),annotation.text());
}
} catch (NoSuchMethodException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}
看下输出:
14:25:23.158 [main] INFO com.example.demo.spring5.Test - 方法didi上的value值:didi
14:25:23.163 [main] INFO com.example.demo.spring5.Test - 方法didi上的text值:打印内容
14:25:23.163 [main] INFO com.example.demo.spring5.Test - 属性chang上的value值:100
14:25:23.163 [main] INFO com.example.demo.spring5.Test - 属性chang上的text值:长度
Process finished with exit code 0
哎,我就是想让这个注解只能使用在类上,有没有办法?
有!
元注解@Target
这个注解的作用就是限定注解的使用范围,值有很多,常见的有TYPE、METHOD、FIELD
,CONSTRUCTOR也算一个吧。顾名思义,METHOD就是作用在方法上,TYPE就是类上,FIELD就是属性上。
来,我们添加元注解@Target(ElementType.TYPE)
现在可以看到应用在属性或者方法上都是会报错的。