自定义注解及应用

自定义注解及应用

************ 如有侵权请提示删除 ***************



1.注解的概念

注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。

2.注解的使用范围

注解又许多用法,其中有:【为编译器提供信息】 - 注解能被编译器检测到错误或抑制警告。【编译时和部署时】的处理 - 软件工具能处理注解信息从而生成代码,XML文件等等。【运行时的处理】 - 有些注解在运行时能被检测到。

3.基本语法

  1. 关键字@interface,
    在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
  2. 属性格式
    数据类型 属性名() [default 默认值];
    1. 访问修饰符必须为public,不写默认为public;
    2. 元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
    3. 元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(在引用时可以省略value直接给值);
    4. ()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
    5. default代表默认值,值必须和定义的类型一致;
    6. 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
  3. 注解的属性的类型:
    基本数据类型:
    String类型:
    Class类型:
    注解类型:
    枚举类型:
    以上类型的一维数组:

4.元注解

专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的
例如:

  • @Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    /** 类,接口(包括注解类型)或枚举的声明 */
    TYPE,

    /** Field declaration (includes enum constants) */
    /** 属性的声明 */
    FIELD,

    /** Method declaration */
     /** 方法的声明 */
    METHOD,

    /** Formal parameter declaration */
    /** 方法形式参数声明 */
    PARAMETER,

    /** Constructor declaration */
     /** 构造方法的声明 */
    CONSTRUCTOR,

    /** Local variable declaration */
    /** 局部变量声明 */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
     /** 注解类型声明 */
    ANNOTATION_TYPE,

    /** Package declaration */
    /** 包的声明 */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}
  • @Retention翻译为持久力、保持力。即用来修饰自定义注解的生命力
    注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。同样使用了RetentionPolicy枚举类型定义了三个阶段:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}
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
}

再详解一下:

1.如果一个注解被定义为RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;

2.如果一个注解被定义为RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到;

3.如果一个注解被定义为RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME;

4.在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS。

  • @Documented
    是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

  • @Inherited
    是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。

5.自定义注解的配置使用

a.在方法上的应用
  • 第一步,定义注解——相当于定义标记;
  • 第二步,配置注解——把标记打在需要用到的程序代码中;
  • 第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。
/**
 * @Description  新建自定义注解,新建注解与新建接口类似,将interface改为@interface即可。
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2019-09-25
 */
@Target({ElementType.METHOD}) //定义注解修饰的目标,方法
@Retention(RetentionPolicy.RUNTIME) //定义注解的生命周期(SOURCE源码级别,CLASS编译期级别,RUNTIME运行期级别)
public @interface DoneTime{
    //数据类型 属性名() [default 默认值];
    String param() default "";
}
/**
 * @Description  创建自定义注解对应切面
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2019-09-25
 */
@Aspect
@Component
public class DoneTimeAspect {
    @Around("@annotation(doneTime)")  //对指定注解使用环绕通知
    public Object around(ProceedingJoinPoint joinPoint, DoneTime doneTime) throws Throwable {
        System.out.println("方法开始时间是:"+new Date());
        Object o = joinPoint.proceed();
        System.out.println("方法结束时间是:"+new Date()) ;
        return o;
    }
}

/**
 * @Description
 * @Author 
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2019-09-25
 */
@Api(value = "aop", description = "aop测试样例文档")
@Controller
@RequestMapping("/aop")
@Slf4j
@Validated
public class IndexController {

    @GetMapping("/index")
    @DoneTime(param = "IndexController")
    public String index(){
        System.out.println("方法执行");
        return "hello guodegang";
    }
}

ps:

  1. 自定义注解DoneTime,属性param,默认为空字符串
  2. 创建自定义注解对应切面,当使用DoneTime注解的方法调用时,会触发方法调用。
  3. 定义一个测试接口,测试注解的作用
    打印效果:
方法开始时间是:Fri Feb 05 15:52:06 CST 2021
方法执行
方法结束时间是:Fri Feb 05 15:52:07 CST 2021
b.特殊语法

特殊语法一:
如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!

@Target(value = {ElementType.TYPE})//定义注解修饰的目标--类
@Retention(RetentionPolicy.RUNTIME) //定义注解的生命周期(SOURCE源码级别,CLASS编译期级别,RUNTIME运行期级别)
public @interface DoneTime{
}
//等效于@DoneTime()
@DoneTime
public class JavaBean{
	//省略实现部分
}

特殊语法二:
如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)

@Target(value = {ElementType.TYPE})//定义注解修饰的目标--类
@Retention(RetentionPolicy.RUNTIME) //定义注解的生命周期(SOURCE源码级别,CLASS编译期级别,RUNTIME运行期级别)
public @interface DoneTime{
    String value();
}
//等效于@DoneTime(value = "this is annotation")
@DoneTime("this is annotation")
public class JavaBean{
	//省略实现部分
}

特殊用法三:
如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!

@Target(value = {ElementType.TYPE})//定义注解修饰的目标--类
@Retention(RetentionPolicy.RUNTIME) //定义注解的生命周期(SOURCE源码级别,CLASS编译期级别,RUNTIME运行期级别)
public @interface DoneTime{
    String[] name;;
}
//等效于@DoneTime(name = {"this is annotation"})
@DoneTime(name = "this is annotation")
public class JavaBean{
	//省略实现部分
}

特殊用法四:

如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。

6.自定义注解的运行时解析

