注解与反射


一、注解

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来确定父类的哪些注解是可以被子类继承的。

  1. 子类会继承父类使用的注解中被@Inherited修饰的注解
  2. 接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰
  3. 类实现接口时不会继承任何接口中定义的注解

二、自定义注解

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的几种方式

如何把类加载到内存,并且得到这个类类型的对象?拿到这个对象有什么用?
因为 必须要先拿到这个对象才能操作反射,才能获取这个类中的构造方法,属性,注解等“类的信息”

  1. 如果在编写代码时, 知道类的名称, 且类已经存在, 可以通过
    包名.类名.class 得到一个类的 类对象
  2. 如果拥有类的对象, 可以通过 Class 对象.getClass() 得到一个类的 类对象
  3. 如果在编写代码时, 知道类的名称 , 可以通过 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对象 获取一个类的属性

  1. getDeclaredField(String filedName)
    根据属性的名称, 获取一个属性对象 (所有属性)
  2. getDeclaredFields() 获取所有属性
    3. getField(String filedName) 根据属性的名称, 获取一个属性对象 (public属性)
  3. 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方法,(更方便、简单地获取方法)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值