前言
今天要讲的是注解, 对于本章节, 最好是有 Servlet 基础的人查阅~因为单纯是 Java 基础的话, 可能用不上注解这个东西但如果开发过 Servlet, 就对 @webServlet 不会陌生
现在的开发都推崇使用注解来进行开发, 这样就可以免去写 XML 配置了, 十分方便的一项技术~
学习注解可以更好地理解注解是怎么工作的, 看见注解了就可以想到它的运行原理了~
如果有错的地方请大家多多包涵并欢迎在评论区指正~
一什么是注解?
注解: Annotation....
注解其实就是代码中的特殊标记, 这些标记可以在编译类加载运行时被读取, 并执行相对应的处理
二为什么我们需要用到注解?
传统的方式, 我们是通过配置文件 (xml 文件) 来告诉类是如何运行的
有了注解技术以后, 我们就可以通过注解告诉类如何运行
例如: 我们以前编写 Servlet 的时候, 需要在 web.xml 文件配置具体的信息
我们使用了注解以后, 可以直接在 Servlet 源代码上, 增加注解...Servlet 就被配置到 Tomcat 上了也就是说, 注解可以给类方法上注入信息
明显地可以看出, 这样是非常直观的, 并且 Servlet 规范是推崇这种配置方式的
三基本 Annotation
在 java.lang 包下存在着 5 个基本的 Annotation, 其中有 3 个 Annotation 我们是非常常见的了
3.1@Overried
重写注解
如果我们使用 IDE 重写父类的方法, 我们就可以看见它了那它有什么用呢??
@Overried 是告诉编译器要检查该方法是实现父类的... 可以帮我们避免一些低级的错误...
比如, 我们在实现 equals()方法的时候, 把 euqals()打错了, 那么编译器就会发现该方法并不是实现父类的, 与注解 @Overried 冲突, 于是就会给予错误
3.2@Deprecated
过时注解
该注解也非常常见, Java 在设计的时候, 可能觉得某些方法设计得不好, 为了兼容以前的程序, 是不能直接把它抛弃的, 于是就设置它为过时
Date 对象中的 toLocalString()就被设置成过时了
- @Deprecated
- public String toLocaleString() {
- DateFormat formatter = DateFormat.getDateTimeInstance();
- return formatter.format(this);
- }
当我们在程序中调用它的时候, 在 IDE 上会出现一条横杠, 说明该方法是过时的
3.3@SuppressWarnings
抑制编译器警告注解
该注解在我们写程序的时候并不是很常见, 我们可以用它来让编译器不给予我们警告
当我们在使用集合的时候, 如果没有指定泛型, 那么会提示安全检查的警告
如果我们在类上添加了 @SuppressWarnings 这个注解, 那么编译器就不会给予我们警告了
3.4@SafeVarargs
Java 7 堆污染警告
什么是堆污染呢?? 当把一个不是泛型的集合赋值给一个带泛型的集合的时候, 这种情况就很容易发生堆污染....
这个注解也是用来抑制编译器警告的注解... 用的地方并不多, 我也不详细说明了...... 有用到的时候再回来填坑吧
- 3.5
- @FunctionalInterface
- @FunctionalInterface
用来指定该接口是函数式接口
用该注解显示指定该接口是一个函数式接口
四自定义注解基础
上面讲解的是 java.lang 包下的 5 个注解, 我们是可以自己来写注解, 给方法或类注入信息
4.1 标记 Annotation
没有任何成员变量的注解称作为标记注解,@Overried 就是一个标记注解
- // 有点像定义一个接口一样, 只不过它多了一个 @
- public @interface MyAnnotation {
- }
4.2 元数据 Annotation
我们自定义的注解是可以带成员变量的, 定义带成员变量的注解叫做元数据 Annotation
在注解中定义成员变量, 语法类似于声明方法一样....
- public @interface MyAnnotation {
- // 定义了两个成员变量
- String username();
- int age();
- }
注意: 在注解上定义的成员变量只能是 String 数组 Class 枚举类注解
有的人可能会奇怪, 为什么注解上还要定义注解成员变量?? 听起来就很复杂了....
上边已经说了, 注解的作用就是给类方法注入信息那么我们经常使用 XML 文件, 告诉程序怎么运行 XML 经常会有嵌套的情况
<书>
<作者 > zhongfucheng</ 作者>
<价钱 > 22222</ 价钱>
</ 书>
那么, 当我们在使用注解的时候, 也可能需要有嵌套的时候, 所以就允许了注解上可以定义成员变量为注解
4.3 使用自定义注解
上面我们已经定义了一个注解了, 下面我们来使用它吧
4.3.1 常规使用
下面我有一个 add 的方法, 需要 username 和 age 参数, 我们通过注解来让该方法拥有这两个变量!
- // 注解拥有什么属性, 在修饰的时候就要给出相对应的值
- @MyAnnotation(username = "zhongfucheng", age = 20)
- public void add(String username, int age) {
- }
4.3.2 默认值
当然啦, 我们可以在注解声明属性的时候, 给出默认值那么在修饰的时候, 就可以不用具体指定了
- public @interface MyAnnotation {
- // 定义了两个成员变量
- String username() default "zicheng";
- int age() default 23;
- }
在修饰的时候就不需要给出具体的值了
- @MyAnnotation()
- public void add(String username, int age) {
- }
4.3.3 注解属性为 value
还有一种特殊的情况, 如果注解上只有一个属性, 并且属性的名称为 value, 那么在使用的时候, 我们可以不写 value, 直接赋值给它就行
- public @interface MyAnnotation2 {
- String value();
- }
使用注解, 可以不指定 value, 直接赋值
- @MyAnnotation2("zhongfucheng")
- public void find(String id) {
- }
4.4 把自定义注解的基本信息注入到方法上
上面我们已经使用到了注解, 但是目前为止注解上的信息和方法上的信息是没有任何关联的
我们使用 Servlet 注解的时候, 仅仅调用注解, 那么注解的就生效了这是 Web 容器把内部实现了我们自己写的自定义注解是需要我们自己来处理的
那现在问题来了, 我们怎么把注解上的信息注入到方法上呢??? 我们利用的是反射技术
步骤可分为三部:
反射出该类的方法
通过方法得到注解上具体的信息
将注解上的信息注入到方法上
- // 反射出该类的方法
- Class aClass = Demo2.class;
- Method method = aClass.getMethod("add", String.class, int.class);
- // 通过该方法得到注解上的具体信息
- MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
- String username = annotation.username();
- int age = annotation.age();
- // 将注解上的信息注入到方法上
- Object o = aClass.newInstance();
- method.invoke(o, username, age);
当我们执行的时候, 我们发现会出现异常...
此时, 我们需要在自定义注解上加入这样一句代码(下面就会讲到, 为什么要加入这句代码)
@Retention(RetentionPolicy.RUNTIME)
再次执行的时候, 我们就会发现, 可以通过注解来把信息注入到方法中了
五 JDK 的元 Annotation
前面我们已经介绍了 java.lang 包下的几个基本 Annotation 了在 JDK 中除了 java.lang 包下有 Annotation, 在 java.lang.annotation 下也有几个常用的元 Annotation
在 annotation 包下的好几个元 Annotation 都是用于修饰其他的 Annotation 定义
5.1@Retention
上面在将注解信息注入到方法中的时候, 我们最后加上了 @Retention 的注解.... 不然就会报错了.. 那它是干什么用的呢?
@Retention 只能用于修饰其他的 Annotation, 用于指定被修饰的 Annotation 被保留多长时间
@Retention 包含了一个 RetentionPolicy 类型的 value 变量, 所以在使用它的时候, 必须要为 value 成员变量赋值
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,
- /**
- * 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.
- *
- * @see java.lang.reflect.AnnotatedElement
- */
- RUNTIME
- }
java 文件有三个时期: 编译, class, 运行 @Retention 默认是 class
前面我们是使用反射来得到注解上的信息的,** 因为 @Retention 默认是 class mailto:**因为@retention默认是class , 而反射是在运行时期来获取信息的 ** 因此就获取不到 Annotation 的信息了于是, 就得在自定义注解上修改它的 RetentionPolicy 值
5.2@Target
@Target 也是只能用于修饰另外的 Annotation, 它用于指定被修饰的 Annotation 用于修饰哪些程序单元
@Target 是只有一个 value 成员变量的, 该成员变量的值是以下的:
- public enum ElementType {
- /** Class, interface (including annotation type), or enum declaration */
- TYPE,
- /** Field declaration (includes enum constants) */
- FIELD,
- /** Method declaration */
- METHOD,
- /** Parameter declaration */
- PARAMETER,
- /** Constructor declaration */
- CONSTRUCTOR,
- /** Local variable declaration */
- LOCAL_VARIABLE,
- /** Annotation type declaration */
- ANNOTATION_TYPE,
- /** Package declaration */
- PACKAGE
- }
如果 @Target 指定的是 ElementType.ANNOTATION_TYPE, 那么该被修饰的 Annotation 只能修饰 Annotaion
5.3@Documented
@Documented 用于指定被该 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档
该元 Annotation 用得挺少的....
5.4@Inherited
@Inherited 也是用来修饰其他的 Annotation 的, 被修饰过的 Annotation 将具有继承性
例子:
@xxx 是我自定义的注解, 我现在使用 @xxx 注解在 Base 类上使用....
使用
@Inherited 修饰 @xxx 注解
当有类继承了 Base 类的时候, 该实现类自动拥有 @xxx 注解
六注入对象到方法或成员变量上
6.1 把对象注入到方法上
前面我们已经可以使用注解将基本的信息注入到方法上了, 现在我们要使用的是将对象注入到方法上.....
上边已经说过了, 注解上只能定义 String 枚举类 Double 之类的成员变量, 那怎么把对象注入到方法上呢?
6.1.2 模拟场景:
Person 类, 定义 username 和 age 属性, 拥有 uername 和 age 的 getter 和 setter 方法
- public class Person {
- private String username;
- private int age;
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- }
PersonDao 类, PersonDao 类定义了 Person 对象, 拥有 person 的 setter 和 getter 方法
- public class PersonDao {
- private Person person;
- public Person getPerson() {
- return person;
- }
- public void setPerson(Person person) {
- this.person = person;
- }
- }
现在我要做的就是: 使用注解将 Person 对象注入到 setPerson()方法中, 从而设置了 PersonDao 类的 person 属性
- public class PersonDao {
- private Person person;
- public Person getPerson() {
- return person;
- }
- // 将 username 为 zhongfucheng,age 为 20 的 Person 对象注入到 setPerson 方法中
- @InjectPerson(username = "zhongfucheng",age = 20)
- public void setPerson(Person person) {
- this.person = person;
- }
- }
步骤:
: 自定义一个注解, 属性是和 JavaBean 类一致的
- // 注入工具是通过反射来得到注解的信息的, 于是保留域必须使用 RunTime
- @Retention(RetentionPolicy.RUNTIME)
- public @interface InjectPerson {
- String username();
- int age();
- }
: 编写注入工具
- //1. 使用内省后边需要得到属性的写方法, 得到想要注入的属性
- PropertyDescriptor descriptor = new PropertyDescriptor("person", PersonDao.class);
- //2. 得到要想注入属性的具体对象
- Person person = (Person) descriptor.getPropertyType().newInstance();
- //3. 得到该属性的写方法 setPerson()
- Method method = descriptor.getWriteMethod();
- //4. 得到写方法的注解
- Annotation annotation = method.getAnnotation(InjectPerson.class);
- //5. 得到注解上的信息注解的成员变量就是用方法来定义的
- Method[] methods = annotation.getClass().getMethods();
- //6. 将注解上的信息填充到 person 对象上
- for (Method m : methods) {
- // 得到注解上属性的名字 age 或 name
- String name = m.getName();
- // 看看 Person 对象有没有与之对应的方法 setAge(),setName()
- try {
- //6.1 这里假设: 有与之对应的写方法, 得到写方法
- PropertyDescriptor descriptor1 = new PropertyDescriptor(name, Person.class);
- Method method1 = descriptor1.getWriteMethod();//setAge(), setName()
- // 得到注解中的值
- Object o = m.invoke(annotation, null);
- // 调用 Person 对象的 setter 方法, 将注解上的值设置进去
- method1.invoke(person, o);
- } catch (Exception e) {
- //6.2 Person 对象没有与之对应的方法, 会跳到 catch 来我们要让它继续遍历注解就好了
- continue;
- }
- }
- // 当程序遍历完之后, person 对象已经填充完数据了
- //7. 将 person 对象赋给 PersonDao 通过写方法
- PersonDao personDao = new PersonDao();
- method.invoke(personDao, person);
- System.out.println(personDao.getPerson().getUsername());
- System.out.println(personDao.getPerson().getAge());
: 总结一下步骤
其实我们是这样把对象注入到方法中的:
得到想要类中注入的属性
得到该属性的对象
得到属性对应的写方法
通过写方法得到注解
获取注解详细的信息
将注解的信息注入到对象上
调用属性写方法, 将已填充数据的对象注入到方法中
6.2 把对象注入到成员变量
上面已经说了如何将对象注入到方法上了, 那么注入到成员变量上也是非常简单的
步骤:
: 在成员变量上使用注解
- public class PersonDao {
- @InjectPerson(username = "zhongfucheng",age = 20) private Person person;
- public Person getPerson() {
- return person;
- }
- public void setPerson(Person person) {
- this.person = person;
- }
- }
: 编写注入工具
- //1. 得到想要注入的属性
- Field field = PersonDao.class.getDeclaredField("person");
- //2. 得到属性的具体对象
- Person person = (Person) field.getType().newInstance();
- //3. 得到属性上的注解
- Annotation annotation = field.getAnnotation(InjectPerson.class);
- //4. 得到注解的属性注解上的属性使用方法来表示的
- Method[] methods = annotation.getClass().getMethods();
- //5. 将注入的属性填充到 person 对象上
- for (Method method : methods) {
- //5.1 得到注解属性的名字
- String name = method.getName();
- // 查看一下 Person 对象上有没有与之对应的写方法
- try {
- // 如果有
- PropertyDescriptor descriptor = new PropertyDescriptor(name, Person.class);
- // 得到 Person 对象上的写方法
- Method method1 = descriptor.getWriteMethod();
- // 得到注解上的值
- Object o = method.invoke(annotation, null);
- // 填充 person 对象
- method1.invoke(person, o);
- } catch (IntrospectionException e) {
- // 如果没有想对应的属性, 继续循环
- continue;
- }
- }
- // 循环完之后, person 就已经填充好数据了
- //6. 把 person 对象设置到 PersonDao 中
- PersonDao personDao = new PersonDao();
- field.setAccessible(true);
- field.set(personDao, person);
- System.out.println(personDao.getPerson().getUsername());
七总结
: 注入对象的步骤: 得到想要注入的对象属性, 通过属性得到注解的信息, 通过属性的写方法将注解的信息注入到对象上, 最后将对象赋给类
: 注解其实就是两个作用:
让编译器检查代码
将数据注入到方法成员变量类上
: 在 JDK 中注解分为了
基本 Annotation
在 lang 包下, 用于常用于标记该方法, 抑制编译器警告等
元 Annotaion
在 annotaion 包下, 常用于修饰其他的 Annotation 定义
来源: https://www.cnblogs.com/Java3y/p/8692333.html