【Java基础系列】
【Java基础01】基础概述
【Java基础02】常用类
【Java基础03】时间类
【Java基础04】异常
【Java基础05】枚举类
【Java基础06】泛型
【Java基础07】注解
【Java基础08】反射
【Java基础09】代理
【Java基础10】IO流
反射
文章概述:总结描述Java基础中反射的概述和使用,包括注解的概念、Class类对象的概念、类加载器ClassLoader的概念及涉及的双亲委派模型的原理,获取类对象的三种方式,反射操作获取类的构造方法、成员变量、成员方法、泛型及注解信息。
前言
反射在Java中是很强的一种技术,很多优秀的开源框架都是通过反射完成的,但是因为java反射影响性能,所以被运行时注解APT替代了,java反射有个开源框架jOOR相信很多人都用过,不过这里我们还是主要学习反射的基础语法。基础是根基之本,对于学习使用进阶起到重要作用。
反射是框架设计的灵魂
(使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码))
一、反射的概述
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
实际上,我们创建的每一个类也都是对象,即类本身是java.lang.Class类的实例对象。这个实例对象称之为类对象,也就是Class对象。
反射就是把java类中的各种成分映射成一个个的Java对象。
二、Class对象
Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
其中Class对象比较特殊,了解一下这个Class类
- Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
- Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
- Class 对象用于提供类本身的信息,比如类的构造方法,各种属性,各种普通方法
Class 没有公共的构造方法,方法共有64个
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lvDyYwxE-1678346571338)(images/【Java基础08】反射/20170513144141409.png)](https://img-blog.csdnimg.cn/9c2a300725664b54952e102be53375bc.png)
三、类加载器ClassLoader
3.1 类加载器ClassLoader
虚拟机设计团队把类加载阶段中的**“通过一个类的全限定名来获取此类的二进制字节流”这个动作放到 Java 虚拟机外部去实现**,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。类装载器是用来把类(class)装载进 JVM 的。
从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
- 启动类加载器(Bootstrap ClassLoader),使用 C++ 实现,是虚拟机自身的一部分
- 所有其它类的加载器,使用 Java 实现,独立于虚拟机,并且全部继承自抽象类 java.lang.ClassLoader。
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
- 启动类加载器(Bootstrap ClassLoader):前面已经大致介绍过了,这个类加载器负责将存放在 <JRE_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
- 扩展类加载器(Extension ClassLoader):这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
- 应用程序类加载器(Application ClassLoader):这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
启动加载类加载,加载核心库,例如:rt.jar包
扩展加载器加载,加载外部依赖
系统类加载器,加载对应程序的类
下面代码结果可以分析出三种类加载器的父子级关系
public class Reflex_02_ClassLoader {
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//获取系统类的加载器的父加载器 --> 扩展加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//获取扩展加载器的父加载器 --> 根加载器
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//获取类对应的加载器
ClassLoader classLoader = Class.forName("com.wei.moudle.reflex.Reflex_02_ClassLoader").getClassLoader();
System.out.println(classLoader);
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);
}
}
输出:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@2e817b38
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null
3.2 双亲委派模型(类加载器的层次关系)
3.2.1 概念
上图是上面所介绍的这几种类加载器的层次关系,称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。
双亲委派模型,其实就是一种类加载器的层次关系。
3.2.2 工作过程
如果一个类加载器收到了类加载请求,它首先不会自己尝试去加载这个类,而是把请求委派给父加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才开始自己去加载。
3.2.3 优点
-
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
例如 :java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。那么程序中所有的 Object 都是rt.jar 的 Object。
-
避免了多份同样字节码的加载,内存是宝贵的,没必要保存相同的两份 Class 对象。JVM中每个类只会存在一个类对象
3.2.4 实现原理
双亲委派模型对于保证 Java 程序的稳定运作很重要,但它的实现却非常的简单,实现双亲委派的代码都集中在 java.lang.ClassLoader 的 loadClass() 方法中。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
从 loadClass() 的源码,我们再来看一下双亲委派模型的工作过程。它可以被下面这张图来概括。
- 首先从底向上的检查类是否已经加载过,就是这行代码:
Class<?> c = findLoadedClass(name);
- 如果都没有加载过的话,那么就自顶向下的尝试加载该类。
四、获取类对象的三种方式
- 通过全类名获取 Class.forName() (常用)
- 通过类名获取 Object.class
- 通过对象获取 new Object().getClass()
三种方式中,常用第一种,第二种需要导入类的包,依赖太强,不导包就抛编译错误。第三种对象都有了就无需反射了。一般都第一种,一个字符串可以传入也可写在配置文件中等多种方法。
public static void main(String[] args) throws ClassNotFoundException {
Class aClass = Class.forName("com.wei.domain.user.User");
System.out.println(aClass);
// 一个类在内存中只有一个class对象
// 一个类被加载后,类的整个结构都会被封装在class对象中
Class regionEntityClass = com.wei.domain.user.User.class;
System.out.println(regionEntityClass);
com.wei.domain.user.User regionEntity = new com.wei.domain.user.User();
Class bClass = regionEntity.getClass();
System.out.println(bClass);
}
输出:
class com.wei.domain.user.User
class com.wei.domain.user.User
class com.wei.domain.user.User
**在一个JVM中,一种类,只会有一个类对象存在。**所以以上三种方式取出来的类对象,都是一样。(此处准确是在ClassLoader下,只有一个类对象)
五、反射获取类的各种属性
5.1 获取构造方法
与传统的通过new 来获取对象的方式不同反射机制,反射会先拿到对象的“类对象”,然后通过类对象获取“构造器对象”再通过构造器对象创建一个对象。
构造方法不同时,获取构造器对象的方法
//无参构造方法
public Reflex_03_Constructor(){
System.out.println("调用了公有、无参构造方法执行了。。。");
}
// 一个参数的构造方法
public Reflex_03_Constructor(String str){
System.out.println("参数1 = " + str);
}
//有多个参数的构造方法
public Reflex_03_Constructor(String name ,String code){
System.out.println("参数1:"+name+"参数2:"+ code);
}
//受保护的构造方法
protected Reflex_03_Constructor(boolean n){
System.out.println("受保护的构造方法 参数1: = " + n);
}
//私有构造方法
private Reflex_03_Constructor(float hp){
System.out.println("私有的构造方法 参数1::"+ hp);
}
private void printInfo() {
System.out.println("方法被调用....");
}
通过反射机制获取对象
/*
* 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
*
* 1.获取构造方法:
* 1).批量的方法:
* public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
* 2).获取单个的方法,并调用:
* public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
* public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
*
* 2.创建对象
* Constructor对象调用newInstance(Object... initargs)
*/
public static void main(String[] args) throws Exception {
//1.加载Class对象
Class clazz = Class.forName("com.wei.moudle.reflex.Reflex_03_Constructor");
//2.获取所有公有构造方法
System.out.println("**********************所有公有构造方法*********************************");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("*****************获取公有、无参的构造方法*******************************");
Constructor con = clazz.getConstructor(null);
//1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
//2>、返回的是描述这个无参构造函数的类对象。
System.out.println("con = " + con);
//调用构造方法
Reflex_03_Constructor constructor1 = (Reflex_03_Constructor)con.newInstance();
constructor1.printInfo();
System.out.println("******************获取私有构造方法,并调用*******************************");
con = clazz.getDeclaredConstructor(float.class);
System.out.println(con);
//调用构造方法
con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
Reflex_03_Constructor constructor2 = (Reflex_03_Constructor)con.newInstance(100);
constructor2.printInfo();
}
输出:
**********************所有公有构造方法*********************************
public com.wei.moudle.reflex.Reflex_03_Constructor(java.lang.String,java.lang.String)
public com.wei.moudle.reflex.Reflex_03_Constructor(java.lang.String)
public com.wei.moudle.reflex.Reflex_03_Constructor()
************所有的构造方法(包括:私有、受保护、默认、公有)***************
private com.wei.moudle.reflex.Reflex_03_Constructor(float)
protected com.wei.moudle.reflex.Reflex_03_Constructor(boolean)
public com.wei.moudle.reflex.Reflex_03_Constructor(java.lang.String,java.lang.String)
public com.wei.moudle.reflex.Reflex_03_Constructor(java.lang.String)
public com.wei.moudle.reflex.Reflex_03_Constructor()
*****************获取公有、无参的构造方法*******************************
con = public com.wei.moudle.reflex.Reflex_03_Constructor()
调用了公有、无参构造方法执行了。。。
方法被调用....
******************获取私有构造方法,并调用*******************************
private com.wei.moudle.reflex.Reflex_03_Constructor(float)
私有的构造方法 参数1::100.0
方法被调用....
总结
1.获取构造器对象方法:
1).批量的方法:
public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
2).获取单个的方法:
public Constructor getConstructor(Class… parameterTypes): 获取单个的"公有的"构造方法
public Constructor getDeclaredConstructor(Class…parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
con.setAccessible(true);//暴力访问(忽略掉访问修饰符,否则私有方法不能访问)
5.2 获取成员变量
设置两个公有成员变量,一个私有成员变量
public String linkMan;
public String linkTel;
private String address;
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class aClass = Class.forName("com.wei.moudle.reflex.Reflex_04_Fields");
//包名 + 类名
System.out.println(aClass.getName());
//类名
System.out.println(aClass.getSimpleName());
Reflex_04_Fields reflex_04_fields = (Reflex_04_Fields) aClass.newInstance();
//找到public属性
System.out.println("**********************公有成员变量*********************************");
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field);
System.out.println(field.getName());
}
System.out.println("**********************所有成员变量*********************************");
//找到全部属性
fields = aClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
System.out.println(field.getName());
}
fields[0].set(reflex_04_fields, "luo");
System.out.println(reflex_04_fields.getLinkMan());
}
输出:
com.wei.moudle.reflex.Reflex_04_Fields
Reflex_04_Fields
**********************公有成员变量*********************************
public java.lang.String com.wei.moudle.reflex.Reflex_04_Fields.linkMan
linkMan
public java.lang.String com.wei.moudle.reflex.Reflex_04_Fields.linkTel
linkTel
**********************所有成员变量*********************************
public java.lang.String com.wei.moudle.reflex.Reflex_04_Fields.linkMan
linkMan
public java.lang.String com.wei.moudle.reflex.Reflex_04_Fields.linkTel
linkTel
private java.lang.String com.wei.moudle.reflex.Reflex_04_Fields.address
address
luo
总结
getField和getDeclaredField的区别
getField 只能获取public的,包括从父类继承来的字段。
getDeclaredField 可以获取本类所有的字段,包括private的,但是 不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))
5.3 获取成员方法
public void printInfo() {
System.out.println("方法被调用....");
}
private void printInfoWithStr(String info) {
System.out.println("方法被调用带信息:" + info);
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class aClass = Class.forName("com.wei.moudle.reflex.Reflex_04_Fields");
Reflex_04_Fields reflex_04_fields = (Reflex_04_Fields) aClass.newInstance();
System.out.println("**********************获取方法*********************************");
//获取方法
aClass.getMethods();
aClass.getDeclaredMethods();
//获取指定方法
Method printInfo = aClass.getMethod("printInfo", null);
Method printInfoWithStr = aClass.getDeclaredMethod("printInfoWithStr", String.class);
System.out.println(printInfo);
System.out.println(printInfoWithStr);
printInfo.invoke(reflex_04_fields, null);
printInfoWithStr.invoke(reflex_04_fields, "hello");
}
输出
**********************获取方法*********************************
public void com.wei.moudle.reflex.Reflex_04_Fields.printInfo()
private void com.wei.moudle.reflex.Reflex_04_Fields.printInfoWithStr(java.lang.String)
方法被调用....
方法被调用带信息:hello
总结
获取成员方法:
public Method getMethod(String name ,Class<?>… parameterTypes):获取"公有方法";(包含了父类的方法也包含Object类)
public Method getDeclaredMethods(String name ,Class<?>… parameterTypes) :获取成员方法,包括私有的(不包括继承的)
参数解释:
name : 方法名;
Class … : 形参的Class类型对象
调用方法
Method --> public Object invoke(Object obj,Object… args):
参数说明:
obj : 要调用方法的对象;
args:调用方式时所传递的实参;
5.4 获取main方法
public static void main(String[] args) {
try {
//1、获取HeroPlus对象的字节码
Class clazz = Class.forName("com.wei.moudle.reflex.Reflex_01_GetClass");
//2、获取main方法,第一个参数:方法名称,第二个参数:方法形参的类型,
Method methodMain = clazz.getMethod("main", String[].class);
//3、调用main方法
// methodMain.invoke(null, new String[]{"a","b","c"});
//第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
//这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。所以需要将它强转。
methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
} catch (Exception e) {
e.printStackTrace();
}
}
5.5 反射操作泛型
Java 中的泛型仅仅是给编译器 javac 使用的,确保数据的安全性和免去强制类型转换的麻烦,但是一旦编译完成,所有与泛型有关的类型全部擦除。
使用泛型直接读取泛型,是读取不到的,因为反射是操作加载以后的类的。
Java新增的数据类型,代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
为了通过反射操作这些类型以迎合实际开发的需要
类型 | 描述 |
---|---|
ParameterizedType | 表示一种参数化的类型 , 比如 Collection,可以获取 String信息,List |
GenericArrayType | 泛型数组类型 |
TypeVariable | 各种类型变量的公共父接口 |
WildcardType | 代表一种通配符类型表达式,比如? extends Number,? super Integer (Wildcard 是一个单词,就是通配符) |
public void test01(Map<String, User> map, String value) {
}
public Map<String, User> test02(String value) {
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method test01 = ClassTTest.class.getMethod("test01", Map.class, String.class);
// 获取入参
Type[] genericParameterTypes = test01.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("###:"+genericParameterType);
// 如果参数类型属于 ParameterizedType 参数化的类型 如:List<String>
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
System.out.println("##########");
Method test02 = ClassTTest.class.getMethod("test02", String.class);
// 获取返回结果
Type genericReturnType = test02.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
Type[] genericParameterTypes1 = test02.getGenericParameterTypes();
for (Type genericParameterType1 : genericParameterTypes1) {
System.out.println("###:"+genericParameterType1);
}
}
输出:
###:java.util.Map<java.lang.String, com.wei.moudle.reflex.User>
class java.lang.String
class com.wei.moudle.reflex.User
###:class java.lang.String
##########
class java.lang.String
class com.wei.moudle.reflex.User
###:class java.lang.String
5.6 反射操作注解
关于反射操作注解在【Java基础07】注解中也有相关例子的应用
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.wei.moudle.reflex.User");
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
method annotation = (method) c1.getAnnotation(method.class);
System.out.println(annotation.value());
Field id = c1.getDeclaredField("id");
field annotation1 = id.getAnnotation(field.class);
System.out.println(annotation1.len()+annotation1.name());
}
}
@Data
@method("test_user")
class User {
@field(name = "user_id", len = 10)
private String id;
@field(name = "user_age", len = 8)
private Integer age;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface method {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface field {
String name();
int len();
输出:
@com.wei.moudle.reflex.method(value=test_user)
test_user
10user_id
六、反射的用法举例
- 使用JDBC连接数据库时,使用Class.forName(驱动类名),通过反射加载数据库的举动类。
- Spring框架的IOC(动态加载管理Bean)创建对象以及AOP(动态代理)使用反射进行底层实现。
- MyBatis框架的Mapper接口代理对象的创建,也是通过反射来实现的。
七、反射的优缺点
优点:
- 自由,使用灵活,不受类的访问权限限制。
- 可以根据指定类名、方法名来实现方法调用,非常适合实现业务的灵活配置。
缺点:
- 反射不受类的访问权限限制,所以其安全性低,很大部分的java安全问题都是反射导致的。
- 相对于正常的对象的访问调用,反射因为存在类和方法的实例化过程,性能也相对较低。
- 破坏java类封装性,类的信息隐藏性和边界被破坏。