java反射以及在Mybatis源码中的应用
前言
java反射的概念以及简单使用已经在java反射机制简单应用中提到,今天这篇文章来进一步介绍java反射及其java反射在一些框架中的应用
一、反射的使用
java给我们提供了非常完整的API来对类的结构进行操作
/*反射使用*/
/* 1、获得类对象aClass,aClass中有类的定义的结构,可以访问和操作类的方法和属性
* (1)类的属性:类对象可以提供对类的静态属性的访问。通过类对象,可以获取和修改类的静态字段值。
* getDeclaredField():得到当前该类的属性,不能直接用getField,因为会得到该类和其父类(Object)的所有属性
(2)类的方法:类对象可以提供对类的静态方法的访问。可以使用类对象调用类的静态方法,无需实例化类的对象。
* getDeclaredMethod(String name, Class<?>... parameterTypes):得到当前该类的方法,同样的,也不能直接用getMethod
(3)类的构造函数:类对象可以提供对类的构造函数的访问。可以使用类对象创建类的对象,实例化类。
* getDeclaredConstructors():得到该类的所有构造方法
* getConstructors:得到该类公共的构造方法
* getDeclaredConstructor(Class<?>... parameterTypes):得到该类特定的构造方法,通过参数列表来确定
(4)类的注解:类对象可以返回类的注解信息,包括在类级别上声明的注解。
* getAnnotations():得到所有该类的注解,一个注解数组
* getAnnotation(Class<T> annotationClass):得到特定的注解
(5)类的接口信息:类对象可以返回实现的接口信息,包括继承的接口和实现的接口。
* getInterfaces():得到该类的实现的所有接口,一个接口数组
* 如果需要得到特定的接口信息,则需要遍历接口数组,加以判断:
* for (Type anInterface : interfaces) {
// 判断是否为特定接口
if (anInterface == MyInterface1.class) {
......
}
}
* getInterfaces():得到接口的父接口信息
(6)类的父类信息:类对象可以返回直接父类的相关信息,包括父类的名称和类型。
* getSuperclass()
(7)类的修饰符和访问级别:类对象可以提供对类的修饰符(如public、private、abstract等)和访问级别(如public、protected、private等)的访问。
* getModifiers():得到类的访问修饰符
* // 判断修饰符
if (Modifier.isPublic(modifiers)) {
System.out.println("Modifier: public");
} else if (Modifier.isAbstract(modifiers)) {
System.out.println("Modifier: abstract");
} else if (Modifier.isFinal(modifiers)) {
System.out.println("Modifier: final");
} else {
System.out.println("Modifier: unknown");
}
* getPackage():得到类的访问级别(其实就是得到类的包名)
* */
===============================================================================================
/*
*我们先定义一个普通的Person类
*/
package com.darkForest.test.reflex;
public class Person {
private int id=1;
private String name;
private String password;
public Person() {
}
public Person(String name, String password) {
this.name = name;
this.password = password;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void show(String name,String password){
System.out.println(this.name+password);
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
===============================================================================================
/*
*接下来得到Person的类对象
*/
Class<?> aClass = Class.forName("com.darkForest.test.reflex.Person");
//对Person类的构造方法的操作,可对Person进行实例化
Person person = (Person) aClass.newInstance();//无参构造方法的方式
Constructor<?> constructor=aClass.getConstructor(String.class,String.class);//含参构造方法的方式
Person object = (Person) constructor.newInstance("zhaa", "12345");
//对Person类的属性的操作
//获得属性对象
Field name = aClass.getDeclaredField("name");
/*对私有属性赋值时,需要设置权限,不然对属性的操作会报
* class com.darkForest.test
* .reflex.Test cannot access a member
* of class com.darkForest.test.reflex.Person with modifiers "private"
* 这个错误*/
name.setAccessible(true);
//给属性赋值
name.set(person,"fff");
//对Person类的方法的操作
//获取方法对象
Method setName = aClass.getDeclaredMethod("setName", String.class);
//invoke:调用person对象指定的方法
setName.invoke(person,"hhh");
二、手写Mybatis源码时对Java反射的应用
在手写mybatis源码时,处理从数据库中查询到的结果集变成java的数据类型(实体类对象、map、list(Object)、list<Map<String,Object>>)等的时候就需要用到反射来对类的方法进行获取和操作,我这里采用的是全参构造方法的方式来对实体类进行创建并赋值,大家可以看下面的代码:
public <T>T getEntity(Class clazz) throws InstantiationException, IllegalAccessException, SQLException, NoSuchMethodException, InvocationTargetException {
List<Object> list=null;
while (resultSet.next()) {
//得到所有属性
Field[] fields=clazz.getDeclaredFields();
//数组来保存参数类型
Class<?> [] parameterTypes=new Class[fields.length];
//遍历所有属性,得到属性数据类型
for (int i = 0; i < fields.length; i++) {
parameterTypes[i]=fields[i].getType();
}
//使用含参构造函数实例化对象
Constructor<?> constructor=clazz.getConstructor(parameterTypes);
//获取了结果集的元数据信息。结果集中列的信息,比如列的名称、数据类型、列的数量 getMetaData:得到(元数据对象)
ResultSetMetaData md = resultSet.getMetaData();
//得到结果集数据记录(列)数
int columnCount = md.getColumnCount();
//数组,用来暂时存储每一条记录的值
Object[] values=new Object[columnCount];
for (int i = 1; i <=columnCount; i++) {
//得到当前(i)对应的一条记录对象
Object val=resultSet.getObject(i);
//获取与查询结果集中指定列(对应的i)关联的目录名称
String catalogName = md.getCatalogName(i);
Object value=resultSet.getObject(i);
values[i-1]=value;
}
//采用全参的方式实例化,给实体类中每个属性都赋值
Object object = constructor.newInstance(values);
//把实例化的对象存进list集合里
list.add(object);
}
return (T)list;
}
采用这种方式会有一个弊端,就是必需查询数据库表中的所有列的数据,如果只查询其中的一些列的数据,那么实体类中没有与之对应的属性就会为空,而且是采用构造函数的方式,参数的顺序没有做处理,如果直接把参数数组放进去,就会有可能导致参数数组类型顺序、长度和构造函数参数数组类型顺序、长度不一致的情况,比如把结果集中的name的值赋值给实体类中的password等等。
更好的解决方法是在遍历结果集的所有列时,获取当前列的名称和类型,再找到实体类中与之相对应的方法,把数据依次存进实体类中,如果对这种方式有兴趣,可以看一下我的另一篇博文手写Mybatis源码01。
总结
java的反射是一种很强大的机制,是多数框架的基础,对于学习和使用甚至开发框架是必不可少的。