什么是反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制。
什么是动态呢?动态是相对于静态而言的,主要区别就是二者创建对象的时间不同,静态是在编译时创建对象,动态是在运行期创建对象。
其实在显示业务逻辑中能用到反射的地方很少,一般在工具类中还有aop中使用,但是在基础框架的设计中应用比较多。
反射的优缺点
优点:运行期进行类型的判断,动态加载,提高了代码的灵活度
缺点:性能时反射的最大的缺点,反射相当于一系列解释操作,通知jvm要做的事情,性能比直接的java代码要慢很多。并且很多地方是没法优化或者优化很有限。
业务可读性不好,且在出现问题时排错成本很高。
适用场景
- 逆向代码 ,例如反编译
- 与注解相结合的框架 例如Retrofit
- 单纯的反射机制应用框架 例如EventBus 2.x
- 动态生成类框架 例如Gson
- 编码阶段不知道需要实例化的类名是哪个,需要在runtime从配置文件中加载
- 在runtime阶段,需要临时访问类的某个私有属性
四个基本类
- Field:反射的属性
- Constractor:构造函数
- Method:方法
- Class:类的字节码对象
反射的应用场景
示例一:对账工具类
说明:比较两个集合中有差异数据的对象,最后返回差异数据的集合,并列出差异的文字描述。
注意点:
1.如何确定两个集合哪两个对象进行比较,这个需要根据具体业务来定。此例中是根绝借款订单号和期数来确定两个集合中的哪两个对象进行比较
2.如何确定对象中的哪些字段参与比较,是根据自定义注解。即字段上有这个自定义注解此字段就参与比较
代码:
/**
* 构建差异数据
* <P>
* 1.比较的实体里面必须包含exInfo字段,用于存储比较后的文字信息
* 2.自定义注解ContrastTitle,此注解主要用于区分哪些字段需要比较
* </p>
*当然这只是一个基本的写法,还有性能更高的写法,比如:buildDifferentialData方法中的第二个for循环完全可以使用其他的方案代替,直接查询到相应的对象而不是通过for循环来查找
* @param csvDatas csv文件数据 通常就是外来数据
* @param localDatas 本地数据
* @return
*/
public <T> List<T> buildDifferentialData(List<T> csvDatas, List<T> localDatas){
List<T> differentialDatas = new ArrayList<>();
try{
for (int i=0;i<csvDatas.size() ;i++) {
boolean isEqually=false;
T csvDataObj=csvDatas.get(i);
// 获取对象的属性(这个属性是为了存放比对结果得备注属性)
Field exInfoField = csvDataObj.getClass().getDeclaredField("exInfo");
// 对象的属性的访问权限设置为可访问
exInfoField.setAccessible(true);
for (T localData:localDatas){
if(csvDataObj.equals(localData)){
isEqually=true;
StringBuffer sb1= compareTwoClass(csvDataObj, localData);
if(!sb1.toString().equals("")){
// 设置此属性的值
exInfoField.set(csvDataObj,sb1.append("不一致").toString());
differentialDatas.add(csvDataObj);
differentialDatas.add(localData);
}
localDatas.remove(localData);
break;
}
}
if(!isEqually){
//csvDatas中有独有的
// 设置此属性的值
exInfoField.set(csvDataObj,"合作方独有的数据");
differentialDatas.add(csvDataObj);
}
}
for(T localDataPeculiar:localDatas){
Field exInfoField = localDataPeculiar.getClass().getDeclaredField("exInfo");
// 对象的属性的访问权限设置为可访问
exInfoField.setAccessible(true);
// 设置此属性的值
exInfoField.set(localDataPeculiar,"自己独有的数据");
differentialDatas.add(localDataPeculiar);
}
}catch (Exception e){
}
return differentialDatas;
}
/**
* 比较两个实体对象的属性值是否一致
*
* @param srouceClass1
* @param targetClass2
* @return
*/
public static StringBuffer compareTwoClass(Object srouceClass1, Object targetClass2) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
StringBuffer exInfo = new StringBuffer("");
//获取对象的class
Class<?> clazz1 = srouceClass1.getClass();
Class<?> clazz2 = targetClass2.getClass();
//获取对象的属性列表
Field[] csvDataFieldArr = clazz1.getDeclaredFields();
//遍历属性列表csvDataFieldArr
for (int i = 0; i < csvDataFieldArr.length; i++) {
Field csvDatafield = csvDataFieldArr[i];
//判断此字段上是否有需要对比的注解,只有有这个注解的字段才会参与比对
boolean fieldHasAnno = csvDatafield.isAnnotationPresent(ContrastTitle.class);
if (!fieldHasAnno) {
continue;
}
//设置能够访问私有属性
csvDatafield.setAccessible(true);
//获取目标对象中相同名称的字段
Field localDataField = clazz2.getDeclaredField(csvDatafield.getName());
//设置能够访问私有属性
localDataField.setAccessible(true);
//比对并记录两个字段的字段值不一样的字段名称
if (!compareTwo(csvDatafield.get(srouceClass1), localDataField.get(targetClass2))) {
ContrastTitle fieldAnno = csvDatafield.getAnnotation(ContrastTitle.class);
//获取注解属性的值,用于拼装差异数据的备注
String value = fieldAnno.value();
exInfo.append(value + ",");
}
}
return exInfo;
}
/**
* 比较属性值是否相同
*
* @param object1
* @param object2
* @return
*/
private static boolean compareTwo(Object object1, Object object2) {
if (object1 == null && object2 == null) {
return true;
}
//这个表达式的意思是只要有一个对象为空就返回false
if (object1 == null ^ object2 == null) {
return false;
}
//特殊类型处理
if (object1 instanceof BigDecimal && object2 instanceof BigDecimal) {
return ((BigDecimal) object1).compareTo((BigDecimal) object2) == 0;
}
//具体业务处理,这个不是必须的。因为我得业务中表示时间的字段,合作方传过来的是string,而我得entity中是date所以我需要都转换成String才能比较
if (object2 instanceof Date) {
object2 = DateUtil.format((Date) object2, "yyyy-MM-dd HH:mm:ss");
}
if (object1.equals(object2)) {
return true;
}
return false;
}
自定义注解:
import java.lang.annotation.*;
/**
* 对账中需要对账的字段需要添加这个注解
*
* @author kuangxiang 2018年11月08日
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ContrastTitle {
String value();
}
对掌集合中的实体对象:
import com.zhongan.insf.athena.insure.account.annotations.ContrastTitle;
import com.zhongan.insf.common.annotation.ExcelTitle;
import lombok.Getter;
import lombok.Setter;
import java.math.BigDecimal;
/**
*
* Title: RepayPlanDetailFileVO
* Description: 还款计划对账文件转换
* @author kuangxiang
* @date 2018年8月17日
*/
@Getter
@Setter
public class RepayPlanDetailFileVO {
/**
* 借款申请单号
*/
@ExcelTitle(value = "借款申请单号")
private String loanOrderId;
@ExcelTitle(value = "应还本金")
@ContrastTitle(value = "应还本金")
private BigDecimal repayAmount;
@ExcelTitle(value = "应还利息")
@ContrastTitle(value = "应还利息")
private BigDecimal repayInterest;
@ExcelTitle(value = "当前期数")
@ContrastTitle(value = "当前期数")
private Integer repayNum;
@ExcelTitle(value = "约定还款日")
private String repayDate;
@ExcelTitle(value = "备注")
private String exInfo;
@Override
public boolean equals(Object object) {
RepayPlanDetailFileVO vo=(RepayPlanDetailFileVO)object;
//通过借款订单号和当前期数确定比对对象
if(vo.getLoanOrderId().equals(loanOrderId)&&vo.getRepayNum()==repayNum){
return true;
}
return false;
}
@Override
public String toString() {
return "RepayPlanDetailFileVO{" +
"loanOrderId='" + loanOrderId + '\'' +
", repayAmount=" + repayAmount +
", repayInterest=" + repayInterest +
", repayNum=" + repayNum +
", repayDate='" + repayDate + '\'' +
", exInfo='" + exInfo + '\'' +
'}';
}
}
另一种给属性设置值的方法
Field exInfo = repayPlanDetailFileVO.getClass().getDeclaredField("exInfo");
Class<?> type = exInfo.getType();
Method setReadOnly = repayPlanDetailFileVO.getClass().getMethod("setExInfo", type);
String s ="test2";
setReadOnly.invoke(repayPlanDetailFileVO,s);
System.out.println(repayPlanDetailFileVO.getExInfo());
示例2:两个对象中相同属性值的copy
private static Object constructObject(Object fromObject,Object toObject,String[] fields) throws Exception{
// 数据源的class
Class fromClass = fromObject.getClass();
// 目标的class
Class toClass = toObject.getClass();
for (String field : fields){
try{
// 获取fromClass的Field
Field fromDeclaredField = fromClass.getDeclaredField(field);
fromDeclaredField.setAccessible(true);
// 从fromClass中获取属性的值
Object value = fromDeclaredField.get(fromObject);
// 获取toClass的Field
Field toDeclaredField = toClass.getDeclaredField(field);
toDeclaredField.setAccessible(true);
// 将fromClass中该属性的值设置给toClass中的该属性
toDeclaredField.set(toObject, value);
}catch (NoSuchFieldException e){
System.out.println(field+"属性不存在");
e.printStackTrace();
}
}
// 如果没有传递属性过来,那么默认对比from和to中的属性,存在的进行赋值操作
if(fields.length == 0){
Field[] fromDeclaredFields = fromClass.getDeclaredFields();
Field[] toDeclaredFields = toClass.getDeclaredFields();
List<String> fromList = new ArrayList<String>();
List<String> toList = new ArrayList<String>();
// 取出from中所有field
for (Field field : fromDeclaredFields){
field.setAccessible(true);
fromList.add(field.getName());
}
// 取出to中所有field
for (Field field : toDeclaredFields){
field.setAccessible(true);
toList.add(field.getName());
}
// 循环from属性list
for (String name : fromList){
// to中是否包含该属性
if(toList.contains(name)){
// 包含先进行取值
Field fromDeclaredField = fromClass.getDeclaredField(name);
fromDeclaredField.setAccessible(true);
Object value = fromDeclaredField.get(fromObject);
// 进行赋值操作
Field toDeclaredField = toClass.getDeclaredField(name);
toDeclaredField.setAccessible(true);
toDeclaredField.set(toObject, value);
}
}
}
return toObject;
}
}
示例三:JDBC数据库的链接
public class ConnectionJDBC {
/**
* @param args
*/
//驱动程序就是之前在classpath中配置的JDBC的驱动程序的JAR 包中
public static final String DBDRIVER = "com.mysql.jdbc.Driver";
//连接地址是由各个数据库生产商单独提供的,所以需要单独记住
public static final String DBURL = "jdbc:mysql://localhost:3306/test";
//连接数据库的用户名
public static final String DBUSER = "root";
//连接数据库的密码
public static final String DBPASS = "";
public static void main(String[] args) throws Exception {
Connection con = null; //表示数据库的连接对象
Class.forName(DBDRIVER); //1、使用CLASS 类加载驱动程序 ,反射机制的体现
con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、连接数据库
System.out.println(con);
con.close(); // 3、关闭数据库
}
模拟spring加载Xml配置文件
public class BeanFactory {
private Map<String, Object> beanMap = new HashMap<String, Object>();
/**
* bean工厂的初始化.
* @param xml xml配置文件
*/
public void init(String xml) {
try {
//读取指定的配置文件
SAXReader reader = new SAXReader();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//从class目录下获取指定的xml文件
InputStream ins = classLoader.getResourceAsStream(xml);
Document doc = reader.read(ins);
Element root = doc.getRootElement();
Element foo;
//遍历bean
for (Iterator i = root.elementIterator("bean"); i.hasNext();) {
foo = (Element) i.next();
//获取bean的属性id和class
Attribute id = foo.attribute("id");
Attribute cls = foo.attribute("class");
//利用Java反射机制,通过class的名称获取Class对象
Class bean = Class.forName(cls.getText());
//获取对应class的信息
java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);
//获取其属性描述
java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();
//设置值的方法
Method mSet = null;
//创建一个对象
Object obj = bean.newInstance();
//遍历该bean的property属性
for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {
Element foo2 = (Element) ite.next();
//获取该property的name属性
Attribute name = foo2.attribute("name");
String value = null;
//获取该property的子元素value的值
for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) {
Element node = (Element) ite1.next();
value = node.getText();
break;
}
for (int k = 0; k < pd.length; k++) {
if (pd[k].getName().equalsIgnoreCase(name.getText())) {
mSet = pd[k].getWriteMethod();
//利用Java的反射极致调用对象的某个set方法,并将值设置进去
mSet.invoke(obj, value);
}
}
}
//将对象放入beanMap中,其中key为id值,value为对象
beanMap.put(id.getText(), obj);
}
} catch (Exception e) {
System.out.println(e.toString());
}
}
//other codes
}
学习链接
反射机制实现的基本介绍
反射基本介绍