基本注解
在介绍完了元注解后,接下来了解一下Java中默认提供的5个基本注解,它们存在于java.lang包下:@Deprecated、@FunctionalInterface、@Override、@SafeVarargs和@SuppressWarnings。
![820ccd64fed9796415e5fb884366995f.png](https://i-blog.csdnimg.cn/blog_migrate/ded4dce0b85df33b1c069053b26cb22c.jpeg)
请注意不要和java.lang.annotation包下的6个元注解搞混淆了:
![5d80ec0d46e7b9df966703d61760d368.png](https://i-blog.csdnimg.cn/blog_migrate/6aa3dd71a23ea7c2f403dd4faead7982.jpeg)
@Deprecated注解
首先查看一下@Deprecated注解的源码信息,如下所示:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})public @interface Deprecated {}
可以看到@Deprecated注解自jdk1.5引入,它用来标记过时的元素,这个注解在日常开发中会经常碰到。如果编译器在编译阶段遇到这个注解时会自动发出提醒警告,用于告诉开发人员正在调用一个过时的元素,如过时的方法、过时的类、过时的成员变量等(可以参看它的Target注解)。
举个例子,新建一个Envy类,里面的代码为:
public class Envy { @Deprecated public void hello(){ System.out.println("hello"); } public void world(){ System.out.println("world"); } public static void main(String[] args){ new Envy().hello(); new Envy().world(); }}
可以看到目前在该hello方法山添加了@Deprecated注解,然后在main方法内新建一个Envy对象去调用这个hello方法,可以发现编译器此时自动给该方法中间添加了横线:
![df2b41128bf1c54116bec28f806ceecf.png](https://i-blog.csdnimg.cn/blog_migrate/53b6a48c6b9206744315746a0256a27c.jpeg)
注意这个效果是编译器添加的,并不是程序具有的效果。注意过时方法不是不能使用,而是不建议使用。
@FunctionalInterface注解
首先查看一下@FunctionalInterface注解的源码信息,如下所示:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface FunctionalInterface {}
可以看到@FunctionalInterface注解自jdk1.8引入,它是一个函数式接口注解,这是1.8的一个新特性。所谓的函数式接口 (Functional Interface) 其实就是一个只具有一个方法的普通接口。看到这里,笔者立马就想到了多进程中的Runnable接口,它就是一个只含有run方法的函数式接口:
@FunctionalInterfacepublic interface Runnable { public abstract void run();}
此时你可能有一个疑惑,函数式接口有什么用,等下一篇介绍Lambda表达式的时候就会深深体会到它的用处。
@Override注解
首先查看一下@Override注解的源码信息,如下所示:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.SOURCE)public @interface Override {}
可以看到@Override注解自jdk1.5引入,它是一个重写注解,这是使用最多的注解。当某个方法添加@Override注解后,编译器会去检查该方法是实现父类的对应方法,可以避免一些低级错误,如单词拼写错误等。举个例子,这里有一个Movie类:
public class Movie { public void watch(){ System.out.println("watch"); }}
然后有一个ActionMovie类,该类继承了Movie类,并实现了其中的watch方法,但是由于粗心将watch写成了wath,此时编译器就会抛出提示信息:
![1c637d76fed39edb42078ee0d4875fc4.png](https://i-blog.csdnimg.cn/blog_migrate/2152b59caaa39f9b8786546aabb2cdfb.jpeg)
根据这个信息就能知道出错原因是方法单词拼错了。
@SafeVarargs注解
首先查看一下@SafeVarargs注解的源码信息,如下所示:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})public @interface SafeVarargs {}
可以看到@SafeVarargs注解自jdk1.7引入,它是一个参数安全类型注解,大家也称之为Java 7“堆污染”警告,所谓的堆污染其实就是把一个非泛型集合赋值给一个泛型集合的时候,这时就很容易发生堆污染。如果使用@SafeVarargs注解后,它会提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生unchecked这样的警告。这个用的不是很多。
@SuppressWarnings注解
首先查看一下@SuppressWarnings注解的源码信息,如下所示:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})@Retention(RetentionPolicy.SOURCE)public @interface SuppressWarnings { String[] value();}
可以看到@SuppressWarnings注解自jdk1.5引入,它是一个抑制编译器警告注解。前面说过调用被@Deprecated注解修饰的方法后,编译器会发出提醒,如果开发者想忽略这种警告,那么可以在调用时添加@SuppressWarnings注解来实现这个目的:
public class Envy { @Deprecated public void hello(){ System.out.println("hello"); } public void world(){ System.out.println("world"); } @SuppressWarnings("deprecation") public static void main(String[] args){ new Envy().hello(); new Envy().world(); }}
这样编辑器就不会去检测hello方法是否过期。同样通过一张图片来总结一下这5个基本注解:
![eb1dc09a53669c2fc585c272c48ecf30.png](https://i-blog.csdnimg.cn/blog_migrate/d3fe233725d22dd34e5541ddcce2ff5c.jpeg)
自定义注解的使用
将自定义信息注入到方法中
在前面介绍了如何在运行时获取注解中的信息,接下来介绍如何将自定义信息注入到方法中。这里就不再自定义获取注解中信息的方法了,而是直接使用内置的方法。自定义一个@EnvyThink注解,里面的代码为:
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})public @interface EnvyThink { String name() default "envy"; int age() default 22;}
接着新建一个Envy类,其中的代码为:
public class Envy { @EnvyThink() public void hey(String name,int age){ System.out.println("name--->"+name+""+ "age--->"+age); }}
然后新建一个测试类EnvyTest,里面的代码为:
public class EnvyTest { public static void main(String[] args){ try { /**第一步,通过反射得到该类被注解修饰的方法*/ Class> aClass = Envy.class; Method method = aClass.getDeclaredMethod("hey",String.class,int.class); /**第二步,通过该方法来得到注解的信息*/ EnvyThink envyThink = method.getAnnotation(EnvyThink.class); String name = envyThink.name(); int age = envyThink.age(); /**第三步,将注解的信息注入到方法内*/ Object object = aClass.newInstance(); method.invoke(object,name,age); } catch (Exception e) { e.printStackTrace(); } }}
然后运行该方法,可以知道该方法的结果为:
name--->envyage--->22
将自定义对象注入到方法中
可以看到前面注入的是注解内自定义的成员属性,接下来尝试将自定义对象注入到方法中。首先解释一下什么是将自定义对象注入到方法中?举一个例子,首先定义Book实体类,里面的代码为:
public class Book { private String name; private double price; public Book(){}; public Book(String name,double price){ this.name=name; this.price=price; } //getter/setter/toString方法}
其次再定义一个Author类,里面的代码为:
public class Author { private Book book; public Book getBook(){ return book; } public void setBook(Book book){ this.book = book; }}
大家肯定知道这个setBook方法内的参数book对象其实是外界传进来的,所以如果需要调用这个方法则必须使用类似如下操作:
public class AuthorTest { public static void main(String[] args){ new Author().setBook(new Book("三国演义",128)); }}
现在想通过注解的方式来将该Book对象注入到Author中,也就是如下方式:
@BookAnno(name = "三国演义", price = 128)public void setBook(Book book){ this.book = book;}
应该如何实现呢?往下看,首先定义一个@BookAnno注解,里面定义两个属性name和price(注意属性数量和名称必须与Book完全保持一致),里面的代码为:
@Retention(RetentionPolicy.RUNTIME)public @interface BookAnno { String name() default "三国演义"; double price() default 128;}
注意该注解必须添加@Retention(RetentionPolicy.RUNTIME)注解语句,否则无法通过反射实现相应的功能。
接着定义一个AuthorAnnoTest类,用于实现将Book对象注入到Author的setBook方法中。在该类中,需要通过内省(Introspector)来对JavaBean类属性、事件进行缺省处理。关于内省的相关介绍,后续会专门出一篇文章进行说明。AuthorAnnoTest类中的代码为:
public class AuthorAnnoTest { public static void main(String[] args) { try { /** 1.使用内省机制来获取想要注入的属性 */ /** 第一个参数为待注入属性,第二个参数为该属性所依附的类 */ PropertyDescriptor descriptor = new PropertyDescriptor("book", Author.class); /** 2.获取该属性的setPerson方法(写方法)*/ Method method = descriptor.getWriteMethod(); /** 3.获取写方法的注解*/ Annotation annotation = method.getAnnotation(BookAnno.class); /** 4.获取注解上的信息(注意必须是方法,因为注解内成员变量的定义就是采用方法形式)*/ Method[] methods = annotation.getClass().getMethods(); /** 5.获取待注入属性的实例对象*/ Book book = (Book) descriptor.getPropertyType().newInstance(); /** 6.将注解上的信息添加到book对象上*/ for (Method meth : methods) { /** 7.获取注解内相关属性的名称(注意这里的name不仅仅只是name属性,而是一个属性别称) */ String name = meth.getName(); /** 8.判断Book对象中是否存在各属性对应的setter方法*/ try { /** 8.1.1 假设存在相对应的setter方法,则采用内省机制来获取对应的setter方法*/ PropertyDescriptor descriptor1 = new PropertyDescriptor(name, Book.class); /** 8.1.2 获取各属性相对应的setter写方法 */ Method method1 = descriptor1.getWriteMethod(); /** 8.1.3 获取注解中的值*/ Object o = meth.invoke(annotation, null); /** 8.1.4 调用Book对象的setter写方法,将前面获取的注解的值设置进去*/ method1.invoke(book, o); } catch (Exception e) { /** 8.2 如果Book对象中不存在各属性对应的setter方法,那么会跳过当前循环,继续遍历注解*/ continue; } } /** 9、遍历完成,book对象中的各个setter方法已经成功写入了注解中的值*/ /** 9.1 通过setter方法将book对象写入到author对象中*/ Author author = new Author(); method.invoke(author, book); /** 10.输出author对象中的name和price的信息 */ System.out.println(author.getBook().getName()); System.out.println(author.getBook().getPrice()); } catch (Exception e) { e.printStackTrace(); } }}
运行该方法,可以得到输出结果为:
三国演义128.0
接下来总结一下该步骤:1、使用内省机制来获取想要注入的属性;2、获取该属性的写方法;3、获取写方法的注解;4、获取注解上的信息(注意必须是方法,因为注解内成员变量的定义就是采用方法形式);5、获取待注入属性的实例对象;6、将注解上的信息添加到对象上;7、调用属性的写方法将已添加数据的对象注入到方法中;8、验证对象是否成功注入方法中。
Spring内置的@Autowried注解就是完成了类似的功能才使得用户可以直接在方法中就能使用对象。
将自定义对象注入到属性中
将自定义对象注入到属性中,这也是一个常见的使用场景,通过前面的介绍,开发者可以尝试照葫芦画瓢来实现这一功能。也就是当开发者使用如下方式:
public class Author { @BookAnno(name = "三国演义", price = 128) private Book book; public Book getBook(){ return book; } public void setBook(Book book){ this.book = book; }}
的代码配置时,我们依然可以像前面那样在author对象中获取注解的值。接着定义一个AuthorAnnoFiledTest类,用于实现将Book对象注入到Author的book属性中。需要注意的是首先需要使用反射机制来获取对应的属性,其次使用内省机制来获取对应的写方法。AuthorAnnoFiledTest类中的代码为:
public class AuthorAnnoFiledTest { public static void main(String[] args){ try { /** 1.使用反射机制来获取想要注入的属性 */ Field field = Author.class.getDeclaredField("book"); /** 2.获取属性的注解*/ Annotation annotation = field.getAnnotation(BookAnno.class); /** 3.获取注解上的信息(注意必须是方法,因为注解内成员变量的定义就是采用方法形式)*/ Method[] methods = annotation.getClass().getMethods(); /** 4.获取待注入属性的实例对象*/ Book book = (Book) field.getType().newInstance(); /** 5.将注解上的信息添加到book对象上*/ for (Method meth : methods) { /** 6.获取注解内相关属性的名称(注意这里的name不仅仅只是name属性,而是一个属性别称) */ String name = meth.getName(); /** 7.判断Book对象中是否存在各属性对应的setter方法*/ try { /** 7.1.1 假设存在相对应的setter方法,则采用内省机制来获取对应的setter方法*/ PropertyDescriptor descriptor = new PropertyDescriptor(name, Book.class); /** 7.1.2 获取各属性相对应的setter写方法 */ Method method1 = descriptor.getWriteMethod(); /** 7.1.3 获取注解中的值*/ Object o = meth.invoke(annotation, null); /** 7.1.4 调用Book对象的setter写方法,将前面获取的注解的值设置进去*/ method1.invoke(book, o); } catch (Exception e) { /** 7.2 如果Book对象中不存在各属性对应的setter方法,那么会跳过当前循环,继续遍历注解*/ continue; } } /** 8、遍历完成,book对象中的各个setter方法已经成功写入了注解中的值*/ /** 8.1 通过setter方法将book对象写入到author对象中*/ Author author = new Author(); field.setAccessible(true); field.set(author,book); /** 10.输出author对象中的name和price的信息 */ System.out.println(author.getBook().getName()); System.out.println(author.getBook().getPrice()); } catch (Exception e) { e.printStackTrace(); } }}
运行该方法,可以得到输出结果为:
三国演义128.0
接下来总结一下该步骤:1、使用反射机制来获取想要注入的属性;2、获取该属性的注解;3、获取注解上的信息(注意必须是方法,因为注解内成员变量的定义就是采用方法形式);4、获取待注入属性的实例对象;5、将注解上的信息添加到对象上;6、调用属性的写方法将已添加数据的对象注入到方法中;7、验证对象是否成功注入方法中。
注解总结
接下来对注解进行总结,注解其实就两个作用:(1)在适当的时候将数据注入到方法、成员变量、类或者其他组件中;(2)让编译器检查代码,进而发出提示信息。关于注解就先介绍到这里,后续会出一篇文章来总结Spring框架中常用的注解。
![1f59faaa6007842f9293bd0630d74cb5.png](https://i-blog.csdnimg.cn/blog_migrate/74d2b26861cc62e739282fc71b51d918.jpeg)