05-反射与注解

反射与注解

1、反射

含义

JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。

简单说就是:反射可以动态操纵java代码、能在运行时分析类、检查对象、利用Method调用方法。

Class类

  • 对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。
    对于每个类而言,JRE都为其保留一个不变的Cass类型的对象。一个Cass对象包含了特
    定某个结构的有关信息。
  • 类加载器:将cass文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,
    然后生成一个代表这个类的 Jjava. lang Class对象
  1. C|ass本身也是一个类
  2. Cass对象只能由系统建立对象
  3. 一个加载的类在JWM中只会有一个Class实例
  4. 一个Class对象对应的是一个加载到JVM中的一个class文件
  5. 每个类的实例都会记得自己是由哪个Cass实例所生成,通过cass可以完整地得到一个类中的所有被加载的结构
  6. Clas类是 Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

获取Class类对象

  • 类的对象.getClass()
  • Class.forName(“ 类名 ”)
  • 类.class

一个类在内存中只有一个class对象,只要元素类型与维度一样,就是同一个class(例如与数组长度无关)。一个类被加载后,该类的整体结构都会被封装在class对象中

有了Class类对象,可以构造对应的实例对象:

Class cl = Class.forName(className);
Object ob = cl.getConstruct0r().newInstance();//该类必须有无参构造

使用反射对类进行操作

主要靠反射包java.lang.reflect中的三个类:

  • Filed:描述类的字段
  • Method:类的方法
  • Constructor:类的构造器

这些类可以用一些方法对类的字段、方法、构造器进行操作,比如:查看修改字段内容、执行方法、使用构造器造实例、设置访问权限等等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x2RXpMav-1617939966115)(C:\Users\91051\AppData\Roaming\Typora\typora-user-images\image-20210311155407065.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rUyXJghr-1617939966117)(C:\Users\91051\AppData\Roaming\Typora\typora-user-images\image-20210311155319113.png)]

可以用Class类的三种方法获取到以下三个类的对象

  • Filed[] getFileds()
  • Method[] getMethods()
  • Constructor[] getConstructors()

这里就不深入了解怎么利用反射操作类了!

反射优缺点

  • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。

  • 缺点: 1,性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。

    ​ 2,安全问题,让我们可以动态操作改变类的属性同时也增加了类的安全隐患。

2、注解

含义

如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是为当前读取该注解的程序提供判断依据及少量附加信息。

比如程序只要读到加了@Test的方法,就知道该方法是待测试方法,又比如@Before注解,程序看到这个注解,就知道该方法要放在@Test方法之前执行。有时我们还可以通过注解属性,为将来读取这个注解的程序提供必要的附加信息,比如@RequestMapping("/user/info")提供了Controller某个接口的URL路径。

注解本质上是一个继承了Annotation的接口

分类

大致分为三类:自定义注解、JDK内置注解、还有第三方框架提供的注解。

  • 自定义注解就是我们自己写的注解,比如@UserLog

  • JDK内置注解,比如@Override检验方法重写,@Deprecated标识方法过期,@SupperWarnning镇压警告等

  • 第三方框架定义的注解比如SpringMVC的@Controller等

定义注解

元注解
public @interface 注解名称{
    
    //属性列表,String表示属性类型,value表示属性名字,可以指定默认值
    String value() default “ hello annotation ”;
}

但这样定义完后此注解还不能使用,我们必须为其指定保留策略

注解的保留策略有三种:SOURCE/ClASS/RUNTIME,分别对应源码阶段、编译阶段、运行阶段

img

“保留策略”这个玩意的意义在哪呢?

一般来说,普通开发者使用注解的时机都是运行时,比如反射读取注解(也有类似Lombok这类编译期注解),既然反射是运行时调用,那就要求注解的信息必须保留到虚拟机将.class文件加载到内存为止。如果你需要反射读取注解,却把保留策略设置为RetentionPolicy.SOURCE、RetentionPolicy.CLASS,那就读取不到了。

使用元注解定义注解的保留策略、使用目标等

**所谓元注解,就是加在注解上的注解。**作为普通程序员,常用的就是:

  • @Documented:用于制作文档,不是很重要,忽略便是
  • @Target:加在注解上,限定该注解的使用位置。不写的话,好像默认各个位置都是可以的。
  • @Retention:注解的保留策略
@Retention(RetentionPolicy.RUNTIME)//定义保留策略
public @interface MyAnnotation {
    String getValue() default "no description";
}

前面讲完定义,记下来看看基本使用

注解使用

只要用到注解,就一定有三样东西:

  • 定义注解
  • 使用注解
  • 读取注解

我们使用别人的注解就只管使用,定义与读取是又别人实现;如果我们想自定义注解,就必须三个都要有。

