Java学习-junit测试、反射基础、注解

1. Junit单元测试

  • 测试分类:
    1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
    2. 白盒测试:需要写代码的。关注程序具体的执行流程。

junit使用: 白盒测试

  • 1.定义一个测试类(测试用例)

    • 测试类名:被测试的类类名Test
    • 包名:xxx.xxx.xxx.test
  • 2.定义一个测试方法可以独立运行

    • 方法名: test测试的方法名
    • 返回值: void (测试很多我们是不需要观察返回值的)
    • 参数列表: 空参
  • 3 给方法加上@Test 就可以独立运行, 但是首先你必须先导入junit环境

  • 判定结果:

    • 红色: 失败
    • 绿色:成功
    • 一般我们会使用断言操作来处理结果
      • Assert.assertEquals(期望的结果,运算的结果);

在这里插入图片描述

  • 补充:
    • @Before:
      • 修饰的方法会在测试方法之前被自动执行
    • @After:
      • 修饰的方法会在测试方法执行之后自动被执行

示例代码:

  • 包的结构
    在这里插入图片描述
/**
 * 写一个计算机方法的类
 * 如何使使用Junit单元测试
 */
public class Calculator {
    /**
     * 加法
     * @param a
     * @param b
     * @return
     */
    public int add(int a, int b) {
        return a + b;
    }

    /**
     * 减法
     * @param a
     * @param b
     * @return
     */
    public int sub(int a, int b) {
        return a - b;
    }
}


//编写测试类
public class CalculatorTest {

    /*
        有时候测试的时候,会需要一些资源的申请,我们可以在方法上使用@befor来修饰即可
     */
    @Before
    public void init(){
        System.out.println("资源初始化-----init");
    }

    @Test
    public void testAdd(){
        Calculator calculator = new Calculator();
        int result = calculator.add(1,3);

        /*
        很多时候我们看输出是无法确定结果,这里需要断言来配合使用
         */
        Assert.assertEquals(4,result);
    }

    @After
    public void close(){
        System.out.println("释放资源====== close");
    }
}

2. 反射

  • 反射: 框架设计的灵魂

    • 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码
    • 反射:将类的各个组成部分封装为其他对象,这就是反射机制
      • 好处:
        1. 可以在程序运行过程中,操作这些对象
        2. 可以解耦,提高程序的可扩展性。
  • Java程序在计算机中经历的阶段: 三个阶段

在这里插入图片描述

2.1获取Class对象的方式

public class reflesDemo {
    public static void main(String[] args) throws Exception {
        //实现获取class类的几种方式
        /*
            Error:(20, 37) java: 未报告的异常错误java.lang.ClassNotFoundException; 必须对其进行捕获或声明以便抛出
            发生这种异常,一定是包名错了或者是类名错了
         */
        //1.第一种获取类的方式 通过Class.forName() 这个静态方法获取
        Class class1 = Class.forName("com.gy.reflex.demo1.Person");
        System.out.println(class1);//class com.gy.reflex.demo1.Person

        //第二种获取class的方, 通过类名.class 获取class
        Class<Person> personClass = Person.class;
        System.out.println(personClass);//class com.gy.reflex.demo1.Person

        //第三种方式是通过对象名.getClass() 获取Class文件
        Person person = new Person();
        Class<? extends Person> aClass = person.getClass();
        System.out.println(aClass);//class com.gy.reflex.demo1.Person

    }
}

  • 第一种方式多用于配置文件,将类名定义在配置文件中。读取文件,加载类
  • 第二种方式多用于参数的传递
  • 第三种方式多用于对象的获取字节码的方式

2.2.Class的一些方法介绍

/*
               ClassLoader	getClassLoader():返回类的类加载器。

//返回类的成员对象方法
               Field	getField(String name):返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段 类对象。
               Field[]	getFields():返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段 类对象。
               Field getDeclaredField(String name):返回一个 Field对象,它反映此表示的类或接口的指定已声明字段 类对象。
               Field[]	getDeclaredFields():返回的数组 Field对象反映此表示的类或接口声明的所有字段 类对象。

//返回类的构造器方法
                Constructor<T>	getConstructor(类<?>... parameterTypes):返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数。
                Constructor<?>[]	getConstructors():返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象。
                Constructor<T>	getDeclaredConstructor(类<?>... parameterTypes):返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 类函数。
                Constructor<?>[]	getDeclaredConstructors():返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组 类 。


//返回类的成员方法
                Method getMethod(String name, 类<?>... parameterTypes):返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 类对象。
                Method getMethods():返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 类对象,包括那些由类或接口和那些从超类和超接口继承的声明。
                Method getDeclaredMethod(String name, 类<?>... parameterTypes):返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 类对象。
                Method[] getDeclaredMethods():返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
         */

