Java反射与注解快速入门

所有笔记个人从以下视频总结得到 https://www.bilibili.com/video/BV1J4411877m

本文转自 个人博客

JUnit是一个Java语言的单元测试框架,Unit有它自己的JUnit扩展生态圈,多数Java的开发环境都已经集成了JUnit作为单元测试的工具。

Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。

JUnit

1、定义测试类:

测试类名:ClassNameTest
包名:xxx.xxx.test

2、定义方法:

方法名:testMethodName()
返回值:void
参数列表:空参

3、方法添加 @Test注解,并且导入 JUnit 环境

4、判断测试结果,第一个参数为期望结果,第二个参数为测试结果

 Assert.assertEquals(expectedValue, testResult);

6、初始化测试资源与关闭资源,前者依靠 @Before注解,后者依赖 @After注解

/* 在测试之前会执行,无论测试是否通过 */
@Before
public void init() {
    // Initialization...
}

/* 在测试之后会执行,无论测试是否通过 */
@After
public void close() {
    // Termination...
}

反射

image-20200908205354234

1、反射带来的好处:

a. 程序运行中操作对象

b. 解耦,提高扩展性

2、获取 Class 对象方法

Class.forName("全类名");  // 将字节码文件加载进内存,返回class对象
类名.class;  // 通过类名的属性class获取
对象.getClass(); 

第一种方式多用于配置文件,把类名信息放到一个文件中

第二种方式多用于传参

第三种方式就是单纯获取字节码对象

3、Class 类中的方法

这里不废话,主要就是各种 get 方法,会获取到对应的 FieldConstructor,和 Method等数组或对象,然后调用其内部的一些方法

先说 Field:

获取公开的内容:

classObj.getXXX();

获取到全部内容:

classObj.getDeclaredXXX();

忽略访问权限:

classObj.setAccessible(true);

然后是 Constructor:

获取到指定的构造函数:

classObj.getConstructor(String.class, int.class);  // 参数传形参的字节码文件

利用构造函数对象创建对象:

constructor.newInstance(...);
/* 获取到的 constructor 是什么传参顺序,这里就用什么顺序
 * 比如上面的是先 String,然后再 int,那这里也按照这个顺序填写
 * (空参构造一般用 classObj.newInstance() 来创建对象)
 */

其他的 declared 的,也可以通过 setAccessible来使用到,这里就不废话了

最后是 Method:

获取方法对象:

classObj.getMethod("methodName", ...);  // 后面方法对应的参数的字节码文件

调用方法:

method.invoke(obj, ...);
/* 这里,method 是通过字节码获取到的 Method 对象
 * 第一个参数为需要调用该方法的实例对象,后面则可以传入实际的参数内容 
 */

反射小案例

利用反射来实现一个可以创建任意类,并且可以调用其内部任意方法的小框架

package com.guanyu.reflect;

import java.lang.reflect.Method;
import java.util.Properties;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Properties pro = new Properties();
		// 通过 ClassLoader 来获取配置文件路径,并直接转换成 Stream
        ClassLoader clsLoader = ReflectTest.class.getClassLoader();
        pro.load(clsLoader.getResourceAsStream("pro.properties"));
		// 获取类名和方法名
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
		// 创建 Class 对象,创建 Method 对象
        Class cls = Class.forName(className);
        Method m = cls.getMethod(methodName);
		// 创建实例对象,并通过 Method 对象来调用该实例对象的方法
        Object o = cls.newInstance();
        m.invoke(o);

    }
}

注解

概念∶说明程序的。给计算机看的
注释∶用文字描述程序的。给程序员看的

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

作用:

  1. 编译检查:让编译器实现基本的编译检查功能(e.g @Override)
  2. 编写文档:生成 Java 文档所需要的注解 (e.g @author)
  3. 代码分析:对代码进行分析(e.g 使用反射)

JDK常见自带

检查该方法是够是继承自父级

@Override

久过时弃用的内容,在前面用 @Deprecated 进行注解

@Deprecated

可以压住一些不必要的黄色警告,可以传入一个 all 作为参数

@SuppressWarnings

自定义注解

注解本质是一个 interface,其继承自 java.lang.annotation.Annotation

public interface MyAnno extends java.lang.annotation.Annotation {}

注解格式:

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

其中注解列表内的返回值类型可以是 Primitive Type,String,Enum,Annotation,以及上述数组。

public @interface MyAnno {
    int age();
    String name() default "张三";
    Person per();  // 这是个枚举类
    MyAnno2 anno2();
    String[] strs();
}

使用自己的注解的时候,可以选择赋值,或者在一开始给一个 default 值

@MyAnno(age = 12, name = "李四", ...)  // 后面就不展开写了
class Worker {}