这波学习注解的原理,主要是想了解如何在程序运行时检测到注解,并进行一系列特殊操作!

不多说,来个栗子!!!


/**
 * @Description  新建自定义注解,新建注解与新建接口类似,将interface改为@interface即可。
 * @Author zhoumm
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2019-09-25
 */
@Target({ElementType.METHOD}) //定义注解修饰的目标,方法
@Retention(RetentionPolicy.RUNTIME) //定义注解的生命周期(SOURCE源码级别,CLASS编译期级别,RUNTIME运行期级别)
public @interface DoneTime{
    //数据类型 属性名() [default 默认值];
    String param() default "";
}
public class Student {
    @DoneTime(param = "学习时间到了")
    public void study(int times){
        for(int i = 0; i < times; i++){
            System.out.println("Good Good Study, Day Day Up!");
        }
    }
}
public class TestAnnotation {
    public static void main(String[] args){
        try {
            //获取Student的Class对象
            Class stuClass = Class.forName("com.mmz.tkp.controller.aoptest.annotation.Student");

            //说明一下,这里形参不能写成Integer.class,应写为int.class
            Method stuMethod = stuClass.getMethod("study",int.class);

            if(stuMethod.isAnnotationPresent(DoneTime.class)){
                System.out.println("Student类上配置了DoneTime注解!");
                //获取该元素上指定类型的注解
                DoneTime doneTime = stuMethod.getAnnotation(DoneTime.class);
                System.out.println("param: " + doneTime.param());
            }else{
                System.out.println("Student类上没有配置DoneTime注解!");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

ps:

  • 1.如果我们要获得的注解是配置在方法上的,那么我们要从Method对象上获取;如果是配置在属性上,就需要从该属性对应的Field对象上去获取,如果是配置在类型上,需要从Class对象上去获取。总之在谁身上,就从谁身上去获取!
  • 2. isAnnotationPresent(Class<? extends Annotation> annotationClass) 方法是专门判断该元素上是否配置有某个指定的注解;
  • 3. getAnnotation(Class annotationClass) 方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据;
  • 4.反射对象上还有一个方法getAnnotations(),该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组,需要注意的是该数组的类型是Annotation类型,这个Annotation是一个来自于java.lang.annotation包的接口。

参考:
https://blog.csdn.net/xsp_happyboy/article/details/80987484

7.注解属性的应用

@Target({TYPE, METHOD, FIELD, CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestA {
	String name();

	int id() default 0;

	Class gid();
}
@TestA(name = "type", gid = Long.class) //类成员注解
public class UserAnnotation {
	@TestA(name = "param", id = 1, gid = Long.class) //类成员注解
	private Integer age;

	@TestA(name = "construct", id = 2, gid = Long.class)//构造方法注解
	public UserAnnotation() {
	}

	@TestA(name = "public method", id = 3, gid = Long.class) //类方法注解
	public void a() {
		Map m = new HashMap(0);
	}

	@TestA(name = "protected method", id = 4, gid = Long.class) //类方法注解
	protected void b() {
		Map m = new HashMap(0);
	}

	@TestA(name = "private method", id = 5, gid = Long.class) //类方法注解
	private void c() {
		Map m = new HashMap(0);
	}

	public void b(Integer a) {
	}
}
public class ParseAnnotation {

	public static void parseTypeAnnotation() throws ClassNotFoundException {
		Class clazz = Class.forName("com.mmz.tkp.controller.aoptest.useannotation.UserAnnotation");

		Annotation[] annotations = clazz.getAnnotations();
		for (Annotation annotation : annotations) {
			TestA testA = (TestA) annotation;
			System.out.println("id= "+testA.id()+"; name= "+testA.name()+"; gid = " + testA.gid());
		}
	}

	public static void parseMethodAnnotation() {
		Method[] methods = UserAnnotation.class.getDeclaredMethods();
		for (Method method : methods) {

			boolean hasAnnotation = method.isAnnotationPresent(TestA.class);
			if (hasAnnotation) {

				TestA annotation = method.getAnnotation(TestA.class);
				System.out.println("method = " + method.getName()
						+ " ; id = " + annotation.id() + " ; description = "
						+ annotation.name() + "; gid= " + annotation.gid());
			}
		}
	}

	public static void parseConstructAnnotation() {
		Constructor[] constructors = UserAnnotation.class.getConstructors();
		for (Constructor constructor : constructors) {

			boolean hasAnnotation = constructor.isAnnotationPresent(TestA.class);
			if (hasAnnotation) {

				TestA annotation = (TestA) constructor.getAnnotation(TestA.class);
				System.out.println("constructor = " + constructor.getName()
						+ " ; id = " + annotation.id() + " ; description = "
						+ annotation.name() + "; gid= " + annotation.gid());
			}
		}
	}

	public static void main(String[] args) throws ClassNotFoundException {
		parseTypeAnnotation();
		parseMethodAnnotation();
		parseConstructAnnotation();
	}
}

打印

id= 0; name= type; gid = class java.lang.Long
method = c ; id = 5 ; description = private method; gid= class java.lang.Long
method = b ; id = 4 ; description = protected method; gid= class java.lang.Long
method = a ; id = 3 ; description = public method; gid= class java.lang.Long
constructor = com.mmz.tkp.controller.aoptest.useannotation.UserAnnotation ; id = 2 ; description = construct; gid= class java.lang.Long

参考:
https://blog.csdn.net/xsp_happyboy/article/details/80987484

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值