2.2示例运用的相关类

  • 收我们需要创建一个Person类
package com.gy.reflex.demo1;

public class Person {
    private String name;
    private int age;


    public String a;
    protected String b;
    String c;
    private String d;

    public Person() {

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    public void eatFood(String foodName){
        System.out.println("eat=======" + foodName);
    }

    private String eatFood2(String foodName) {
        System.out.println("eat=======" + foodName);

        return foodName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", a='" + a + '\'' +
                ", b='" + b + '\'' +
                ", c='" + c + '\'' +
                ", d='" + d + '\'' +
                '}';
    }
}

2.3 反射获取Class对象的Field

public class FieldDemo  {
    /*
         Field	getField(String name):返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段 类对象。
         Field[] getFields():返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段 类对象。
         Field getDeclaredField(String name):返回一个 Field对象,它反映此表示的类或接口的指定已声明字段 类对象。
         Field[]	getDeclaredFields():返回的数组 Field对象反映此表示的类或接口声明的所有字段 类对象。
     */
    public static void main(String[] args) throws Exception {
        //1.首先我们需要先获取Class对象
        Class<Person> personClass = Person.class;

        //Field	getField(String name):返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段 类对象。
        Field a = personClass.getField("a");
        System.out.println(a);//public java.lang.String com.gy.reflex.demo1.Person.a
        System.out.println("-----------------");

        //Field[] getFields():返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段 类对象。
        //获取所有的成员变量
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }//public java.lang.String com.gy.reflex.demo1.Person.a 打印类中所有共有的成员变量
        System.out.println("-----------------");

        //Field getDeclaredField(String name):返回一个 Field对象,它反映此表示的类或接口的指定已声明字段 类对象。
        Field name = personClass.getDeclaredField("name");//可以获取类中指定名称成员变量,不管是共有的还是私有的
        /*
            拿到成员变量 我们可以给成员变量赋值
            当我们该私有的成员变量赋值的时候,会抛出异常Exception in thread "main" java.lang.IllegalAccessException:
            这时候我们可以使用setAccessible(): 强制反射, 当设置为true的时候我们也可以操作类中的私有成员变量
         */
        Person person = new Person();
        name.setAccessible(true);//强制反射方法
        //设置成员变量的值
        name.set(person,"张三");
        //获取成员变量的 值
        Object o = name.get(person);
        System.out.println(o);//张三

        System.out.println("-------------------");

        //Field[]	getDeclaredFields():返回的数组 Field对象反映此表示的类或接口声明的所有字段 类对象
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field field : declaredFields) {
            System.out.println(field);
        }
        /*
            打印结果:
            private java.lang.String com.gy.reflex.demo1.Person.name
            private int com.gy.reflex.demo1.Person.age
            public java.lang.String com.gy.reflex.demo1.Person.a
            protected java.lang.String com.gy.reflex.demo1.Person.b
            java.lang.String com.gy.reflex.demo1.Person.c
            private java.lang.String com.gy.reflex.demo1.Person.d
         */
    }
}

2.4反射获取Class对象的Constructor(构造器)方法

public class ConstructorDemo {
    public static void main(String[] args) throws Exception {
        //1.首先我们需要先获取Class对象
        Class<Person> personClass = Person.class;

        /*
        Constructor<T>	getConstructor(Class<?>... parameterTypes):返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数。
        Constructor<?>[]	getConstructors():返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象。
        Constructor<T>	getDeclaredConstructor(类<?>... parameterTypes):返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 类函数。
        Constructor<?>[]	getDeclaredConstructors():返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组 类 。
         */

        //1.Constructor<T>	getConstructor(类<?>... parameterTypes):返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数
        //获取公共的构造方法 方法参数 是传递构造方法的参数, 单是是每个参数类型的Class对象
        Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
        System.out.println(constructor);//public com.gy.reflex.demo1.Person(java.lang.String,int)

        //如果我们需要获取无参的构造方法可以不传递参数,因为这个参数列表是 可变参数
        Constructor<Person> constructor1 = personClass.getConstructor();
        System.out.println(constructor1);//public com.gy.reflex.demo1.Person()

        //当我们拿到构造方法之后,我们可以用来创建对象
        Person person = constructor.newInstance("张三", 22);
        System.out.println(person);//Person{name='张三', age=22, a='null', b='null', c='null', d='null'}

        //这里出了通过这个直接创建对象之后,其实Class中还有一个静态方法创建对象
        Person person1 = personClass.newInstance();
        System.out.println("person1======" + person1);//person1======Person{name='null', age=0, a='null', b='null', c='null', d='null'}

        //其他两个方法都和Field的两个方法类似 都是获取构造构造方法,不管是私有还是共有修饰的
    }
}

