springmvc 自定义注解没生效 spring自定义注解实现_自定义注解

Spring的源码满是注解,理解注解是怎么回事,有助于接下来的理解。

springmvc 自定义注解没生效 spring自定义注解实现_springmvc 自定义注解没生效_02

创建自定义注解

使用IDEA,new Java Class会出现如下对话框,选择Annotation,输入文件名,就创建好了一个自定义注解。

springmvc 自定义注解没生效 spring自定义注解实现_spring源码_03

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,就是咱们自己创建的注解啦。

springmvc 自定义注解没生效 spring自定义注解实现_spring_04

注解的值

对比咱们日常使用到的@Service("carService")你会发现,这个@Service注解带了一个值,咱们能不能带?能。

springmvc 自定义注解没生效 spring自定义注解实现_自定义注解_05

springmvc 自定义注解没生效 spring自定义注解实现_spring_06

但是,很明显,报错了

此时,只需要在注解里写上String value() default "";变成如下这样,就可以了。

public @interface MyLog {
    String value() default "";
}

我想有多个值,例如再增加一个text,String text() default "";

springmvc 自定义注解没生效 spring自定义注解实现_spring自定义注解_07

又报错了。这是因为value是个特殊值,如果只有它自己,是可以省略不写的,但是如果有多个值,那么必须要这么写:

springmvc 自定义注解没生效 spring自定义注解实现_自定义注解_08

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

啥也没有

springmvc 自定义注解没生效 spring自定义注解实现_spring_09

很明显,没有进入到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

终于获取到值了

springmvc 自定义注解没生效 spring自定义注解实现_spring_10

突然又发现,咱现在的注解只用在类上面,那用在方法、属性上行不行呐?

好,现在对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

springmvc 自定义注解没生效 spring自定义注解实现_spring源码_11

哎,我就是想让这个注解只能使用在类上,有没有办法?

有!

元注解@Target

这个注解的作用就是限定注解的使用范围,值有很多,常见的有TYPE、METHOD、FIELD,CONSTRUCTOR也算一个吧。顾名思义,METHOD就是作用在方法上,TYPE就是类上,FIELD就是属性上。

来,我们添加元注解@Target(ElementType.TYPE)

springmvc 自定义注解没生效 spring自定义注解实现_spring_12

现在可以看到应用在属性或者方法上都是会报错的。