@注解名字(参数名=参数内容)
//有属性且没有默认值的注解我们使用这个注解时必须传参数给它
//如果注解的属性只有一个且叫value时,使用该注解时可以省略参数名,即:@注解名字(参数内容)
//但是注解的属性如果有多个,无论是否叫value,都必须写明属性的对应关系

读取注解

通过反射获取类的所有注解

public class AnnotationTest {
    public static void main(String[] args) throws Exception {
        // 获取类上的注解
        Class<Demo> clazz = Demo.class;
        MyAnnotation annotationOnClass = clazz.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnClass.getValue());

        // 获取成员变量上的注解
        Field name = clazz.getField("name");
        MyAnnotation annotationOnField = name.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnField.getValue());

        // 获取hello方法上的注解
        Method hello = clazz.getMethod("hello", (Class<?>[]) null);
        MyAnnotation annotationOnMethod = hello.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnMethod.getValue());

        // 获取defaultMethod方法上的注解
        Method defaultMethod = clazz.getMethod("defaultMethod", (Class<?>[]) null);
        MyAnnotation annotationOnDefaultMethod = defaultMethod.getAnnotation(MyAnnotation.class);
        System.out.println(annotationOnDefaultMethod.getValue());

    }
}

但是,注解的读取并不只有反射一种途径。比如@Override,它由编译器读取(你写完代码ctrl+s时就编译了),而编译器只是检查语法错误,此时程序尚未运行。

img

山寨版Junit

还是三步曲:

imgimg

定义

MyBefore注解(定义注解)

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

MyTest注解(定义注解)

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

MyAfter注解(定义注解)

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

EmployeeDAOTest(使用注解)

/**
 * 和我们平时使用Junit测试时一样
 *
 * @author qiyu
 */
public class EmployeeDAOTest {
    @MyBefore
    public void init() {
        System.out.println("初始化...");
    }

    @MyAfter
    public void destroy() {
        System.out.println("销毁...");
    }

    @MyTest
    public void testSave() {
        System.out.println("save...");
    }

    @MyTest
    public void testDelete() {
        System.out.println("delete...");
    }
}

MyJunitFrameWork(读取注解)

/**
 * 这个就是注解三部曲中最重要的:读取注解并操作
 * 相当于我们使用Junit时看不见的那部分(在隐秘的角落里帮我们执行标注了@Test的方法)
 *
 * @author qiyu
 */
public class MyJunitFrameWork {
    public static void main(String[] args) throws Exception {
        // 1.先找到测试类的字节码:EmployeeDAOTest
        Class clazz = EmployeeDAOTest.class;
        Object obj = clazz.newInstance();

        // 2.获取EmployeeDAOTest类中所有的公共方法
        Method[] methods = clazz.getMethods();

        // 3.迭代出每一个Method对象,判断哪些方法上使用了@MyBefore/@MyAfter/@MyTest注解
        List<Method> myBeforeList = new ArrayList<>();
        List<Method> myAfterList = new ArrayList<>();
        List<Method> myTestList = new ArrayList<>();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyBefore.class)) {
                //存储使用了@MyBefore注解的方法对象
                myBeforeList.add(method);
            } else if (method.isAnnotationPresent(MyTest.class)) {
                //存储使用了@MyTest注解的方法对象
                myTestList.add(method);
            } else if (method.isAnnotationPresent(MyAfter.class)) {
                //存储使用了@MyAfter注解的方法对象
                myAfterList.add(method);
            }
        }

        // 执行方法测试
        for (Method testMethod : myTestList) {
            // 先执行@MyBefore的方法
            for (Method beforeMethod : myBeforeList) {
                beforeMethod.invoke(obj);
            }
            // 测试方法
            testMethod.invoke(obj);
            // 最后执行@MyAfter的方法
            for (Method afterMethod : myAfterList) {
                afterMethod.invoke(obj);
            }
        }
    }
}

效果展示:

imgimg

小结

  • 注解就像标签,是程序判断执行的依据。比如,程序读到@Test就知道这个方法是待测试方法,而@Before的方法要在测试方法之前执行
  • 注解需要三要素:定义、使用、读取并执行
  • 注解分为自定义注解、JDK内置注解和第三方注解(框架)。自定义注解一般要我们自己定义、使用、并写程序读取,而JDK内置注解和第三方注解我们只要使用,定义和读取都交给它们
  • 大多数情况下,三角关系中我们只负责使用注解,无需定义和执行,框架会将注解类读取注解的程序隐藏起来,除非阅读源码,否则根本看不到。平时见不到定义和读取的过程,光顾着使用注解,久而久之很多人就忘了注解如何起作用了!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值