2.5 反射获取Class对象的Method(方法)

public class MethodDemo {
    public static void main(String[] args) throws Exception {
        //1.首先我们需要先获取Class对象
        Class<Person> personClass = Person.class;

        /*
        Method getMethod(String name, 类<?>... parameterTypes):返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 类对象。
        Method getMethods():返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 类对象,包括那些由类或接口和那些从超类和超接口继承的声明。
        Method getDeclaredMethod(String name, 类<?>... parameterTypes):返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 类对象。
        Method[] getDeclaredMethods():返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
         */

        //Method getMethod(String name, 类<?>... parameterTypes):返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 类对象。
        //获取名称name的公共方法 第一个参数的名称是方法的名称 第二个是方法对应的参数类型的Class对象
        //获取一个无参的方法
        Method eat = personClass.getMethod("eat");
        System.out.println(eat);//public void com.gy.reflex.demo1.Person.eat()

        //获取一个带参的方法
        Method eat1 = personClass.getMethod("eatFood", String.class);
        System.out.println(eat1);//public void com.gy.reflex.demo1.Person.eatFood(java.lang.String)
        //那么我们获取方法,我们需要执行方法public Object invoke(Object obj, Object... args)
        //第一个是方法来自于哪个对象, 第二个 是方法的参数列表,是个可变参数
        Person person = new Person();
        eat1.invoke(person,"苹果");//eat=======苹果

        //如果我们需要获取私有方法,并调用
        Method eatFood2 = personClass.getDeclaredMethod("eatFood2", String.class);
        //因为私有方法 理论来说是不可以被访问调用, 如果我们需要调用 需要强制反射
        eatFood2.setAccessible(true);
        eatFood2.invoke(person,"饭");//eat=======饭

        //接下来我们获取所有方法
        Method[] methods = personClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        /*
            打印结果: 因为Person类是继承自Object类的 ,所以 这里打印的方法不仅包含Person中的方法,还包括Object中的一些方法
        public void com.gy.reflex.demo1.Person.setAge(int)
        public int com.gy.reflex.demo1.Person.getAge()
        public void com.gy.reflex.demo1.Person.eatFood(java.lang.String)
        public void com.gy.reflex.demo1.Person.eat()
        public java.lang.String com.gy.reflex.demo1.Person.toString()
        public java.lang.String com.gy.reflex.demo1.Person.getName()
        public void com.gy.reflex.demo1.Person.setName(java.lang.String)
        public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
        public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
        public final void java.lang.Object.wait() throws java.lang.InterruptedException
        public boolean java.lang.Object.equals(java.lang.Object)
        public native int java.lang.Object.hashCode()
        public final native java.lang.Class java.lang.Object.getClass()
        public final native void java.lang.Object.notify()
        public final native void java.lang.Object.notifyAll()

         */

        //Method[] getDeclaredMethods():返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 类对象,包括公共,保护,默认(包)访问和私有方法,但
        //此方法是获取类中的所有方法,不管是共有 还是私有,获取步骤和上述一样,这里不做解释了
    }
}

2.6 案例

2.6.1 需求分析

/*
    需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
        实现:
            1.配置文件
            2.反射
        步骤:
            1.将需要创建的对象的全类名h和需要执行的方法定义在配置文件中
            2.在程序中加载读取配置文件
            3.使用反射加载类文件进内存
            4.创建对象
            5.执行方法
 */
  • 运用配置文件,我们首先需要了解Java中的Properties的用法

2.6.2 Properties的用法

  • 配置文件的位置和内容
    在这里插入图片描述
    在这里插入图片描述

  • Properties:主要用于读取Java的配置文件,在Java中,其配置文件常为.properties文件,格式为文本文件,文件的内容的格式是“键=值”的格式,文本注释信息可以用"#"来注释。