当属性列表里,只有一条属性且该属性名叫 value的时候,赋值时不需要写属性名

元注解

元注解就是解释注解的注解,是 Java 内置的一些固定的注解,比如下面几个例子:

@Target:描述注解能够作用的位置

这个元注解里,有个叫 value() 的 ElementType[] 的一个数组,其中 ElementType 本身是一个枚举;这里要求我们传值的,用来标记该注解能作用在哪些地方。常见的几个取值有:

  1. TYPE:可以作用在类上
  2. METHOD:可以作用在方法上
  3. FIELD:可以作用在成员变量上
// 这里的含义就是该注解可以注解在类上和方法上,另外该处 value 可以省略
@Target(value = {ElementType.TYPE, ElementType.METHOD})
public @interface MyAnno {}

@Retention:描述注解被保留的阶段

这个元注解,和上面的差不多,有一个叫 RetentionPolicy 的枚举类,在使用这个注解的时候,只传一个值,此处不是 RetentionPolicy[],这里有三个值可以填写:

  1. SOURCE:只在源码阶段有效
  2. CLASS:一直到字节码期间都有效
  3. RUNTIME:一直到运行时都有效
// 当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {}

@Documented:描述注解是否被抽取到 api 文档中

@Inherited:描述注解是否被子类继承,比如注解描述了 A,然后 B 继承了 A,如果 A 的注解中有该项,则 B 也会继承表述 A 的注解

注解的使用(解析)

下面的第二步骤中,是使用了 getAnnotation()方法,而不是 getAnnotations(); 前者可以通过字节码文件获取到特定的注解,后者是获取到所有的

@Pro(className = "com.guanyu.annotation.Demo1", methodName = "show")
public class Reflection {
    public static void main(String[] args) {
        // 1. 获取该类字节码文件
        Class<Reflection> reflectionClass = Reflection.class;
        // 2. 通过字节码文件获取到注解对象
        Pro an = reflectionClass.getAnnotation(Pro.class);
        // 3. 调用注解对象中的抽象方法,获取返回值
        String className = an.className();
        String methodName = an.methodName();
        // (下面略过,见反射小案例)
        // ...
    }
}

另外,这个注解对象,是在 reflectionClass.getAnnotation()的时候,创建了一个对象出来,其中有它的方法:

public class ProImpl implements Pro{
    public String className(){
        // 但是这里不知道这个值从哪里来,感觉似乎是
        // 一种封装,或者是一种反射获取到的值
        return "cn.itcast.annotation.Demo1";
    }
    public String methodName(){
        return "show";
    }
}

小案例

简单的测试框架

CheckAnnotation:

package com.guanyu.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

Calculator Class:

package com.guanyu.annotation;

public class AnnCalculator {

    @Check
    public void add() {
        System.out.println("1 + 0 = " + (1 + 0));
    }

    @Check
    public void sub() {
        System.out.println("1 - 0 = " + (1 - 0));
    }

    @Check
    public void mul() {
        System.out.println("1 * 0 = " + (1 * 0));
    }

    @Check
    public void div() {
        System.out.println("1 / 0 = " + (1 / 0));
    }

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

CheckTest:

package com.guanyu.annotation;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * 简单的测试框架
 *
 * 当主方法执行后,会自动自行被检测的所有方法(加了Check注解的方法),判断方法是否有异常,记录到文件中
 */
public class TestChek {
    public static void main(String[] args) throws IOException {
        // 1. 先创建对象
        AnnCalculator c = new AnnCalculator();
        Class cls = c.getClass();
        // 2. 通过字节码对象获取所有方法
        Method[] methods = cls.getMethods();
        // 3. 遍历所有方法,如果该方法有对应的注解,则执行
        int error = 0;
        BufferedWriter bw = new BufferedWriter(new FileWriter("log.txt"));

        for (Method m : methods) {
            if (m.isAnnotationPresent(Check.class)) {
                try {
                    m.invoke(c);
                } catch (Exception e) {
                    // 4. 捕获异常,并且记录到文件中
                    error++;
                    bw.write("[ " + m.getName() + " ]" + " 方法出现异常");
                    bw.newLine();
                    bw.write("异常名称:" + e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常原因:" + e.getCause().getMessage());
                    bw.newLine();
                    bw.write("---------------------------------");
                    bw.newLine();
                }
            }
        }
        bw.write("本次测试共出现异常次数:" + error);
        bw.flush();
        bw.close();
    }
}

小结

小结:

  1. 以后大多数时候,我们会使用注解,而不是自定义注解
  2. 注解给谁用?
    1. 编译器
    2. 给解析程序用
  3. 注解不是程序的一部分,可以理解为注解就是一个标签
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值