前言
反射和注解在平时的业务开发中极少会用到,这些技术都是属于学习框架或者开发框架时的底层源码,学习这些知识的目的是为大家今后
理解框架甚至自己开发框架做铺垫的。
一、认识反射
反射就是:加载类,并允许以编程方式解剖类中的各种成分(成员变量、方法、构造器等),
指的就是把某个类的整个字节码文件加载到内存中,并允许以编程的方式解析出类中的各种成员,比如说把类中的成员变量给解析出来,
把类中的构造器解析出来,解析出来之后就可以对它们进行相应的操作了。
反射还学什么?
学习获取类的信息、操作它们:
1、反射第一步:加载类,获取类的字节码:Class对象【类对象】
2、获取类的构造器:Constructor对象【构造方法】
3、获取类的成员变量:Field对象【字段】
4、获取类的成员方法:Method对象【方法】
二、使用反射
2.1 获取Class对象的三种方式
-
Class c1=类名.class
-
调用Class提供方法:public static Class ForName(String pageage);
-
Object提供的方法:public Class getClass(); Class c3=对象.getClass();
代码示例:
先在cn.hxzy包下创建一个Student类
package cn.hxzy;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "demo{name = " + name + ", age = " + age + "}";
}
public String study(){
return "我爱学习Java";
}
public String play(String game){
return "我喜欢打"+game+"游戏";
}
}
然后创建Student类测试
//获取class的三种方法
package cn.hxzy;
public class demo {
public static void main(String[] args) throws Exception{
//第一种方式获取Class对象
Class c1 = Student.class;
System.out.println(c1.getName());//获得全类名:cn.hxzy.Student
System.out.println(c1.getSimpleName());//获得简名:Student(类名)
//第二种方式获取Class对象
Class c2=Class.forName("cn.hxzy.Student");
//第三种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class c3 = stu1.getClass();//获取Class对象
}
}
因为字节码文件是一份,所以拿到的c1、c2、c3三个对象是同一个对象。
2.2 获取类中的构造器
Class 提供的从类中获取构造器的方法:
package cn.hxzy;
import org.apache.tomcat.util.bcel.Const;
import java.lang.reflect.Constructor;
public class DemoTest {
public static void main(String[] args) {
//1.反射第一步先获取类的class对象
Class c=Student.class;
//2.获取类的全部构造器、创建一个构造器类型的数组来接受类中的所有构造器
Constructor[] constructors=c.getConstructors();
//3.遍历数组中的每一个构造器、获取构造器的名字和构造器的参数个数
for (Constructor constructor : constructors) {
System.out.println(constructor.getName()+"="+constructor.getParameterCount());
}
}
}
上面的代码可以得到无参构造和有参构造的名字和参数个数。
注意:getConstructors()只能拿到public修饰的构造器,建议使用getDeclaredConstructors();
package cn.hxzy;
import org.apache.tomcat.util.bcel.Const;
import java.lang.reflect.Constructor;
public class DemoTest {
public static void main(String[] args) {
//1.反射第一步先获取类的class对象
Class c=Student.class;
//2.获取类的全部构造器、创建一个构造器类型的数组来接受类中的所有构造器
Constructor[] constructors=c.getDeclaredConstructors();
//3.遍历数组中的每一个构造器、获取构造器的名字和构造器的参数个数
for (Constructor constructor : constructors) {
System.out.println(constructor.getName()+"="+constructor.getParameterCount());
}
}
}
getConstructor(参数类型)获取单个构造器,需要写参数类型来定位构造器
package cn.hxzy;
import org.apache.tomcat.util.bcel.Const;
import java.lang.reflect.Constructor;
public class DemoTest {
public static void main(String[] args) {
//1.反射第一步先获取类的class对象
Class c=Student.class;
//2.获取无参构造器
Constructor constructor=c.getConstructor();
//3.获取构造器的名字和构造器的参数个数
System.out.println(constructor.getName()+"="+constructor.getParameterCount());
}
}
注意:getConstructor()只能拿到public修饰的构造器,建议使用getDeclaredConstructor();
package cn.hxzy;
import org.apache.tomcat.util.bcel.Const;
import java.lang.reflect.Constructor;
public class DemoTest {
public static void main(String[] args) {
//1.反射第一步先获取类的class对象
Class c=Student.class;
//2.获取无参构造器
Constructor constructor=c.getDeclaredConstructor();
System.out.println(constructor.getName()+"="+constructor.getParameterCount());
//3.获取有参数构造器
Constructor constructor2=c.getDeclaredConstructor(String.class,int.class)
System.out.println(constructor2.getName()+"="+constructor2.getParameterCount());
}
}
暴力反射
获取类构造器的作用:依然是初始化对象返回。
2.3 获取类的成员变量
Class提供了从类中获取成员变量的方法。
//1、反射第一步:先得到类的Class对象
Class c=Student.class;
//2、获取类的全部成员变量
Field[] fields=c.getDeclaredFields();
//3、遍历成员变量数组
for(Field field:fields){
System.out.println(field.getName()+"="+field.getType());
}
//4、获取特定的成员变量
Field fname=c.getDeclaredField("name");
System.out.println(fname.getName()+"="+fname.getType());
获取成员变量的作用依然是赋值,取值
//赋值
Student student=new Student();
fname.setAccessible(true);
fname.set(student,"小王");
System.out.print(student);
//取值
String name=(String)fname.get(student);
System.out.print(name);
获取成员变量访问修饰符:
Field field = c.getDeclaredField("address");
int modifiers = field.getModifiers();
System.out.println(modifiers);
String str = Modifier.toString(modifiers);
2.4 获取类的成员方法
Class提供了从类中获取成员方法的API
//1、反射第一步:先得到类的Class对象
Class c=Student.class;
//2、获取类的全部成员方法
Method [] methods=c.getDeclareMethods();
//3、遍历这个数组中的每个方法对象
for(Method method : methods){
System.out.print(method.getName()+"=="+method.getParameterCount()+"="+method.getReturnType());//获取参数个数与返回结果数据类型
}
//4、获取某个特定的方法对象
Method m1=c.getDeclaredMethod("play", String.class);
System.out.print(m1.getName()+"=="+m1.getParameterCount()+"="+m1.getReturnType());
成员方法的作用依然是执行
//5、执行方法对象
Student student=new Student();
m1.setAccessible(true);
String gameName=m1.invoke(student,"王者荣耀");
System.out.print(gameName);
三、反射的作用
(1)基本作用:得到一个类的全部成分并操作(IDEA获取成员方法,变量)
(2)破坏封装性
(3)最重要是:适合做Java的框架,基本上主流框架都会基于反射设计出一些通用的功能
案例:使用反射做一个简易版的框架
需求:对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去。
实现步骤:
1、定义一个方法,可以用来接收任意对象
2、每收到一个对象之后,使用反射获取该对象的 Class对像,获取全部成员变量
3、得到这个类的全部成员变量,然后提取成员变量在该对象中的具体
4、把成员变量名和值写出文件中去
定义几个类,具体步骤省略
然后定义一个框架类
实体类
package cn.hxzy.entity;/**
@author:mengshujun
@createTime: 2024-03-21 15:02:40 星期四
*/
public class Goods {
private String goodId;
private String goodName;
private double price;
private int count;
private String goodType;
public Goods() {
}
public Goods(String goodId, String goodName, double price, int count, String goodType) {
this.goodId = goodId;
this.goodName = goodName;
this.price = price;
this.count = count;
this.goodType = goodType;
}
public String getGoodId() {
return goodId;
}
public void setGoodId(String goodId) {
this.goodId = goodId;
}
public String getGoodName() {
return goodName;
}
public void setGoodName(String goodName) {
this.goodName = goodName;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public String getGoodType() {
return goodType;
}
public void setGoodType(String goodType) {
this.goodType = goodType;
}
@Override
public String toString() {
return "Goods{" +
"goodId='" + goodId + '\'' +
", goodName='" + goodName + '\'' +
", price=" + price +
", count=" + count +
", goodType='" + goodType + '\'' +
'}';
}
}
框架实现类:
public class ObjectFrame{
// 目标:保存任意对象的字段和其数据到文件中去
public static void saveObject(Object obj){
FileOutputStream fos=new FileOutputStream("d:/obj.txt");
PrintStream ps=new PrintStream(fos);
//1、obj是任意对象,到底有多少个字段要保存,使用反射
Class c=obj.getClass();
String cname= c.getSimpleName();
ps.pritnln("=========="+cname+"=============");
//2、从这个类中提取它的全部成员变量
Field [] fields= c.getDeclaredFields();
//3、遍历每个成员变量
for(Field field : fields){
//4、拿到成员变量的名字
String name=field.getName();
//5、拿到这个成员变量在对象中的数据
field.setAccessible(true); //禁止检查访问控制
String value=(String) field.get(obj);
ps.println(name+"="+value);
}
ps.close();
fos.close();
}
}
测试类
package cn.hxzy.mycase;
import cn.hxzy.entity.Goods;
import cn.hxzy.entity.Student;
import java.io.IOException;
/**
@author:mengshujun
@createTime: 2024-03-21 15:05:03 星期四
*/
public class CaseDemo {
public static void main(String[] args) {
try {
//Goods goods = new Goods("1001","iphone15pro max",8900.00,10,"手机");
Student student=new Student("张三丰",156,10000);
//ObjectFrame.saveObject(goods);
ObjectFrame.saveObject(student);
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
使用反射的原因
Java中使用反射的原因包括提高程序的灵活性、屏蔽实现细节、实现动态加载类、动态创建对象、访问私有成员、扩展性和灵活性、类型分析和调试。
提高程序的灵活性。反射允许在运行时确定和加载类,而不是在编译时,这使得Java能够像动态语言那样在运行时改变程序结构和变量类型。
屏蔽实现细节。反射允许开发者在不需要知道具体类的情况下操作对象,从而提供了更大的灵活性和便捷性。
实现动态加载类。反射允许在运行时动态加载类,而不需要在编译时将类引入到代码中。
动态创建对象。反射允许在运行时动态创建对象的实例,而不需要在编译时确定对象的类型。
访问私有成员。反射可以绕过访问权限限制,获取和修改私有成员变量,以及调用私有方法。
扩展性和灵活性。反射使得程序更加灵活和可扩展,可以在运行时动态地获取和操作类的信息。
类型分析和调试。反射可以用来分析类的结构,获取类的属性、方法等信息,以及在调试过程中动态获取和修改类的信息。
在框架中使用。在构建大型应用程序和框架时,反射是实现通用性和灵活性的关键工具,
例如,Spring框架就大量使用了反射机制。
四、注解
4.1 注解的概念
Annotation表示注解,是JDK1.5的新特性
注解的主要作用:对我们的程序进行标注,通过注解可以给类增加额外的信息,注解是给编译器或JVM看的,编译器或JVM可以根据注解
来完成对应的功能,让其他程序根据注解信息来决定怎么执行该程序。
Java中已经存在的注解:
① @Override:表示方法的重写
② @Deprecated:表示修饰的方法已过时
③ @SuppressWarnings("all"):压制警告 ,镇压警告
除此之外,还需要掌握第三方框架中提供的注解,后续框架知识会学习很多注解。
4.2 自定义注解
自定义注解单独存在是没有什么意义的,一般会跟反射结合起来使用,会用反射去解析注解。针对于注解,只要掌握别人已经写好的注解
即可。关于注解的解析,一般是在框架的底层已经写好了
自定义注解就是自己做一个注解来使用
public @interface 注解名称 {
public 属性类型 属性名() default 默认值;
}
这里的属性类型只可以是:基本数据类型、String、Class、注解、枚举以及以上类型的一维数组
使用:
@注解名(属性名1 = 值1, 属性名2 = 值2)
注意:
① 使用自定义注解时要保证注解每个属性都有值 ② 注解可以使用默认值
示例:
public @interface MyAnno {
public String name();
public int age();
}
特殊属性:
① value属性,如果只有一个value属性,使用value属性的时候可以省略value名称不写 ② 但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的
4.3 元注解
元注解就是注解的注解,也就是写在注解上面的注解
元注解常用的有两个:
① @Target:约束自定义注解只能在哪些地方使用 ② @Retention:申明注解的生命周期
@Target中可使用的值定义在ElementType中,常用值如下:
① Type:类、接口
② FIELD:成员变量
③ METHOD:成员方法
④ PARAMETER:方法参数
⑤ CONSTRUCTOR:构造器
⑥ LOCAL_VARIABLE:局部变量
@Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下:
① SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
② CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
③ RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
示例:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
public String age();
public String name() default "2";
}
4.4 注解的解析
注解的操作中经常需要进行解析,注解的解析就是判断类上,方法上、成员变量上是否存在注解,并把注解里面的内容给解析出来
如何解析注解,主要就是要解析“谁”上面的注解,就应该拿到“谁”。
比如说:要解析类上的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解。
再比如说:要解析成员方法上的注解,则应该先获取到该成员方法的Method对象,再通过Method对象解析其上面的注解。
所有的类成分Class,Method,Field,Constructor,都实现了AnnotatedElement接口,他们都拥有解析注解的能力
与注解解析相关的接口:
Annotation:注解的顶级接口
可以利用反射解析注解:
方法 | 说明 |
---|---|
Annotation[] getDeclaredAnnotations() | 获得当前对象上使用的所有注解,返回注解数组 |
T getDeclaredAnnotations(Class<T> annotationClass) | 根据注解类型获得对应注解对象 |
boolean isAnnotationPresent(Class<Annotation> annotationClass) | 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false |
案例:
限制注解使用的位置:类和成员方法上
指定注解的有效范围:一直到运行时
package cn.hxzy.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAno {
String value();
String name();
int b() default 10;
String [] arr();
}
定义类
package cn.hxzy.annotation;
import java.lang.annotation.Annotation;
/**
* @author:mengshujun
* @createTime: 2024-03-21 15:49:26 星期四
*/
@MyAno(value = "qq", name = "腾讯", b = 15,arr = {"x","y"})
public class MyClass {
@MyAno(value = "微信", name = "腾讯", b = 25,arr={"java","spring"})
public void select(int num) {
}
}
测试类
Class c = MyClass.class;
if(c.isAnnotationPresent(MyAno.class)){
//父类 父类对象=new 子类();
MyAno myAno = (MyAno) c.getDeclaredAnnotation(MyAno.class);
System.out.println(myAno.b());
System.out.println(myAno.name());
System.out.println(myAno.value());
System.out.println(Arrays.toString(myAno.arr()));
}
解析注解的技巧:
① 注解在哪个成分上,我们就先拿哪个成分对象 ② 比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解 ③ 比如注解作用在类上,则要获得该类的Class对象,再来拿上面的注解 ④ 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解
4.5 注解的使用场景
案例:模拟Junit框架
需求:定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行
分析:
① 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在 ② 定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有这个注解的方法不能执行
代码:
MyTest注解:
//表示着我们的注解可以写在方法上面,其他地方不能写
@Target(ElementType.METHOD)
//表示我们的注解可以在任意时期都存在
//如果写source,那么只能在源码阶段存在,利用反射无法解析,因为反射是通过字节码文件执行的
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
使用该注解的类(测试类):
public class MyTestDemo {
@MyTest
public void method1() {
System.out.println("method1");
}
@MyTest
public void method2() {
System.out.println("method2");
}
public void method3() {
System.out.println("method3");
}
}
类似Junit底层的实现:
public class MyAnnoDemo {
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
//1.获取main这个类的字节码文件对象
Class<?> clazz = Class.forName("p1.MyTestDemo");
//对象
main mtd = new MyTestDemo();
//2.获取所有方法
Method[] methods = clazz.getDeclaredMethods();
//3.遍历得到每一个方法
for (Method method : methods) {
//4.临时修改权限
method.setAccessible(true);
//5.判断当前方法上面有没有MyTest注解
if(method.isAnnotationPresent(MyTest.class)) {
method.invoke(mtd);
}
}
}
}