/*
   Properties的继承体系 :Properties类继承自Hashtable
                     java.lang.Object
                        java.util.Dictionary<K,V>
                            java.util.Hashtable<Object,Object>
                                java.util.Properties

   Properties的几个主要方法:
        String	getProperty(String key):使用此属性列表中指定的键搜索属性。用指定的键在此属性列表中搜索属性。也就是通过参数key ,得到 key 所对应的 value。
        load ( InputStream inStream),从输入流中读取属性列表(键和元素对)。通过对定的文件(比如说上面的test.properties 文件)进行装载来获取该文件中的所有键 - 值对。以供 getProperty ( String key) 来搜索。
        setProperty ( String key, String value) ,调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键 - 值对。
        store ( OutputStream out, String comments),以适合使用 load 方法加载到Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。与load 方法相反,该方法将键 - 值对写入到指定的文件中去。
        clear (),清除所有装载的 键 - 值对。该方法在基类中提供。
        Enumeration<?>	propertyNames():返回此属性列表中所有键的枚举,包括默认属性列表中的不同键,如果尚未从主属性列表中找到相同名称的键。

    常见的读取Properties配置文件的方法是通过java.lang.Class类的getResourceAsStream(String name)方法来实现

    InputStream in = getClass().getResourceAsStream("资源Name");作为我们写程序的,用此一种足够。
    或者下面这种也常用:
    InputStream in = new BufferedInputStream(new FileInputStream(filepath));
 */
public class PropertiesDemo {
    public static void main(String[] args) throws IOException {
        //获取test.properties的路劲 ./表示当前项目目录 ../相对于当前目录的上一级目录
        String filePath = "./BasicStreng/src/test.properties";
        getTestPropertiesInfo(filePath);

        System.out.println("-------------------");
        //根据key获取对应的value值
        String value = getValueByKey("name");
        System.out.println("name=======" + value);

        System.out.println("--------------------");
        //往Properties配置文件中写入内容
        writeProperties(filePath,"zhangsan","22");

    }
    /*
        获取Properties中的全部信息
     */
    public static void getTestPropertiesInfo(String filePath) throws IOException {
        //创建一个Properties对象
        Properties properties = new Properties();
        //第一种方法创建一个字节输入流
        //InputStream inputStream = new BufferedInputStream(new FileInputStream(filePath));

        //第二种方法读取得到文件的inputSream流 public InputStream getResourceAsStream(String name) :查找具有给定名称的资源 返回一个inputSream流
        //getClassLoader() 类的加载器可以找到目录下的类文件把类加载到内存, 那么也可以找到相关目录下的 资源文件
        /*
            关于路径的补充问题,上述获得Properties配置文件中英文是通过,是通过直接获取字节流的方式来获取的,可以不用在乎中英文(PropertiesDemo.class.getClassLoader().getResourceAsStream("test.properties");)
            如果是通过Test.class.getClassLoader().getResource()方法,因为该方法返回值是URL,如果项目的目录中有中文命名,则获得的URL会出现乱码,
            所以使用String path=URLDecoder.decode(url.getFile(), "utf-8");
         */
        InputStream inputStream1 = PropertiesDemo.class.getClassLoader().getResourceAsStream("test.properties");

        //使用Properties中的load方法来加载文件
        properties.load(inputStream1);

        //得到配置文件中所有键的一个枚举(类似集合的遍历迭代器)
        // 该接口的功能由Iterator接口复制。 此外,Iterator还添加了一个可选的删除操作,并具有较短的方法名称。 新的实现应该考虑使用迭代器优先于枚举。
        Enumeration<?> enumeration = properties.propertyNames();
        while (enumeration.hasMoreElements()) {
            String key = (String) enumeration.nextElement();
            String property = properties.getProperty(key);
            System.out.println(key + "=====" + property);
        }
        /*
            打印结果:
            name=====JJ
            Weight=====4444
            sss=====我爱java
            long=====212
            Height=====3333

            结果表示:配置文件中不可以使用中文,中文打印出来是乱码, 并且用# 注释的内容也没有打印出来
         */
    }
    /*
        根据key读取Properties中value
     */
    public static String getValueByKey(String key) throws IOException {
        //创建一个Properties对象
        Properties properties = new Properties();
        InputStream inputStream1 = PropertiesDemo.class.getClassLoader().getResourceAsStream("test.properties");

        //使用Properties中的load方法来加载文件
        properties.load(inputStream1);

        String property = properties.getProperty(key);

        return property;
    }

