一、反射
先去谈一下关于Java的反射,在IDEA中,我们时常打出一个字母,系统就能去提醒它的方法,比如当你打出“f”的时候,系统会提示你"for()"方法,有时候就在想,这是怎么做到的?直到学习了反射的知识,其实我认为反射的知识打破了我们对于Java语言的认知,大家都知道,Java的三个特点:封装继承和多态,而反射恰好打破了Java的封装的特点,使一个类可以打破原则去访问其他类的私有方法和私有变量。
1.反射的定义是什么?Java反射是Java语言提供的一种运行时自省机制,它允许程序在运行时获取类的完整内部结构信息(包括类名、字段、方法、构造方法、注解等),并且可以动态操作这些类的对象(创建实例、调用方法、修改字段值)——即使这些类和成员在编译时是完全未知的。
2.反射那么反射的意义和用途在哪?上面我举出的就是一个很典型的例子,反射常用于框架和工具中。
3.反射的底层原理是什么?Java 反射的核心基础是JVM 的类加载机制:当Java程序运行时,JVM 会将.class字节码文件加载到内存中,对于每个被加载的类,JVM会在方法区自动生成一个唯一的java.lang.Class对象,这个Class对象就像一个 "类的镜像",完整保存了该类的所有结构信息,反射机制就是通过操作这个Class对象,来间接操作原类和它的实例
4.反射的优缺点有什么?
优点:
- 极致的动态性:这是反射最大的优势,也是所有Java框架的核心基础。可以在运行时动态加载和操作类,实现"配置驱动"的开发模式
- 通用性:可以编写处理任意类型对象的通用工具类(如JSON解析、对象复制、序列化工具)
- 突破封装限制:在某些特殊场景下(如框架开发、单元测试),需要访问私有成员来完成功能
缺点
- 性能开销大:反射操作比直接调用慢10-100倍,因为需要动态解析类型、检查访问权限、进行参数匹配
- 破坏封装性:可以访问私有成员,违背了Java的面向对象封装原则,可能导致代码结构混乱
- 编译时无法检查错误:反射操作的错误(如方法名写错、参数类型不匹配)只能在运行时抛出异常,增加了调试难度
- 安全风险:恶意代码可以利用反射突破Java的安全沙箱,执行危险操作
5.下面举一下反射的例子来说明一下其中的方法
首先,创建了Student类,定义了变量name,age,id并创建他们的Javabean,在原理中提到,JVM要获取它的class文件,所以要先获取Student的class字节码文件,c.getName(),这个方法是获取类的名字,由于我这里没有创建包,所以结果就是Student,如果在包下面定义了类,那么会返回包名,我这里只会返回Student,如果只想返回类名,就用c.getSimpleName()方法,c.getDeclaredConstructor()用于得到空参构造器,如果要得到有参构造器,就使用c.getDeclaredConstructor(String.class,int.class,int.class),在方法中加上每个参数的类型并获取他们的字节码,JVM就会帮助查到有参构造器,如果方法中不加Declared,那么只能访问public公有的方法,变量和构造器,在每个get方法后加上“s”就可以得到所有的方法,变量和构造器,比如c.getDeclaredConstructors()就可以得到所有构造器(后面的获取变量和方法也是一样),constructor1.getParameterCount()用于获取构造器里的参数数量,由于我constructor1是空参构造器,所以参数数量为0,接下来是constructor1.setAccessible(true),我认为这里是反射的灵魂,setAccessible(true)目的是破坏调用者的封装性,假如这里的构造器是私有的,调用这个方法后,就变为了公有的,然后就可以得到相应的构造器,方法和变量,Student student=(Student)constructor1.newInstance(),这里的newInstance()方法作用是是赋值,这里由于调用空参构造器,所以默认所有初值为null和0,下面调用有参构造器则可以进行赋值,哪为什么要进行强制类型转换呢?这里就要从newInstance()方法的源码说起
这个方法使用了可变参数类型,返回了Object类,那这里我要获取Student类的对象,自然要进行强制类型转换,接下来是获取变量,和上述获取构造器的方法基本一致,得到变量后,我们用增强for去遍历得到的变量,就可以获取它们的名字和类型,当然也可以用变量去调用类中的对应方法,最后是获取方法,就和上述提到的一致了
6.下面举一个反射的例子作为练习:将一个类中所有的变量和类型打印出来,存到文件中
二、注解
相信学过Java继承的应该都知道@Override重写,这就是一个典型的注解的例子,那下面我就谈一下注解
1.注解的定义:Java注解是JDK5.0引入的一种代码元数据,它是一种特殊的标记,可以附加在类、字段、方法、参数、包等代码元素上,用于为代码添加额外的说明、配置或约束信息 。简单来说,注解就是一个额外的声明和约束
2.那么注解的用途是什么呢?注解其实也常用于框架与工具中。
3.注解的底层原理是什么?所有 Java 注解本质上都是一个继承了java.lang.annotation.Annotation接口的特殊接口,当你在代码上使用这个注解时,JVM会在运行时生成一个动态代理类来实现这个MyAnnotation接口,我们通过反射获取到的注解对象,其实就是这个动态代理类的实例。
4.注解的优缺点有什么?
优点
- 简化配置:替代传统的XML配置,让代码更简洁
- 提高开发效率:通过注解自动生成代码或执行通用逻辑
- 类型安全:编译时就能检查注解属性的类型错误
- 灵活性高:可以在运行时动态获取注解信息,实现灵活的逻辑控制
缺点
- 注解泛滥:过度使用注解会导致代码中到处都是注解,可读性下降
- 调试困难:注解的逻辑通常在框架内部,出现问题时难以调试
- 耦合度高:业务代码和框架注解耦合在一起,不利于代码的移植
5.下面介绍几个注解的知识点
1.元注解常用的有:
@Target:指定注解可以作用在哪些代码元素上
常用取值:
ElementType.TYPE:类、接口、枚举
ElementType.FIELD:字段
ElementType.METHOD:方法
ElementType.PARAMETER:方法参数
ElementType.CONSTRUCTOR:构造方法
ElementType.ANNOTATION_TYPE:注解本身
例如:
@Retention:指定注解的生命周期
取值有:RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME
2.自定义注解:格式如下:在接口前添加@
3.注解的使用方式:如下:在方法前声明
4.注解的核心API:
三、代理
先说一下我对于代理的理解,代理是一种设计模式,在工程中,如果遇到核心代码的需求变更,那究竟该怎么办?如果直接去修改核心代码,那核心变动了,其他的功能会不会受到影响?我认为对整个工程的影响还是很大的,这时候就要用到代理的设计模式思想,假设有一个明星,他 要做的事情有很多,但是有很多事情是不需要本人去做的,比如歌星开演唱会需要唱歌,他需要进行舞台的搭建,自己准备话筒吗?显然是不需要的,那我们如何去实现整个开演唱会的过程呢?那就需要去找一个代理,让代理去做这些歌星不需要做的事情,这种场景用到软件工程上就是代理的思想,那以下就是关于代理的一些思想:
1.什么是代理?
代理模式是一种结构型设计模式,它通过创建一个代理对象来代替真实对象对外提供服务,从而在不修改真实对象代码的前提下,实现对真实对象的访问控制、功能增强或逻辑隔离。简单来说:你不直接调用真实对象的方法,而是调用代理对象的方法,代理对象会帮你转发请求给真实对象,并且可以在转发前后添加额外的逻辑。
2.为什么使用代理?
代理模式的核心价值在于在不修改原有代码的前提下,对原有功能进行增强。
具体来说,代理可以实现以下功能:
- 访问控制:限制客户端对真实对象的访问权限
- 功能增强:在真实对象方法执行前后添加额外逻辑(如日志、事务、性能监控)
- 延迟加载:只有当真正需要使用真实对象时,才创建它(懒加载)
- 远程调用:代理对象可以处理网络通信,让客户端像调用本地方法一样调用远程对象
- 隔离变化:当真实对象发生变化时,只需要修改代理对象,不需要修改客户端代码
3.代理的分类:
3.1静态代理
静态代理是在编译时就已经写好代理类的代码,程序运行前代理类的.class 文件就已经存在了。
实现步骤可分为:
- 定义抽象主题接口
- 编写真实主题类,实现抽象主题接口
- 编写代理类,实现抽象主题接口,持有真实主题对象的引用
- 客户端调用代理对象的方法
如:
这里前置增强和后置增强就是在核心代码基础上加上了额外功能,但是静态代理是有缺陷的,他的缺点在于:
1.代码冗余:每个真实类都需要一个对应的代理类,当类很多时,会产生大量的代理类
2.维护困难:如果接口增加一个方法,所有真实类和代理类都需要修改
3.灵活性差:只能代理实现了该接口的类,无法代理没有实现接口的类
这也是为什么我们经常用到的是动态代理
3.2动态代理
动态代理是在程序运行时,通过反射机制动态生成代理类和代理对象,不需要手动编写代理类代码
实现原理
1.动态生成一个实现了抽象主题接口的代理类
2.代理类持有一个InvocationHandler对象的引用
3.当调用代理对象的任何方法时,都会转发到InvocationHandler的invoke()方法
4.在invoke()方法中,通过反射调用真实对象的方法,并添加增强逻辑
下面举一个动态代理的例子,还是上述歌星的例子:
首先,定义一个接口,方法有返回String类型的sing
然后定义一个明星类,实现接口里的方法,此时这个类是被代理类
然后就是代理类,用对应方法创建了代理,用到了反射的思想,获取了接口里的方法,然后判断是调用了哪个方法,再去执行对应方法
在测试类中,先传入歌星的姓名,然后去产生歌星的代理,代理去接口看到了两个方法,然后去被代理类,代理类判断被执行的是sing方法,就用invoke()去执行代理sing()方法,最后得出测试结果。
动态代理的优点在于:
1.不需要手动编写代理类,大大减少了代码量
2.一个 InvocationHandler可以代理多个实现了不同接口的类
3.接口增加方法时,不需要修改代理逻辑