反射与注解

反射&注解

文章目录

  • 反射&注解
  • 第一章 反射
    • 1.1 Class对象
    • 1.2 Class常用功能
      • Field类
      • Constructor类
      • Method类
    • 1.3 案例:一个”简单的框架“
      • 代码实现
  • 第二章 注解
    • 2.1 预定义注解
    • 2.2 自定义注解
      • 属性
      • 元注解
      • 解析注解
    • 2.3 案例:简单的测试框架

第一章 反射

  • 定义:Java反射机制就是把类中的各个组成部分,映射成其他对象,这些对象能获取这个类的所有属性和方法;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
  • 用途:框架设计的灵魂,最大的用途是用来制作框架。框架简述就是半成品软件,可以在框架的基础上进行软件开发,简化编码。

1.1 Class对象

Java认为这些编译后的class文件,这种事物也是一种对象,它也给抽象成了一种类,这个类就是Class

获取方式如下:

  • Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象,多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
  • 类名.class:通过类名的属性class获取,多用于参数的传递。
  • 对象.getClass():getClass()方法在Object类中定义着,多用于对象的获取字节码的方式。

代码示例:

public class ReflectDemo01 {

    public static void main(String[] args) throws Exception {

        Class cls1 = Class.forName("com.slxy.domain.Person");
        System.out.println(cls1); //class com.slxy.domain.Person

        Class cls2 = Person.class;
        System.out.println(cls2); //class com.slxy.domain.Person

        Person p = new Person();
        Class cls3 = p.getClass();
        System.out.println(cls3); //class com.slxy.domain.Person

        //地址相同
        System.out.println(cls1 == cls2); //true
        System.out.println(cls1 == cls3); //true
    }
}

结论:

  • 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。

1.2 Class常用功能

Field类

Field 代表类的成员变量(成员变量也称为类的属性)。

  • Field[] getFields():获取所有public修饰的成员变量。
  • Field getField(String name):获取指定名称的 public修饰的成员变量。
  • Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符。
  • Field getDeclaredField(String name):获取所有的成员变量,不考虑修饰符。

Person类(准备反射的类):

public class Person {
    
    //Person类有不同修饰符的属性
    private String name;
    private int age;

    public int a;
    int b;
    protected int c;

    public void eat() {
        System.out.println("eat...");
    }

    public void eat(String food) {
        System.out.println("eat..." + food);
    }

   //get,set方法已省略...
}

实现类:

public class ReflectDemo02 {

    public static void main(String[] args) throws Exception {

        //获取Person的Class对象 Person类
        Class personClass = Person.class;

        //获取Person类的所有的以public修饰的成员变量
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            System.out.println(field);  //public int com.slxy.domain.Person.a
        }

        //获取Person类的成员变量a
        Field a = personClass.getField("a");
        Person p = new Person();
        //获取a属性的值
        Object value = a.get(p);
        System.out.println(value);  //0
        //更改a属性的值
        a.set(p,18);
        System.out.println(p);  //p=18

        //获取所有的成员变量,不考虑修饰符。
        Field[] all = personClass.getDeclaredFields();
        for (Field field : all) {
            System.out.println(field);
            //private java.lang.String com.slxy.domain.Person.name
            //private int com.slxy.domain.Person.age
            //public int com.slxy.domain.Person.a
            //int com.slxy.domain.Person.b
            //protected int com.slxy.domain.Person.c
        }
        //获取成员变量,不考虑修饰符。
        Field name = personClass.getDeclaredField("name");
        
        //忽略权限修饰符的安全检查(也称暴力反射)
        //因为Person属性是私有的,如果不忽略,会非法访问异常
        name.setAccessible(true);
        
        name.set(p,"张三");
        System.out.println(name.get(p));  //张三
    }
}

小贴士

  • 带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。

Constructor类

Constructor代表类的构造方法,newInstance()会根据传递的参数创建类的对象。

  • Constructor[] getConstructors()
  • Constructor getConstructor(类... parameterTypes)
  • Constructor getDeclaredConstructor(类... parameterTypes)
  • Constructor[] getDeclaredConstructors()

代码示例:

public class ReflectDemo03 {