    /*
        往Properties配置文件中写入数据
     */
    public static void writeProperties(String filePath, String key, String value) throws IOException {
        //创建一个Properties对象
        Properties properties = new Properties();
        InputStream inputStream = new BufferedInputStream(new FileInputStream(filePath));
        //InputStream inputStream1 = PropertiesDemo.class.getClassLoader().getResourceAsStream("test.properties");

        //使用Properties中的load方法来加载文件
        properties.load(inputStream);
        //因为Properties从继承Hashtable时, put种putAll方法可应用于Properties对象。
        // 强烈不鼓励使用它们,因为它们允许调用者插入其键或值不是Strings 。 应该使用setProperty方法

        //往文件中写入数据时 ,应该使用setProperty()方法
        OutputStream outputStream = new FileOutputStream(filePath);
        properties.setProperty(key,value);
        //把设置的数据写入到文件中
        properties.store(outputStream,"Update" + key + "name");
    }
}

2.6.3案例代码

  • 配置文件的位置和内容
    在这里插入图片描述
    在这里插入图片描述
public class ExampleDemo {
    public static void main(String[] args) throws Exception {
        //首先我们需要创建配置文件
        //Properties
        Properties properties = new Properties();
        InputStream inputStream = ExampleDemo.class.getClassLoader().getResourceAsStream("pro.properties");
        properties.load(inputStream);

        //然后根据key值获取Value值
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");

        //首先我们需要把className的文件加载到内存
        Class<?> aClass = Class.forName(className);
        
        //获取类中的方法对象 这里默认方法都是共有 且没有参数
        Method method = aClass.getMethod(methodName);
        method.invoke(aClass.newInstance());
    }
}

3. 注解

3.1 概述

  • 注解:

    • 概念:说明程序给计算机看的
    • 注释:用文字描述的,给程序员看的
  • 定义:注解(Annoatation)也叫元数据。一种代码级别的说明,它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

  • 概念描述:

    1. JDK1.5之后的新特性
    2. 说明程序的
    3. 使用注解:@注解名称

常见的使用:

	@Override
    public String toString() {}
  • 作用分类:

    • ①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
      • 控制台使用命令: javadoc xxx.java文件 就生成了doc文档
    • ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】
    • ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
  • JDK中预定义的一些注解

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

3.2 自定义注解

3.2.1自定义注解的格式

		元注解
        public @interface MyAnnotation {
            属性列表(实际是抽象方法)
        }

// 系统@override的注解源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

注解的本质: 本质上是一个接口,该接口默认继承实现Annotation接口

public interface MyAnnotation extends java.lang.annotation.Annotation {}
  • 注解中的属性
    • 1.属性的返回值有下列取值
      • 基本数据类型
      • String
      • 枚举
      • 注解
      • 以上类型的数组
    • 定义了属性,在使用时需要给属性赋值
      • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。int age() default 22; //基本数据类型
      • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
      • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
public @interface MyAnnotation {
    int age() default 22; //基本数据类型
    String name();//字符串(String)类型
//    TestEnum testEnum();//enum 枚举类型
//    MyAnnotation2 annotation2();//注解类型
//
//    int[] ages();
//    String names();
//    TestEnum[] testEnums();
//    MyAnnotation2[] annotation2s();
}

public class AnnotationDemo {
    @MyAnnotation(name = "sss")
    public void show(){

    }
}

3.2.2之定义注解的元注解

  • 元注解:用于描述注解的注解
    • @Target:描述注解能够作用的位置
      • ElementType取值:
      • TYPE:可以作用于类上
      • METHOD:可以作用于方法上
      • FIELD:可以作用于成员变量上
    • @Retention:描述注解被保留的阶段
      • public enum RetentionPolicy { SOURCE(源代码阶段), CLASS(字节码文件阶段), RUNTIME(编译阶段) }
    • @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
    • @Documented:描述注解是否被抽取到api文档中
    • @Inherited:描述注解是否被子类继承
//自定义注解对象

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    int age();
    String name();
}


//测试代码
public class AnnotationDemo {
    @MyAnnotation(age = 22, name = "zhangsan")
    public void show(){
        System.out.println("show-------");
    }
    public void show2() {
        System.out.println("show2----------");
    }

