注解
1.概念和定义
1.定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释
补充导读:相信很多同学看完之后是整不明白的,为什么呢是因为我们用专业名词去解释了专业名词,所以你才会不懂的,那么我们该怎么办呢
补充导读:相当大家都听说过这样一句话:“乔布斯重新定义了智能手机,罗永浩重新定义了傻逼”,有人肯定会说这个跟咱们今天的课也不沾边啊,其实不然。诚然咱们讲的是编程课,但是概念是可以类比的去理解的,
所谓的注解说白了就是一种“标记”,就像一说到智能手机就是乔布斯,一说到炫富就是郭美美,一说到好男人就是曾小贤。。。。。,都是标记
2.为什么引入注解呢?
曾经我们的商业项目开发的时候我们都是用xml实现配置的,这种方式的特点是耦合度低,但是麻烦,但是不知道是什么时候有一些编码人员和架构师开始使用注解来代替原来的xml配置使得书写简单了,但是从某些角度而言是耦合度提升了,那么我们是不是要全部放弃XML,全部改为注解呢?答案是否定的,为什么呢?那么我们就一起看看注解和XML的对比吧
注解
优点:简化配置使用起来直观且容易,提升开发效率,类型安全
缺点:改变实现类比xml困难
xml
优点:类与类间的松藕合,容易扩展、更换对象间的关系一目了然
缺点:配置冗长,且还要额外多维护一份配置,类型不安全,compile(编译)无法帮忙校验,运行期才会发现错误
小结:xml和注解都要会
现状:xml(配置文件)比注解使用的要多一些,
友情提示:先学会xml形式的,那么再来学注解的基本上就是水到渠成的事,
在商业项目开发的时候经常是xml和注解一起配合使用的
3.注解的作用:
- 生成文档:这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
- 跟踪代码依赖性,实现替代配置文件功能:比如Spring框架依赖注入,将大量注解替换掉配置的xml文件,具有很大用处
- 在编译时进行格式检查:如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
4.注解的分类:
- JDK中提供的标准注解:其实就是JDK官方已经写好的且具有特定功能的注解
- 元注解:由元数据修饰的注解就是元注解(注解的注解),就是标签的标签
- 自定义注解:就是开发者根据注解的规则自己编写的注解,且经常要使用上元注解
5.Java中注解是用来干做什么的:java就是通过注解来表示元数据
6.元数据:元数据是指用来描述数据的数据,更通俗一点,就是描述代码间关系,或者代码与其他资源(例如数据库表)之间内在联系的数据。在一些技术框架,如struts、EJB、hibernate就不知不觉用到了元数据。对struts来说,元数据指的是struts-config.xml,对于servlet来说元数据是指servlet-mapping,其本质就是一种和Class,枚举等一样的一种的Java的数据类型,其最根本的本质就是一个接口
2.JDK中的几个标准注解
JDK1.5中有三个标准注解,当然1.5之后有又增加了一些,那么我们今天就以1.5中的这三个基本的注解做例子进行讲解
他们分别为:
- @override:重写,覆盖
- @Deprecated:过时的,弃用的
- @SuppressWarnings:压缩警告/忽略警告
示例代码:
package com.Li.anno; /** * @desc 注解的学习 电话类 * @author Li Ya Hui * @time 2021年6月22日 上午9:11:36 */ public class Phone { public void call() { System.out.println("电话:可以通话!"); } @Deprecated //注解 Deprecated为过时代码的意思 public void sendLittleMessage() { System.out.println("电话:可以发短信"); } @Override public String toString() { return "Phone [toString()= @Override 是一个注解 且作用为重写 ]"; } }
package com.Li.anno; /** * @desc 测试类,测试JDK中的三个标准注解 * @author Li Ya Hui * @time 2021年6月22日 上午9:17:44 */ public class Test { /** * @SuppressWarnings:压缩警告:告诉编译器忽略指定的警告,不用在编译完成后出现警告信息 * 分类:@SuppressWarnings(""):抑制单类型的警告 * 例如当你的代码中有使用@Deprecated的时候就会产生这种类型的警告:@SuppressWarnings("deprecation") * 例如当你的代码中有使用没有确定泛型的容器的时候会产生这种类型的警告:unchecked抑制未检查的转化 * 那么两者组合在一起就是抑制多类型的警告,那么这样岂不是很麻烦吗,诚然。但是你可以用抑制所有类型的警告这样的话就变的清爽了 * @SuppressWarnings({}):抑制多类型的警告 * @SuppressWarnings("all"):抑制所有类型的警告,但是不建议多使用,因为这样的话就降低了编译器的自检能力,可能会出现意想不到的错误 * 小结:根据自己的需要巧妙的根据IDE的提示去使用@SuppressWarnings这个注解 */ // SuppressWarnings 抑制警告 @SuppressWarnings("deprecation") public static void main(String[] args) { Phone pe = new Phone(); pe.call(); pe.sendLittleMessage(); System.out.println(pe.toString()); } }
小结:
* @SuppressWarnings:压缩警告:告诉编译器忽略指定的警告,不用在编译完成后出现警告信息 * 分类:@SuppressWarnings(""): * 抑制单类型的警告 * 例如当你的代码中有使用@Deprecated的时候就会产生这种类型的警告: * @SuppressWarnings("deprecation") * 例如当你的代码中有使用没有确定泛型的容器的时候会产生这种类型的警告: * unchecked抑制未检查的转化 * 那么两者组合在一起就是抑制多类型的警告,那么这样岂不是很麻烦吗,诚然。 * 但是你可以用抑制所有类型的警告这样的话就变的清爽了`@SuppressWarnings("all")` * @SuppressWarnings({}): * 抑制多类型的警告 * @SuppressWarnings("all"): * 抑制所有类型的警告,但是不建议多使用,因为这样的话就降低了编译器的自检能力,可能会出现意想不到的错误 * 小结:根据自己的需要巧妙的根据IDE的提示去使用@SuppressWarnings这个注解
3.元注解
- 什么是元注解:java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解)
PS:元注解:就是注解的注解 - 元注解包含4种:
1)@Documented –注解是否将包含在JavaDoc中(是否能转为电子文档)
2)@Retention –什么时候使用该注解
3)@Target –注解用于什么地方
4)@Inherited – 是否允许子类继承该注解
3.种元注解的详细:
1.@Retention– 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任 何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
2.@Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用ElementType参数包括
● ElementType.CONSTRUCTOR:用于描述构造器
● ElementType.FIELD:成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE:用于描述局部变量
● ElementType.METHOD:用于描述方法
● ElementType.PACKAGE:用于描述包
● ElementType.PARAMETER:用于描述参数
● ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
3.@Documented–一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。
4.@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
4.自定义注解
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口
分析:
- @interface 用来声明一个注解,格式:public @interface 注解名 {定义内容}
- 其中的每一个方法实际上是声明了一个配置参数
- 方法名的名称就是参数的名称
- 返回值类型就是参数的类型(返回值之能)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
4.1.自定义注解应用举例之分散写
创建自定义注解和注解处理类(器)
FriutName.java 第一个自定义注解:FruitName,其意思为水果名字
package com.Li.anno2; /** * @desc 第一个自定义注解:FruitName,其意思为水果名字 * @author wyh * @time 2018-12-28 上午10:36:03 * */ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(value = { ElementType.FIELD }) //表示该注解用于什么地方。默认值为任何元素 field 成员变量、对象、属性(包括enum实例) @Retention(value = RetentionPolicy.RUNTIME) //表示该注解的生命周期, runtime始终不会丢弃 ,运行期间也保留该注释 @Documented //一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。 public @interface FruitName { //默认值是三 String value() default "123"; }
FriutColor.java 第二个自定义注解:FruitColor,其意思为水果颜色
package com.Li.anno2; /** * @desc 第二个自定义注解:FruitColor,其意思为水果颜色 * @author wyh * @time 2018-12-28 上午10:39:08 * */ import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(value = { ElementType.FIELD })//使用的范围为属性 @Retention(value = RetentionPolicy.RUNTIME)//注解的声明周期为始终不放弃 @Documented public @interface FriutColor { /** * 1.创建一个枚举注解 * @author Li Ya Hui * @time 2021年6月22日 下午8:43:50 */ public enum Color{RED,BLUE,GREEN};//声明一个枚举类型,且给值为红色,绿色,蓝色 /** * 2.为当前的自定义注解FruitColor设定一个属性,通过default这个关键字设定该属性的默认值为红色 * @return */ Color firuitColor() default Color.RED;//默认为红色 }
FruitProcessor.java 注解处理器
package com.Li.anno2; /** * @desc 注解处理器 * @author LiYaHui * @time 2018-12-28 上午10:47:07 * */ import java.lang.reflect.Field; public class FruitProcessor { public static void getFruitInfo(Class<?>clazz) { //1.声明两个变量,且赋初值 String strFruitName="水果的名字:"; String strFruitColor="水果的颜色:"; //2.利用反射获取传递类的属性的集合 Field[] fields = clazz.getFields(); //2.迭代每一个属性,目的是为了取出使用了我们自定义的两个注解的值 for (Field field : fields) { //2.1判断当前传入的注解类对象是否是注解 if(field.isAnnotationPresent(FruitName.class)) { FruitName fruitName = field.getAnnotation(FruitName.class); strFruitName=strFruitName+fruitName.value(); System.out.println(strFruitName); } else if (field.isAnnotationPresent(FriutColor.class)) { FriutColor fruitColor = field.getAnnotation(FriutColor.class); strFruitColor=strFruitColor+fruitColor.firuitColor(); System.out.println(strFruitColor); } } } }
4.2.自定义注解应用举例之写在一起
创建自定义注解和注解处理类(器)
LitChi.java 荔枝类—自定义注解之合在一起写
package com.Li.anno3; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @desc 荔枝类---自定义注解之合在一起写 * @author Li Ya Hui */ public class LitChi { @FruitName(value="荔枝") public String name; public String toStr() { return "当前水果的名字"; } /** * @desc 1.在LitChi类中自定义了一个注解,且名字叫FruitName * @author Li Ya Hui * @time 2021年6月22日 下午9:28:54 */ @Target(value = {ElementType.FIELD}) //运行目标 @Retention(value = RetentionPolicy.RUNTIME) //运行周期 @Documented @interface FruitName{ /** * @desc 2.设置一个属性,且该属性的名字为value,且类型为String 类型,且默认值为“” * @return */ String value() default ""; } }
Test.java 测试自定义注解
package com.Li.anno3; // 反映 import java.lang.reflect.Field; import com.Li.anno3.LitChi.FruitName; /** * @desc 测试自定义注解 * @author Li Ya Hui * @time 2021年6月22日 下午9:36:17 */ public class Test { public static void main(String[] args) { Class<LitChi> lch = LitChi.class; Field[] fields = lch.getFields(); for (Field field : fields) { System.out.println(field);//获取到LitChi类中的属性 //判断LitChi类中的属性是否被自定义注解FruitName修饰 System.out.println(field.isAnnotationPresent(FruitName.class)); if (field.isAnnotationPresent(FruitName.class)) { //获取LitChi类中的fruitName这个属性被标记/修饰的自定义注解对象 FruitName fruitName=field.getAnnotation(FruitName.class); System.out.println(fruitName.value());//获取到给自定义注解中的属性value赋的值 } } } }
Documented注解的作用及其javadoc文档生成工具的使用
Documented注解的作用及其javadoc文档生成工具的使用
作用:生成文档
生成文档的步骤:
- 准备:先进入到LitChi的文件目录下, 然后在该目录下切换出cmd黑窗口
- 输入指令:javac -encoding utf-8 -d . LitChi.java 其目的是为了将该Java文件进行编译
- 输入指令:javadoc -d doc LitChi.java 其目的是为了生成电子文档
Documented注解的作用及其javadoc文档生成工具的使用 作用:生成文档 铺垫:为了能够顺利的实现生成电子文档:我们需要将LitChi.java中的中文给删掉(强烈建议复制粘贴此处的代码) package com.rj.bd.anno3; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class LitChi { @FriutName("lizhi") public String fruitName; public String toString() { return "It is :"+fruitName ; } } @Target(value = { ElementType.FIELD }) @Retention(value = RetentionPolicy.RUNTIME) @Documented @interface FriutName{ public String value() default "" ; } 生成文档的步骤: 0)准备:先进入到LitChi的文件目录下, 然后在该目录下切换出cmd黑窗口 1)输入指令:javac -encoding utf-8 -d . LitChi.java 其目的是为了将该Java文件进行编译 2 ) 输入指令:javadoc -d doc LitChi.java 其目的是为了生成电子文档
4.3.自定义注解应用举例之复杂数据类型
我们所需建的类
- CarAnnotation.java 自定义注解一:复杂数据类型的属性 @Target(value= {ElementType.METHOD})
- CarBodyAnnotation2.java 自定义注解二:里面可以设定字符串类型的属性 @Target(FIELD)
- CarTypes.java 枚举:车的类型
- Car.java 车:第一个使用注解的类
- CarBody.java 车身信息:用上第二个自定义的注解
- CarProcessor.java 注解处理器类
- Test.java 测试类
CarAnnotation.java 自定义注解一:复杂数据类型的属性 @Target(value= {ElementType.METHOD})
package com.Li.anno4; /** * @desc 自定义注解一:复杂数据类型的属性 设定复杂的数据类型作为变量,且使用的案例是车的注解 * @author Li Ya Hui * @time 2021年6月23日 上午10:17:48 */ import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; @Documented @Retention(RUNTIME) @Target(value= {ElementType.METHOD}) //注解作用于方法上 public @interface CarAnnotation { String name();//1.字符串 String engineType() default "汽油";//2.带默认值的字符串 int carDoor() default 5;//3.int型 CarTypes carTypes() default CarTypes.COMMON;//4.枚举型 Class clazz();//5.类类型 CarBodyAnnotation2 my(); //6.注解类型,(在注解中包含注解) int[] arr() default{1,2,3};//7.一元数组类型 CarTypes [] carTypeArray(); //枚举数组 }
CarBodyAnnotation2.java 自定义注解二:里面可以设定字符串类型的属性 @Target(FIELD)
package com.Li.anno4; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; /** * @desc 自定义注解二:里面可以设定字符串类型的属性,且是为了将来能用在CarAnnotation这个注解中 * @author Li Ya Hui * @time 2021年6月23日 上午10:19:49 */ @Documented @Retention(RUNTIME) @Target(FIELD) public @interface CarBodyAnnotation2 { String material();//车身的材料 String info();//车身的信息 }
CarTypes.java 枚举:车的类型
package com.Li.anno4; /** * @desc 枚举:车的类型 * @author Li Ya Hui * @time 2021年6月23日 上午10:21:27 */ public enum CarTypes { COMMON,//普通汽车 SUV,//越野车 CRV,//城市越野 MINI;//迷你 }
Car.java 车:第一个使用注解的类
package com.Li.anno4; /** * @desc 第一个使用注解的类 * @author Li Ya Hui * @time 2021年6月23日 上午10:24:05 */ public class Car { @CarAnnotation(carTypeArray = { CarTypes.COMMON,CarTypes.SUV }, clazz =CarBody.class, arr={3,4,5}, name = "林肯",carDoor=4,engineType="电动", my = @CarBodyAnnotation2(info = "展台的样车", material = "不锈钢") ) public void show(String name,String engineType,int carDoor,int[] arr,CarTypes[] carTypeArray) { } }
CarBody.java 车身信息:用上第二个自定义的注解
package com.Li.anno4; /** * 车身信息:用上第二个自定义的注解 * @author Li Ya Hui * @time 2021年6月23日 上午10:25:17 */ public class CarBody { @CarBodyAnnotation2(info="4.2米长,2.6米宽",material = "钛铝合金") public String carBodyInfo; }
CarProcessor.java 注解处理器类
package com.Li.anno4; import java.lang.reflect.Field; import java.lang.reflect.Method; /** * @desc 注解处理器类 * @author Li Ya Hui * @time 2021年6月23日 上午10:27:21 */ public class CarProcessor { public static void showInfo(Class<?>clazz) { //1.利用反射获取传入的类对象中的所有的方法 Method[] methods = clazz.getMethods(); for (Method method : methods) { if(method.isAnnotationPresent(CarAnnotation.class)) { CarAnnotation myCar = method.getAnnotation(CarAnnotation.class); System.out.println("车门:"+myCar.carDoor()); System.out.println("车的名字:"+myCar.name()); System.out.println("发动机类型:"+myCar.engineType()); CarTypes[] carTypeArray = myCar.carTypeArray(); CarBodyAnnotation2 my = myCar.my(); System.out.println("my.info:"+my.info()); System.out.println("my.material:"+my.material()); for (CarTypes carTypes : carTypeArray) { System.out.println("车的种类:"+carTypes); } Class cla = myCar.clazz();//获取CarAnnotation注解中的类类型 System.out.println("-------->"+cla);//此时可以看到一件获取到车身了,且是已经存在于内存中是已经实例化的 Field[] fields = cla.getFields(); for (Field field : fields) { if (field.isAnnotationPresent(CarBodyAnnotation2.class)) { CarBodyAnnotation2 carBody = field.getAnnotation(CarBodyAnnotation2.class); System.out.println("车身的材料--->"+carBody.material()); System.out.println("车身的信息--->"+carBody.info()); } } } } } }
Test.java 测试类
package com.Li.anno4; /** * @desc 测试类 * @author Li Ya Hui * @time 2021年6月23日 上午10:28:31 */ public class Test { public static void main(String[] args) { CarProcessor.showInfo(Car.class); } }
运行结果
车门:4 车的名字:林肯 发动机类型:电动 my.info:展台的样车 my.material:不锈钢 车的种类:COMMON 车的种类:SUV -------->class com.Li.anno4.CarBody 车身的材料--->钛铝合金 车身的信息--->4.2米长,2.6米宽
5.注解运行的原理和流程
导读模块:
- 注解是给机器看的,注释是给程序员看的
- 类加载器
- 类加载器的种类
注解的运行原理:
- 从java源码到class字节码是由编译器完成的,编译器会对java源码进行解析并生成class文件,而注解也是在编译时由编译器进行处理,编译器会对注解符号处理并附加到class结构中,根据jvm规范,class文件结构是严格有序的格式,唯一可以附加信息到class结构中的方式就是保存到class结构的attributes属性中,
- 在我们的LitChi类被编译后,在对应的LitChi.class文件中会包含一个RuntimeVisibleAnnotations属性由于这个注解是作用在属性上,所以此属性被添加到类的属性集上。即@FriutName注解的键值对value="荔枝"会被记录起来。
- 而当JVM加载LitChi.class文件字节码时,就会将RuntimeVisibleAnnotations属性值保存到LitChi的Class对象中,于是就可以通过LitChi.class.getAnnotation(LitChi.class)获取到@FriutName注解对象,进而再通过@FriutName注解对象获取到LitChi里面的属性值。
- 这里可能会有疑问,@FriutName注解对象是什么?其实注解被编译后的本质就是一个继承Annotation接口的接口,所以@FriutName其实就是“public interface FriutName extends Annotation”,当我们通过LitChi.class.getAnnotation(LitChi.class)调用时,JDK会通过动态代理生成一个实现了@FriutName接口的对象,并把将RuntimeVisibleAnnotations属性值设置进此对象中,此对象即为@FriutName注解对象,通过它的value()方法就可以获取到注解值
- 简而言之:
- 注解运行的过程就是注解处理器类在编译器的作用下通过反射找到注解接口
- 并由JDK动态代理的方式为注解接口生成实现类,最终组合起来工作
- 最终精简理解:就是定义了一个注解接口和注解处理器类,二者配合就可以使用了
- 注解的使用: 大体分为三部分: 定义注解、使用注解、解析注解。
- 在框架中定义与解析这两部分框架都已经为我们做好了,我们在开发的过程中直接使用注解即可
6.Servlet3.0中实现注解开发
Servlet3.0:
* 好处:
* 支持注解配置。可以不需要web.xml了。
* 步骤: 1. 创建JavaEE项目,选择Servlet的版本3.0以上,可以不创建web.xml 2. 定义一个类,实现Servlet接口 3. 复写方法 4. 在类上使用@WebServlet注解,进行配置
1.urlpartten:Servlet虚拟访问路径
然而通过观察@WebServlet注解发现,它的urlPatterns定义的是一个String类型的数组,也就是说可以给一个Servlet定义多个虚拟路径
在配置的@WebServlet的时候有时候是不设置参数参数名,直接传入路径,有时候又设置为urlPatterns,而默认不传入参数名,的时候其实,值是赋值给value的而不是urlPatterns
就感到很困惑 到底value和urlPatterns有什么区别?查到了属性功能的列表 并记录一下
webServlet注解的类型和描述
属性名 | 类型 | 描述 |
name | String | servlet-name,如果没有显示指定,该Servlet的取值为全限定名 |
value | String[] | 等价于 urlPatterns 属性,与该属性不能同时使用 |
urlPatterns | String[] | 指定Servlet url的匹配模式,等价于 |
loadOnStartup | int | 指定Servlet的加载顺序 |
initParams | webInitParam[] | 指定初始化参数 |
asyncSupported | boolean | 是否支持异步操作 |
description | String | 描述 |
displayName | String | servlet显示名 |