    public static void main(String[] args) throws Exception {

        //获取Person的Class对象
        Class<Person> personClass = Person.class;
        
		//创建有参对象
        Constructor con1 = personClass.getConstructor(String.class, int.class);
        System.out.println(con1);
        Object person1 = con1.newInstance("张三", 23);
        System.out.println(person1);
        
        //创建空参对象
        Constructor con2 = personClass.getConstructor();
        Object person2 = con2.newInstance();
        System.out.println(person2);
        
        //简化写法
        Person person3 = personClass.newInstance();
        System.out.println(person3);
    }
}

Method类

Method代表类的方法,invoke()传递object对象及参数调用该对象对应的方法。

  • Method[] getMethods()
  • Method getMethod(String name, 类... parameterTypes)
  • Method[] getDeclaredMethods()
  • Method getDeclaredMethod(String name, 类... parameterTypes)

代码示例:

public class ReflectDemo04 {
    public static void main(String[] args) throws Exception {

        //获取Person的Class对象
        Class<Person> personClass = Person.class;

        //根据指定名称获取指定方法
        Method eat1 = personClass.getMethod("eat");
        Person p = new Person();
        eat1.invoke(p); //eat...

        Method eat2 = personClass.getMethod("eat", String.class);
        eat2.invoke(p, "food"); //eat...food

        //获取所有pubilc修饰的方法
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
            //获取方法名称
            System.out.println(method.getName());
        }

        //获取类名
        String name = personClass.getName();
        System.out.println(name); //com.slxy.domain.Person
    }
}

1.3 案例:一个”简单的框架“

  • 需求:写一个“框架“,不能改变该类的任何代码的前提下,可以帮我们创建任意对象,实现任意方法。
  • 实现方式:配置文件+反射

代码实现

配置文件pro.properties

ClassName=com.slxy.domain.Student
MethodName=sayHello

对象类Student

public class Student {
    public void sayHello() {
        System.out.println("Hello");
    }
}

实现类

public class ReflectTest {

    public static void main(String[] args) throws Exception {

        //加载读取配置文件
        Properties pro = new Properties();
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        pro.load(is);
        String ClassName = pro.getProperty("ClassName");
        String MethodName = pro.getProperty("MethodName");

        //使用反射技术来加载类文件进内存
        Class cls = Class.forName(ClassName);
        //创建对象
        Constructor constructor = cls.getConstructor();
        Object stu = constructor.newInstance();
        //执行方法
        Method method = cls.getMethod(MethodName);
        method.invoke(stu);
    }
}

第二章 注解

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

作用分类

  • 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
  • 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
  • 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

2.1 预定义注解

  • @Override :检测被该注解标注的方法是否是继承自父类(接口)的
  • @Deprecated:该注解标注的内容,表示已过时
  • @SuppressWarnings:压制警告

代码示例:

//压制所有警告
@SuppressWarnings("All")
public class AnnotationDemo02 {

    @Override
    public String toString() {
        return super.toString();
    }

    @Deprecated
    public void show() {
        //已过时
    }
    
    public void demo() {
        show();
    }
}

2.2 自定义注解

自定义注解格式如下:

元注解
public @interface Anno01 {
    属性列表;
}

通过javap命令反编译注解的字节码文件:

Compiled from "Anno01.java"
public interface Anno01 extends java.lang.annotation.Annotation {
}

结论

注解本质上就是一个接口,该接口默认继承Annotation接口。

属性

属性就是接口(注解)中的抽象方法,且返回值类型有限制:

  1. 可以使用的返回值类型:

    • 基本数据类型
    • String
    • 枚举
    • 注解
    • 以上类型的数组
  2. 定义了属性,在使用时需要给属性赋值:

    • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
    • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
    • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略。

代码示例:

//注解
public @interface MyAnno01 {

    int max() default 1;
    String value();
    //enum Person{};
    MyAnno02 anno02();
    String[] values();
}

实现类:

@MyAnno01(value = "All", anno02 = @MyAnno02(),values = {"a","b"})
public class Worker {
}

元注解

用于描述注解的注解。

  • @Target:描述注解能够作用的位置。
//元注解@Target源码分析
package java.lang.annotation;