    public static void main(String[] args) throws  Exception {
        //怎么解析注解?获取注解的属性值
        //1.获取定义定义的位置对象
        Class<AnnotationDemo> annotationDemoClass = AnnotationDemo.class;

        //2.获取该class中的所有方法对象
        Method[] methods = annotationDemoClass.getMethods();

        //判断哪些方法是添加了注解的
        for (Method method : methods) {
            //判断该方法有没有被注解
            if (method.isAnnotationPresent(MyAnnotation.class)){
                //如果有,那么我么需要调用方法
                method.invoke(annotationDemoClass.newInstance());//show-------

                //如果方法被注解了, 我们获取注解对象
                MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
                /*
                    获取注解的实质其实就是在内存中生成了一个该注解接口的子类实现对象,获取属性值调用子类对象实现的方法,
                    你在注解上设置的值@MyAnnotation(age = 22, name = "zhangsan")

		            public class ProImpl implements Pro{
		                public String className(){
		                    return "cn.itcast.annotation.Demo1";
		                }
		                public String methodName(){
		                    return "show";
		                }
		            }
                 */

                //获取注解中的属性值
                int age = annotation.age();
                String name = annotation.name();

                System.out.println(name + "=====" + age);//zhangsan=====22
            } else  {
                //如果没有注解拿我们输出方法名称
                System.out.println(method);//public void com.gy.annotation.demo2.AnnotationDemo.show2()
                //还有一些Objec中的方法 就不说了
            }
        }
    }
}

3.3使用注解来实现反射、配置文件实现的案例

//自定义注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String className();
    String methodName();
}

//实现类
@TestAnnotation(className = "com.gy.reflex.demo1.Person", methodName = "eat")
public class AnnotationDemo {
    public static void main(String[] args) throws Exception {
        //呼气class对象
        Class<AnnotationDemo> annotationDemoClass = AnnotationDemo.class;

        //然后在获取注解的属性对象
        TestAnnotation annotation = annotationDemoClass.getAnnotation(TestAnnotation.class);
        String className = annotation.className();
        String methodName = annotation.methodName();

        //然后根据指定的类名和方法名获取对象,并调用该类对象的方法
        Class<?> aClass = Class.forName(className);

        //这里默认是 公共没有参数列表的方法
        Method method = aClass.getMethod(methodName);

        //调用方法
        method.invoke(aClass.newInstance());
    }
}

3.4 案例- 实现一个简单测试运用

  • 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnottion {

}
  • 被测试的类
public class Calculation {

    @TestAnnottion
    public int add(){
        return 1 + 3;
    }

    @TestAnnottion
    public int sub(){
        return 1 - 3;
    }

    @TestAnnottion
    public int mul(){
        return 1 * 3;
    }

    @TestAnnottion
    public int div(){
        return 2 / 0 ;
    }

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

  • 测试类代码:
/*
    简单的测试框架
    当测试类主方法运行后,或自定去检测被注解标识的方法
 */
public class CalculationTest {
    public static void main(String[] args) throws IOException {
        //创建对象
        Calculation calculation = new Calculation();
        Class<? extends Calculation> aClass = calculation.getClass();

        //获取所有的方法 默认的方法都是共有的
        Method[] methods = aClass.getMethods();
        int number = 0;//记录发生异常的次数
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("bug.text"));
        for (Method method : methods) {
            if (method.isAnnotationPresent(TestAnnottion.class)) {
                //如果这个方法被注解,拿么执行方法,反之不用管
                try {
                    method.invoke(calculation);
                } catch (Exception e) {
                    number ++;
                    //如果发生异常,我们需要把异常写入文件
                    bufferedWriter.write(method.getName() + "方法出现异常了");
                    bufferedWriter.newLine();
                     //getCause() 获取异常类Throwabl,然后在获取异常的相关信息
                    bufferedWriter.write("异常的名称:" + e.getCause().getClass().getSimpleName());
                    bufferedWriter.newLine();
                    bufferedWriter.write("异常的原因" + e.getCause().getMessage());
                    bufferedWriter.newLine();
                    bufferedWriter.write("----------------");
                }
            }
        }
        bufferedWriter.newLine();
        bufferedWriter.write("本次测试一共出现" + number + "次异常");
        bufferedWriter.close();

    }
}

  • 测试结果
    在这里插入图片描述

  • 小结:

    • 以后大多数时候,我们会使用注解,而不是自定义注解
    • 注解给谁用?
      • 编译器
      • 给解析程序用
    • 注解不是程序的一部分,可以理解为注解就是一个标签

3.6 动态代理

3.6.1 动态代理的设计模式介绍

如何为Java创建一个代理对象?

  • java.lang,reflect.Proxy类: 提供了为对象生产代理对象的方法
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
  • 参数一: 用于指定哪个类加载器, 区加载生成的代理类
  • 参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
  • 参数三: 用来指定生成的代理对象要干什么事情

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值