反射与注解

前言

反射和注解在平时的业务开发中极少会用到,这些技术都是属于学习框架或者开发框架时的底层源码,学习这些知识的目的是为大家今后

理解框架甚至自己开发框架做铺垫的。

一、认识反射

反射就是:加载类,并允许以编程方式解剖类中的各种成分(成员变量、方法、构造器等),

指的就是把某个类的整个字节码文件加载到内存中,并允许以编程的方式解析出类中的各种成员,比如说把类中的成员变量给解析出来,

把类中的构造器解析出来,解析出来之后就可以对它们进行相应的操作了。

反射还学什么?

学习获取类的信息、操作它们:

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);
            }
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值