@Documented	//可以被生成api文档
@Retention(RetentionPolicy.RUNTIME)	//注解保留到运行时阶段
@Target({ElementType.ANNOTATION_TYPE})	//注解的作用位置为:注解类(ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value(); //注解的属性为enum枚举类型,且名称为value,可以省略value。
}
  • @Retention:描述注解被保留的阶段。
  • @Documented:描述注解是否被抽取到api文档中。
  • @Inherited:描述注解是否被子类继承。

代码示例:

//自定义的注解
//注解的作用位置为方法,类,成员属性
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
//注解保留到运行时阶段
@Retention(RetentionPolicy.RUNTIME)
//注解可以被api文档抽取
@Documented
//注解会被子类继承
@Inherited
public @interface MyAnno03 {
	//这是定义的自定义注解
}

实现类:

//因为注解的Target描述中有ElementType.TYPE,所以可以作用于类上
@MyAnno03
public class Worker {

    //因为ElementType.FIELD,所以可以作用于变量上
    @MyAnno03
    String str;
    
    //因为ElementType.METHOD,所以可以作用于方法上
    @MyAnno03
    public void show(){
    }
}

反射与注解_第1张图片

小贴士

使用javadoc命令,可以将java文件生成api文档,java文件上类或方法的注释说明,也会被抽取到api文档。

解析注解

注解的解析就是通过反射获取注解中定义的属性值。

  • Class对象的getAnnotation():用来获取注解,生成了一个该注解接口的子类对象。

案例:对前面反射的案例进行改进,使用注解的方式代替读取配置文件:

//注解只能作用于类上
@Target(ElementType.TYPE)
//注解保留到运行时阶段
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {

    String className();
    String methodName();
}

实现类:

@Pro(className = "com.slxy.domain.Student",methodName = "sayHello")
public class ReflectTest {

    public static void main(String[] args) throws Exception {

        //获取字节码对象
        Class<ReflectTest> reflectTestClass = ReflectTest.class;
        //获取注解对象
        Pro annotation = reflectTestClass.getAnnotation(Pro.class);
        //其实就是在内存中生成了一个该注解接口的子类对象
        /*
        public class ProImpl implements Pro{
            public String className(){
                return "com.slxy.domain.Student";
            }
            public String methodName(){
                return "sayHello";
            }
        }
        */
        //调用注解中的属性,获取返回值
        String ClassName = annotation.className();
        String MethodName = annotation.methodName();

        //使用反射技术来加载类文件进内存
        Class aClass = Class.forName(ClassName);
        //创建对象
        Constructor constructor = aClass.getConstructor();
        Object o = constructor.newInstance();
        //执行方法
        Method method = aClass.getMethod(MethodName);
        method.invoke(o);   //Hello
    }
}

2.3 案例:简单的测试框架

需求:当主方法执行后,会自动自行被检测的所有方法(加了Check注解的方法),判断方法是否有异常,并记录到文件bug.txt中。

注解Check

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}

被测试的类Calculator:

public class Calculator {

    @Check
    public int add() {
        return 1 + 2;
    }

    @Check
    public int sub() {
        return 1 - 2;
    }

    @Check
    public int mul() {
        return 1 * 2;
    }

    @Check
    public int div() {
        return 1 / 0;
    }
}

实现类:

public class TestCheck {

    public static void main(String[] args) throws IOException {

        Calculator c = new Calculator();
        //获取字节码文件对象
        Class cls = c.getClass();
        //获取所有方法
        Method[] methods = cls.getMethods();
        //异常数量
        int error = 0;
        //创建字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        bw.write("检测的文件"+cls.getSimpleName()+".java");
        bw.newLine();
        for (Method method : methods) {
            if (method.isAnnotationPresent(Check.class)) {
                try {
                    //执行方法
                    method.invoke(c);
                } catch (Exception e) {
                    //记录错误数
                    error++;
                    //将错误信息写入文件
                    bw.write("错误的方法:" + method.getName());
                    bw.newLine();
                    bw.write("错误的名称:" + e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("错误的信息:" + e.getCause().getMessage());
                    bw.newLine();
                }
            }
        }
        bw.write("本次测试一共的错误的数量:" + error);
        bw.flush();
        bw.close();
    }
}

测试结果bug.txt

检测的文件Calculator.java
错误的方法:div
错误的名称:ArithmeticException
错误的信息:/ by zero
本次测试一共的错误的数量:1

你可能感兴趣的:(反射,java)