一、注解
Java注解(Annotation)又称Java标注,是JDK5.0引入的一种**“注释机制”**。(一种把“注释”嵌入到代码执行时的一种注释机制)
1.Java 语言中的 类、方法、变量、参数和包 等都可以被标注。
2.和注释(//)不同,Java虚拟机可以通过反射获取标注内容。在编译器生成类文件时,标注(注解)可以被嵌入到字节码中。保留标注内容,在运行时可以获取到标注内容。
3.注解支持自定义Java标注
- 注解主要用于:
编译格式检查
反射中解析
生成帮助文档
跟踪代码依赖
…
应主要理解Annotation的语法、用法,不要拘泥于概念
明确:1.注解的概念
2.怎么使用内置注解
3.怎么自定义注解
4反射中怎么获取注解内容
1.注释是为了人,为了其他程序员能更好地理解被注释代码。注释只能在源文件中存在(.java中的代码,经过编译变成.class文件以后,注释是不会保留在.class文件中的!)
2.注解是为了给机器看,(在IDEA中未使用的函数会变暗,这是因为在代码编译时,识别出了问题),注解可告诉编译器,这段程序存在的(含义、依赖关系等)
3.注解是通过反射技术 获取这段代码被加上的注解内容
1.1内置注解有哪些?
@Override
: 重写 *- 定义在java.lang.Override。此注解属于编译格式检查的一部分
@Deprecated
:废弃 *- 定义在java.lang.Deprecated。标注此废弃注解的方法,非要用也是可以用的
@SafeVarargs
- Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
@FunctionalInterface
: 函数式接口 *- Java 8 开始支持,标识一个匿名函数或函数式接口。
@Repeatable
:标识某注解可以在同一个声明上使用多次- Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
SuppressWarnings
:抑制编译时的警告信息。 * 比如在IDEA中未使用的方法、对象会变暗(即出现编译警告,警告 !=bug\错误),可通过此注解进行抑制- 定义在java.lang.SuppressWarnings
- 三种使用方式
1. @SuppressWarnings("unchecked") [^ 抑制单类型的警告]
2. @SuppressWarnings("unchecked","rawtypes") [^ 抑制多类型的警告]
3. @SuppressWarnings("all") [^ 抑制所有类型的警告]
1.2元注解
1.2.1简介
作用在“其他注解”的注解。
理解:(给自定义的注解加注解,在自定义注解时,可以通过四个元注解对自定义注解进行一些配置)
1.2.2元注解有哪些?
a)@Retention
- 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
这个元注解有参数,通过参数指定自定义注解的存在时期。 被该元注解标注的注解,若参数为source,那么自定义注解最多只存在于源码中,如果持久策略为class,那么除了源码会进行检查,编译到class后依然会检查。
RetentionPolicy(注解作用域策略)
“每一个Annotation" 都与 “1 个 RetentionPolicy” 关联
a) 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该 Annotation 就没用了。 例如," @Override" 标志就是一个 Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override" 就没有任何作用了。
b) 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是 Annotation 的默认行为。
c) 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class 文件中,并且可由JVM读入。
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */
CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */
RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}
b)@Documented
- 标记这些注解是否包含在用户文档中 javadoc。
c)@Target
- 标记这个注解应该是哪种 Java 成员。
用于标识 “注解”应该应用于java哪一部分?是方法?包?类?属性?还是一行代码中…
其代码:
package java.lang.annotation;
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* 方法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE /* 包声明 */
}
d)@Inherited
- 标记这个注解是否是自动继承的
不要理解成注解有继承关系!
例如Class A是Class B的父类,Class A含有各种注解,那么Class B在继承时,也会把A的注解继承过来。我们可以通过@Inherited
来确定父类的哪些注解是可以被子类继承的。
- 子类会继承父类使用的注解中被@Inherited修饰的注解
- 接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰
- 类实现接口时不会继承任何接口中定义的注解
二、自定义注解
2.1注解架构
一个注解必须指定1-n个ElementType,(注解应指定用途范围,包、类等)
一个注解必须要与一个RetentionPolicy进行关联(每个注解都要有一个持久策略)
2.2自定义注解的格式
格式:@interface 自定义注解名{}
注解在自定义完毕以后,本质上也是按照接口的模式执行
package web;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@MyAnnotation("张三")
public class Demo2 {
public static void main(String[] args) {
}
}
//注解是否包含在文档中
@Documented
//用途类型
@Target(ElementType.TYPE) //该自定义注解可以在类、接口(包括注释类型)或枚举声明,Target可以控制注解所在的位置
@Retention(RetentionPolicy.RUNTIME) //保存策略
@Inherited //可以继承
@interface MyAnnotation{
String value();
}
2.3注意事项
a)自定义的注解,自动继承了java.lang.annotation.Annotation接口
b)注解中的每一个方法,实际是声明的注解配置参数
- 方法的名称就是配置参数的名称
- 方法的返回值类型,就是配置参数的类型。只能是:基本类型/Class/String/enum
例如上述自定义注解中MyAnnotation{String value();}
c)可以通过default来声明参数的默认值
d)如果只有一个参数成员,一般参数名为value
e)注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
2.4自定义注解举例
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
参数类型 参数名() default 默认值;
}
上面的作用是定义一个 Annotation,我们可以在代码中通过 “@MyAnnotation1” 来使用它。
@Documented, @Target, @Retention, @interface 都是来修饰 MyAnnotation1 的。含义:
(01)@interface
使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation
接口,即该注解就是一个Annotation。 定义 Annotation 时,@interface 是必须的
。
注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。
(02)@Documented
类和方法的 Annotation 在缺省情况下是不出现在 javadoc 中的。如果使用 @Documented修饰该Annotation,则表示它可以出现在 javadoc 中。
定义 Annotation 时,
@Documented 可有可无
;若没有定义,则 Annotation 不会出现在 javadoc中。
(03)@Target(ElementType.TYPE)
前面我们说过,ElementType 是 Annotation 的类型属性。而 @Target 的作用,就是来指定Annotation 的类型属性。
@Target(ElementType.TYPE) 的意思就是指定该 Annotation 的类型是 ElementType.TYPE。这就意味着,MyAnnotation1 是来修饰"类、接口(包括注释类型)或枚举声明"的注解。
定义 Annotation 时,@Target 可有可无
。若有 @Target,则该 Annotation 只能用于它所指定的地方;若没有 @Target,则该 Annotation 可以用于任何地方。
(04)@Retention(RetentionPolicy.RUNTIME)
前面我们说过,RetentionPolicy 是 Annotation 的策略属性,而 @Retention 的作用,就是指定Annotation 的策略属性。
@Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是
RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息保留在 .class 文件中,并且能被虚拟机读取。
定义 Annotation 时,@Retention 可有可无
。若没有 @Retention,则默认是
RetentionPolicy.CLASS。
三、反射
3.1什么是反射?
如果后续要更深入研究Java框架,那么反射是必须掌握的。
反射–反封装…
JAVA反射机制是在运行状态中,获取
任意
一个类的结构 , 创建对象 , 得到方法,执行方法 , 属性 !;
这种在运行状态动态获取信息以及动态调用对象方法的功能被称为java语言的反射机制。
正常流程先通过.java文件编写一个类,最终通过javac把这个.java文件转换为.class文件。然后再由类加载器将类加载到内存,然后JVM在创建对象时,根据加载到内存中的内存信息,构建出对象
正常过程
:(想法(.java)——>图纸(.class)——>工人根据图纸盖房子(创建对象))
反射过程
:代码已经运行起来了,且之前没有这个类(或者这个类之前不存在),此时再通过代码去加载一个类的结构,通过运行的代码去了解这个未知(任意的)的类(的结构),并获取它的属性,方法等
这种在程序运行起来以后,再去动态加载别的类,属于Java动态编程中很重要的点
3.2类加载器
.java代码经过编译变成.class文件(字节码文件),字节码文件需要被类加载器加载到内存以后,才可以执行代码(创建对象)。
类加载器是Java运行时环境(JRE)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。
Java默认有三种类加载器:1)引导启动类加载器(BootstrapClassLoader);2)扩展类加载器(ExtensionClassLoader);3)应用类加载器(App ClassLoader)
问:三种类加载器分别作用是什么?
答:1)引导启动类加载器,是JVM内核中的加载器(用C++写的),它无法直接被Java应用程序直接使用。它主要负责加载JDK安装目录下的bin目录下的类库(即JAVA_HOME/lib下的类库)
。
2)扩展类加载器,主要负责加载JAVA_HOME/lib/ext目录中的类库
,其父加载器是1)中的引导启动类加载器。
3)应用类加载器,这个就是我们常说的类加载器,我们加载配置文件、加载类等操作都是用的这个应用类加载器,它负责加载应用程序下的jar包和class文件
三个加载器存在子父关系,这种子父关系不是Java中的继承关系,而是一种委派概念。
下图是三种类加载器的父子关系示意图:
类通常按需加载,第一次使用该类时才加载,所以加载到内存后不会重复加载。
因为有类加载器,Java运行时,系统不需要知道文件与文件系统
。
此时,需要知道Java的委派概念
问:多个类加载器之间如何避免重复加载类的?
答:因为使用了“双亲委派模型”
。
如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试自己去加载。
一句话:所有的类请求都应该传递到启动类加载器中,当父类加载器搜索范围没有这个类时才会让子类加载器尝试加载
。
3.2.1加载配置文件
如何获取当前的类加载器?
还可以通过:
public class ClassLoaderDemo {
public static void main(String[] args) {
//获取项目src文件夹下的配置文件的输入流,并读取
InputStream is = ClassLoaderDemo.class.getClassLoader().getResourceAsStream("config.txt");
BufferReader br = new BufferReader(new InputStreamReader(is));
String text = br.readLine();
System.out.println(text);
br.close();
}
}
注意:.getResourceAsStream()默认加载的是src路径下的文件,但是如果项目中标记了Resource ROOT 目录时,那么.getResourceAsStream()会变为加载resource root下的文件
3.3所有类型的Class对象
要想了解一个类,必须先要获取到该类的字节码文件对象。
在Java中,每一个字节码文件,被加载到内存后,都存在一个对应的“Class类型的对象”
例如一个项目中,存在com.java.Demo.java和com.java.Person.java两个文件
类被加载到内存后变成了对象,这个对象是Class类型的对象
//这里前面的Class称为 类 类型
Class c = Demo.class;
3.4得到Class的几种方式
如何把类加载到内存,并且得到这个类类型的对象?拿到这个对象有什么用?
因为 必须要先拿到这个对象才能操作反射,才能获取这个类中的构造方法,属性,注解等“类的信息”
- 如果在编写代码时, 知道类的名称, 且类已经存在, 可以通过
包名.类名.class 得到一个类的 类对象- 如果拥有类的对象, 可以通过 Class 对象.getClass() 得到一个类的 类对象
- 如果在编写代码时, 知道类的名称 , 可以通过 Class.forName(包名+类名): 得到一个类的 类对象
public class ClassLoaderDemo {
public static void main(String[] args) throws ClassNotFoundException {
// //第一种方式:通过类名.class 加载类 (=包名.类名.class;当在一个包中,可以省略写包)
// Class<Person> c1 = Person.class;
//
// //第二种方式,通过类的对象获取类的信息
// Person p = new Person();
// Class<Person> c2 = (Class<Person>) p.getClass();
//第三种方式,知道类的名称得到一个类的 类对象,这个不会报错,属于动态编程(更合理)
Class<Person> c3 = (Class<Person>)Class.forName("com.java.demo.Person");
}
}
在后面学框架时,会经常通过配置文件告诉框架我有哪些类。在代码未执行时就告诉框架,我写了哪些类,通过这种方式加载并创建对象
3.5反射如何获取类的构造方法
3.5.1通过class对象,获取一个类的构造方法
1.通过指定的参数类型,获取指定的单个构造方法
getConstructor(参数类型的class对象数组)
例如:
类的构造方法如下: Person(String name,int age)
得到这个构造方法的代码如下:
Constructor c = p.getClass().getConstructor(String.class,int.class);
2.获取构造方法数组
getConstructors();
3.获取所有权限的单个构造方法
getDeclaredConstructor(参数类型的class对象数组)
4. 获取所有权限的构造方法数组
getDeclaredConstructors();
3.5.2通过Constructor创建对象
常用方法:
a) newInstance(Object…para)
调用这个构造方法,把对应的对象创建出来
参数:是一个object类型可变参数,传递的参数顺序,必须匹配构造方法中形式参数列表的顺序。
b)setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法),可以忽略一些私有的构造方法
代码示例:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ClassLoaderDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Person> c3 = (Class<Person>)Class.forName("com.java.demo.Person");
//找到无参构造方法
Constructor<Person> c1=c3.getConstructor();
//使用无参构造方法,创建对象
Person p=c1.newInstance();
//找到包含String name 和 int age的构造方法
//c3.getConstructor(new Class[]{String.class,int.class});
//通过getDeclaredConstructor(参数类型的class对象数组)获取所有权限的单个构造方法
Constructor<Person> c2 = c3.getDeclaredConstructor(String.class);
//设置权限,可访问私有(通过反射技术,外部调用私有构造方法)
c2.setAccessible(true);
//使用这个构造方法,创建对象
Person p2 = c2.newInstance("李四");
}
}
3.6反射如何获取Method
反射如何获取类的方法?获取到类的方法以后,又如何执行方法?
3.6.1通过class对象,获取一个类的方法
1.getMethod(String methodName, class…clss)
根据参数列表的类型和方法名,得到一个方法(public修饰的)
2.getMethods()
得到一个类的所有方法 (public修饰的)
3.getDeclaredMethod(String methodName , class… clss)
根据参数列表的类型和方法名, 得到一个方法(除继承以外所有的:包含私有, 共有, 保护, 默认)
4.getDeclaredMethods();
得到一个类的所有方法 (除继承以外所有的:包含私有, 共有, 保护, 默认)
3.6.2Method执行方法
invoke(Object o, object… para)
调用方法 ,
参数1. 要调用方法的对象
参数2. 要传递的参数列表
getName()
获取方法的方法名称
setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法)
使用举例:
public class GetMethodDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//加载类
Class c1 = Class.forName("com.java.demo.Person");
//获取类的构造方法
Constructor c = c1.getConstructor();
//创建了对象
Object o = c.newInstance();
//获取类的方法
Method setName = c1.getMethod("setName", String.class);
//获取类的私有方法,拿到私有方法以后,还应该设置权限!
Method setAge = c1.getDeclaredMethod("setAge",int.class);
//设置私有方法忽略权限检查:
setAge.setAccessible(true);
//参数1,哪个对象要执行setName方法
//参数2,调用方法时传递的参数 0-n
setName.invoke(o,"张三"); //相当于对象o在调用setName("张三")
}
}
3.7反射如何获取类的属性
3.7.1通过class对象 获取一个类的属性
- getDeclaredField(String filedName)
根据属性的名称, 获取一个属性对象 (所有属性)- getDeclaredFields() 获取所有属性
3. getField(String filedName) 根据属性的名称, 获取一个属性对象 (public属性)- getFields() 获取所有属性 (public)
3.7.2Field属性的对象类型
常用方法:
1.get(Object o ); 参数: 要获取属性的对象 获取指定对象的此属性值
2. set(Object o , Object value); 参数1. 要设置属性值的 对象 参数2. 要设置的值 设置指定对象的属性的值
3. getName() 获取属性的名称
4.setAccessible(boolean flag) 如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的属性) 使用示例:
public class GetMethodDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//加载类
Class c1 = Class.forName("com.java.demo.Person");
//获取类的构造方法
Constructor c = c1.getConstructor();
//创建了对象
Object o = c.newInstance();
//获取类的方法
Method setName = c1.getMethod("setName", String.class);
//获取类的私有方法,拿到私有方法以后,还应该设置权限!
Method setAge = c1.getDeclaredMethod("setAge",int.class);
//设置私有方法忽略权限检查:
setAge.setAccessible(true);
//参数1,哪个对象要执行setName方法
//参数2,调用方法时传递的参数 0-n
setName.invoke(o,"张三"); //相当于对象o在调用setName("张三")
Class c4 = Class.forName("com.java.demo.Person");
Constructor ct = c4.getConstructor();
Object o4 = ct.newInstance();
Field phoneNumber = c4.getField("phoneNumber");
phoneNumber.set(o,"18881882288");
}
}
3.8反射如何获取注解(的信息)
反射如何获取 类、方法、属性 上的注解对象?
3.8.1获取类、属性、方法的全部注解对象
Annotation[] annotations01 = Class/Field/Method.getAnnotations();
for (Annotation annotation : annotations01) {
System.out.println(annotation);
}
3.8.2根据类型获取类、属性、方法的注解对象
注解类型 对象名 = (注解类型)c.getAnnotation(注解类型.class);
ORM对象关系映射:
通过反射找到了对应的类和类上的注解,然后基于注解里填的内容,再在数据库中跟表格进行绑定。
编写了两个自定义注解:
@TableAnnotation
@ColumnAnnotation:
添加了两种注解的Book对象:
运行BookTestDemo,获取Book对象上的注解
结果:
获取注解中的value值:
通过反射找到了类,然后又找到类上的注解,拿到注解的值以后,就可到数据库中创建一个名为“test_Book”的表
四、内省
4.1什么是内省?
基于反射,java所提供的一套应用到JavaBean的API
一个定义在包中的类,a) 拥有无参构造器,b)所有属性私有,c)所有属性提供get/set方法,c)实现了序列化接口,这种类称为bean类.
这个类不包含任何业务逻辑的部分,整个类只是为了存储一些属性,并给属性提供get/set
Java基于以上规则,提供了一套java.beans包的api
4.2Introspector(内省)
获取Bean类信息
方法:
BeanInfo getBeanInfo(Class cls)
通过传入的类信息,得到这个Bean类的封装对象
内省基于反射技术,当传过来一个类,就通过内省获取类属性的get/set方法,(更方便、简单地获取方法)