对于注解相信你们都不陌生,由于初学者第一个注解就是@Override
,用于标识重载方法。在Java EE开发过程当中,注解更是无处不在,像经典的MVC设计模式就至少使用到了4个注解:@Component
、@Repository
、@Service
和@Controller
。如今问题来了,为何要学习注解?它有什么优势,能解决什么问题?经过阅读本篇文章相信读者会有一个比较清晰的认识。html
在Java Web开发中,最先是使用XML的配置方式。举个例子来讲,当开发者在Servlet中定义了LoginServlet类,接下来应该是去web.xml配置文件中增长LoginServlet类的访问映射,可能会使用以下代码:java
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.envy.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/LoginServlet</url-pattern>
</servlet-mapping>
复制代码
这样当用户访问诸如http://localhost:8080/LoginServlet
连接时,就会执行这个类中定义的方法逻辑。可是针对这种配置方式,有人以为这种太麻烦了,因而提出了一种新的配置方式:在LoginServlet类上添加@WebServlet
注解,并结合value="/LoginServlet"
这种属性值的方式也实现了一样的目的,毫无疑问后面这种方式使用起来更加方便,而这种方式就是今天的主角---注解。web
Java注解(Annotation)自Java1.5引入,用于描述Java代码的元信息,一般状况下注解不会直接影响代码的执行,但某些注解能够用来影响代码的执行。初学者可能被这句话给搞晕了,什么是元信息,一下子不会影响代码的执行,一下子又能够影响代码的执行。其实不用着急,相信经过本文的学习,你必定会对这句话有新的理解。编程
其实注解就像是代码中的特殊标记,这些特殊标记能够在类编译、加载、运行时被读取,并执行对应的逻辑。设计模式
注解虽然不像class和interface那样,自一开始就出如今你们的眼前,可是自从它诞生以来,它的地位却在一直提高。注解也是一种类型,所以它和class、interface同样值得被人记住。数组
定义注解很是简单,只需使用@interface
关键词声明便可,好比下面就定义了一个属于开发者本身的注解@MyAnnotation
:浏览器
public @interface MyAnnotation {
}
复制代码
是否是以为和接口很是类似,仅仅在前面添加了一个@
符号而已。那么这个注解如何使用呢?先定义一个Hello类,而后将自定义的@MyAnnotation
添加到这个Hello类上面这样就完成了一个最简单的注解使用案例,里面的代码为:安全
@MyAnnotation
public class Hello {
}
复制代码
**因为自定义的@MyAnnotation
内不包含任何成员变量,所以该注解被称为标记注解,最多见的标记注解就是@Overried
。**可是实际上注解内每每是须要定义成员变量的,所以须要学习另外一个概念:元注解。元注解是对注解进行注解的注解,用于解释说明被做用的注解的做用,所以它是一种很是基本的注解。bash
在JDK的java.lang.annotation
包下定义了6个元注解:@Documented
、@Inherited
、@Native
、@Repeatable
、@Retention
和@Target
,以下图所示:微信
后续会依次解释一下这6个元注解的做用,了解和熟悉它们对于提高编程水平有极大的帮助。
不过在此以前先学习一下带有成员变量的注解,一般称之为元数据。在注解中定义成员变量,它的语法很是相似于方法的声明:
public @interface MyAnnotation {
String username();
String password();
}
复制代码
这样就在以前自定义的MyAnnotation注解内添加了两个成员变量,username和password。请注意在注解上定义的成员变量只能是String、数组、Class、枚举类和注解。
在注解中定义成员变量实际上是为了携带信息,一般在XML中可能一个标签中包含子标签:
<博客>
<做者>余思</做者>
<网址>www.envyzhan.club</网址>
</博客>
复制代码
子标签就是一些信息,而这里在注解中定义的成员变量其实就至关于子标签。
不过因为前面的自定义注解@MyAnnotation
中定义的成员变量没有默认值,所以在使用的时候须要添加默认值:
@MyAnnotation(username = "envy",password = "1234")
public void test(){
}
复制代码
一般状况下会在声明一个注解的时候,给予它的成员变量一个默认值,这样便于后续使用:
public @interface MyAnnotation {
String username() default "envy";
String password() default "1234";
}
复制代码
这样在使用的时候能够根据实际状况来选择是否修改注解的默认值:
@MyAnnotation()
public void test(){
}
复制代码
注意这里面存在一个特殊状况:**当一个注解内只存在一个成员属性,且属性值为value,那么开发者能够在声明该注解的时候不给予它默认值,且在使用的时候不须要指出value属性,而是直接赋值便可。**举一个例子来讲,如今有一个注解@MyAnnotation
,它的内部结构为:
public @interface MyAnnotation {
String value();
}
复制代码
那么使用的时候能够采起以下的方式进行:
@MyAnnotation("envy")
public void test(){
}
复制代码
注意这里的写法是@MyAnnotation("envy")
,固然也能够采用@MyAnnotation(value="envy")
的通用写法,可是不建议这么作,由于这自己就是一个语法糖而已。那有人以为是否是只要一个注解内只要知足只要一个成员属性这一条件就能够呢?往下看,这里定义了一个@OneAnnotation
,它的内部结构为:
public @interface OneAnnotation {
String name();
}
复制代码
而后使用的时候也按照语法糖来写,那是会报错的,只要属性值为value才能够,这一点须要引发特别注意:
首先查看一下@Documented
注解的源码信息,以下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
复制代码
能够看到@Documented
注解自jdk1.5引入,顾名思义@Documented
注解是一个用于修饰的注解,被此注解修饰的注解能够被javadoc等工具文档化,注意它只负责标记,没有成员取值。一般javadoc中不包括注解,可是当某个注解被@Documented
修饰时,那么它会被 javadoc工具处理,以后该注解类型信息会存在于生成的文档中。
举一个例子,前面自定义了一个注解@MyAnnotation
,如今尝试在该注解上添加@Documented注解,
@Documented
public @interface MyAnnotation {
}
复制代码
而后新建一个Hello.java文件,在该类上使用该注解:
@MyAnnotation
public class Hello {
}
复制代码
接着进入dos命令行,切换到该注解所在的文件夹,而后执行javadoc -d doc *.java
,该命令的做用是使用javadoc工具生成一个对该目录下全部java文件的说明文档。运行命令后能够发现当前目录下多了一个doc目录,进入该目录,而后使用浏览器打开其中的index.html
文件:
能够发现这个文档中出现了对注释类型的说明(注解实际上是一种类型,成为注释类型,可是一般你们都称之为注解)
如今尝试删除这个doc目录,且去掉MyAnnotation注释上的@Documented
注解,再来运行一下上述命令,能够发现此时生成的index.html
文件中就没有对注释类型的说明了:
这就是@Documented
注解的使用方法,继续下一个注解。
首先查看一下@Native
注解的源码信息,以下所示:
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}
复制代码
能够看到@Native
注解自jdk1.8引入,用于注释该字段是一个常量,其值引用native code
,能够发现它的保留时间为SOURCE阶段,这个用的不是不少。
首先查看一下@Target
注解的源码信息,以下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
复制代码
能够看到@Target
注解自jdk1.5引入,用于指明被修饰注解的使用范围,即用来描述被修饰的注解能够在哪里使用。定义在ElementType
枚举类中,一共有10种策略(1.8以前存在前8种,jdk1.8新增后2种):
枚举常量 | 注解使用范围 |
---|---|
TYPE | 类,接口(包括注解类型),和枚举 |
FIELD | 字段(包括枚举常量) |
METHOD | 方法 |
PARAMETER | 形式参数 |
CONSTRUCTOR | 构造函数 |
LOCAL_VARIABLE | 局部变量 |
ANNOTATION_TYPE | 注解类型 |
PACKAGE | 包 |
TYPE_PARAMETER (jdk1.8引入) | 类型参数 |
TYPE_USE (jdk1.8引入) | 使用类型 |
请注意这里的PACKAGE,不是使用在通常类中,而是用在固定的package-info.java
文件中,注意名称必须是package-info
不能修改。举个例子来讲,新建一个WeAnnotation
注解,其中的代码为:
@Target({ElementType.PACKAGE,ElementType.METHOD})
@Documented
public @interface WeAnnotation {
}
复制代码
接着再建立一个package-info.java
文件,注意必须使用相似于文件建立的方式,不然IDEA不容许建立:
@WeAnnotation
package com.envy.annotation;
复制代码
这样就成功的使用了该注解。请注意在@Target
注解内定义的成员变量是一个数组,所以必须使用诸如@Target({ElementType.PACKAGE})
形式:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
复制代码
首先查看一下@Retention
注解的源码信息,以下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
复制代码
能够看到@Retention
注解自jdk1.5引入,用于指明被修饰注解的保留时间。定义在RetentionPolicy
枚举类中,一共有三种策略:SOURCE、CLASS和RUNTIME。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
复制代码
SOURCE,顾名思义就是源码阶段,该注解只存在于.java源文件中,当.Java文件编译成.class字节码文件的时候,该注解即被遗弃,被编译器忽略。CLASS,顾名思义就是字节码阶段,该注解保留到.class字节码文件中,当.class字节码文件被jvm加载时即被遗弃,这是默认的生命周期。RUNTIME,注解不只被保存到.class字节码文件中,且被jvm加载后,仍然存在,所以这个时候就有一种很是厉害的技术--反射。
其实这三个生命周期分别对应于三个阶段:.java源文件
---> .class字节码文件
---> 内存中的字节码
。也就是说生命周期长度顺序为SOURCE< CLASS< RUNTIME,所以前者能做用的地方后者必定也能做用。
通常的,若是开发者想要作一些检查性的操做,如是否方法重载,禁止编译器警告等,能够选择SOURCE
生命周期。若是想要在编译时进行一些预处理操做,如生成一些辅助代码等,能够选择CLASS
生命周期。若是想要在运行时去动态获取注解信息,那此时必须选择RUNTIME
生命周期。
**反射技术demo演示@Retention
和@Target
注解使用。**前面提到的反射技术就是在运行时动态获取类的相关信息,下面就结合反射来介绍如何使用@Retention
和@Target
注解,便于demo演示,这里仅仅获取类的经常使用信息,如Class、Field和Method 。
**第一步,新建3个自定义注解。**新建myannotation包,并在其中依次建立3个自定义运行时注解,且做用范围依次为Class、Field和Method:
//ClassAnno.java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ClassAnno {
String value();
}
//MethodAnno.java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAnno {
String name() default "envy";
String data();
int age() default 22;
}
//FieldAnno.java
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnno {
int[] value();
}
复制代码
**第二步,新建测试类RunTimeTest。**新建RunTimeTest
测试类,其中的代码为:
@ClassAnno("RunTime")
public class RunTimeTest {
@FieldAnno(value = {66,88})
public String word = "hello";
@FieldAnno(value = {1024})
public int number = 2018;
@MethodAnno(data = "good",name="envy", age = 22)
public static void sayHello(){
System.out.println("this is sayHello method.");
}
}
复制代码
**第三步,获取注解内部信息。**新建GetRunTimeTest类,用于运行时获取注解内部信息,相应的代码为:
public class GetRunTimeTest {
public static void outInfo(){
StringBuilder builder = new StringBuilder();
Class<?> aClass = RunTimeTest.class;
/**获取ClassAnno注解信息*/
builder.append("ClassAnno注解:").append("\n");
ClassAnno classAnno = aClass.getAnnotation(ClassAnno.class);
if(classAnno!=null){
builder.append(Modifier.toString(aClass.getModifiers())).append(" ")
.append(aClass.getSimpleName()).append("\n");
builder.append("注解值为:").append(classAnno.value()).append("\n\n");
}
/**获取MethodAnno注解信息*/
builder.append("MethodAnno注解:").append("\n");
Method[] methods = aClass.getDeclaredMethods();
for(Method method:methods){
MethodAnno methodAnno = method.getAnnotation(MethodAnno.class);
if(methodAnno !=null){
builder.append(Modifier.toString(method.getModifiers())).append(" ")
.append(method.getReturnType().getSimpleName()).append(" ")
.append(method.getName()).append("\n");
builder.append("注解值为:").append("\n");
builder.append("name--->").append(methodAnno.name()).append("\n");
builder.append("data--->").append(methodAnno.data()).append("\n");
builder.append("age--->").append(methodAnno.age()).append("\n\n");
}
}
/**获取FieldAnno注解信息*/
builder.append("FieldAnno注解:").append("\n");
Field[] fields = aClass.getDeclaredFields();
for(Field field:fields){
FieldAnno fieldAnno = field.getAnnotation(FieldAnno.class);
if(fieldAnno != null){
builder.append(Modifier.toString(field.getModifiers())).append(" ")
.append(field.getType().getSimpleName()).append(" ")
.append(field.getName()).append("\n");
builder.append("注解值为:").append(Arrays.toString(fieldAnno.value())).append("\n\n");
}
}
System.out.println(builder.toString());
}
public static void main(String[] args){
outInfo();
}
}
复制代码
运行该方法后,结果以下:
ClassAnno注解:
public RunTimeTest
注解值为:RunTime
MethodAnno注解:
public static void sayHello
注解值为:
name--->envy
data--->good
age--->22
FieldAnno注解:
public String word
注解值为:[66, 88]
public int number
注解值为:[1024]
复制代码
能够发现使用反射技术,成功的在程序运行时动态地获取到了注解中的信息,其实在Spring框架内的@Autowried
注解就是起这个做用,在类运行时将须要的Bean注入Spring容器,以供后续程序的调用。
首先查看一下@Inherited
注解的源码信息,以下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
复制代码
能够看到@Inherited
注解自jdk1.5引入,顾名思义@Inherited
注解容许子类继承父类的注解。举个例子来讲,假设你使用@Inherited
注解修饰了自定义的@HeyAnnotation
注解,若是此时自定义的@HeyAnnotation
注解修饰一个类Book后,而又一个TechBook类又继承了Book类,那么此时的TechBook类则默认也拥有你自定义的@HeyAnnotation
注解。
HeyAnnotation.java注解中的代码:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface HeyAnnotation {
}
复制代码
请注意后续是经过反射来判断子类中是否存在@HeyAnnotation
注解,而注解默认的保留时间为CLASS,这使得该注解没法进入RUNTIME时期,所以必须加上@Retention(RetentionPolicy.RUNTIME)
。接下来是Book.java文件中的代码:
@HeyAnnotation
public class Book {
}
复制代码
而后是Book子类TechBook类中的代码:
public class TechBook extends Book {
public static void main(String[] args){
System.out.println(TechBook.class.isAnnotationPresent(HeyAnnotation.class));
}
}
复制代码
而后运行该main方法,能够发现输出结果为true。
首先查看一下@Repeatable
注解的源码信息,以下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
Class<? extends Annotation> value();
}
复制代码
能够看到@Repeatable
注解自jdk1.8引入,Repeatable是重复的意思,所以被该注解修饰的注解能够屡次应用于相同的声明或类型中。那么什么样的注解能够被屡次使用呢?固然是注解的值能够同时取多个了。
举个例子来讲,一个男人多是父亲,多是儿子,也有多是丈夫。首先定义一个@Persons
注解:
public @interface Persons {
Person[] value();
}
复制代码
注意这里面的成员变量Person也是注解,该注解中的代码为:
@Repeatable(Persons.class)
public @interface Person {
String role() default "";
}
复制代码
能够看到在该注解上使用了@Repeatable(Persons.class)
注解,这个其实就至关于一个容器注解,容器注解顾名思义就是存放其余注解的注解,注意容器注解也是一个注解。而后定一个Male类来使用这个@Person
注解,相应的代码为:
@Person(role = "husband")
@Person(role = "son")
@Person(role = "father")
public class Male {
}
复制代码
回过头再来看一下@Persons
注解内的代码:
public @interface Persons {
Person[] value();
}
复制代码
能够看到它的成员变量是一个被@Repeatable(Persons.class)
注解修饰的注解,注意它是一个数组。
接下来经过一张图片来总结一下这6个元注解:
在介绍完了元注解后,接下来了解一下Java中默认提供的5个基本注解,它们存在于java.lang
包下:@Deprecated
、@FunctionalInterface
、@Override
、@SafeVarargs
和@SuppressWarnings
。
请注意不要和java.lang.annotation
包下的6个元注解搞混淆了:
首先查看一下@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方法,能够发现编译器此时自动给该方法中间添加了横线:
注意这个效果是编译器添加的,并非程序具备的效果。注意过期方法不是不能使用,而是不建议使用。
首先查看一下@FunctionalInterface
注解的源码信息,以下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
复制代码
能够看到@FunctionalInterface
注解自jdk1.8引入,它是一个函数式接口注解,这是1.8的一个新特性。所谓的函数式接口 (Functional Interface) 其实就是一个只具备一个方法的普通接口。看到这里,笔者立马就想到了多进程中的Runnable接口,它就是一个只含有run方法的函数式接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
复制代码
此时你可能有一个疑惑,函数式接口有什么用,等下一篇介绍Lambda表达式的时候就会深深体会到它的用处。
首先查看一下@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,此时编译器就会抛出提示信息:
根据这个信息就能知道出错缘由是方法单词拼错了。
首先查看一下@SafeVarargs
注解的源码信息,以下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
复制代码
能够看到@SafeVarargs
注解自jdk1.7引入,它是一个参数安全类型注解,你们也称之为Java 7“堆污染”警告,所谓的堆污染其实就是把一个非泛型集合赋值给一个泛型集合的时候,这时就很容易发生堆污染。若是使用@SafeVarargs
注解后,它会提醒开发者不要用参数作一些不安全的操做,它的存在会阻止编译器产生unchecked
这样的警告。这个用的不是不少。
首先查看一下@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个基本注解:
在前面介绍了如何在运行时获取注解中的信息,接下来介绍如何将自定义信息注入到方法中。这里就再也不自定义获取注解中信息的方法了,而是直接使用内置的方法。自定义一个@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+"\n"+ "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--->envy
age--->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;
}
}
/** 九、遍历完成,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
复制代码
接下来总结一下该步骤: 一、使用内省机制来获取想要注入的属性;二、获取该属性的写方法;三、获取写方法的注解;四、获取注解上的信息(注意必须是方法,由于注解内成员变量的定义就是采用方法形式);五、获取待注入属性的实例对象;六、将注解上的信息添加到对象上;七、调用属性的写方法将已添加数据的对象注入到方法中;八、验证对象是否成功注入方法中。
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;
}
}
/** 八、遍历完成,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)让编译器检查代码,进而发出提示信息。关于注解就先介绍到这里,后续会出一篇文章来总结Spring框架中经常使用的注解。
获取更多内容请关注我的微信公众帐号:余思博客,或者微信扫描下方二维码便可直接关注: