Skip to content

反射

基础概念

反射定义: 在一个类中 定义了一个私有属性/方法,但是使用反射能使所有属性都访问到,会破解私有属性

反射的应用场景:

反编译: .class → .java

  • 通过反射机制访问 java 对象的属性,方法,构造方法等
  • 反射技术的使用
    • Class类 代表类的实体,在运行的Java应用程序中表示类和接口
    • Field类 代表类的成员变量(成员变量也称为类的属性)
    • Method类 代表类的方法
    • Constructor类 代表类的构造方法
      • 1.getFieldgetMethodgetCostructor方法可以获得指定名字的域、方法和构造器
      • 2.getFieldsgetMethodsgetCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员
      • 3.getDeclatedFieldsgetDeclatedMethodsgetDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。

反射的基础使用: https://www.cnblogs.com/cg-ww/p/15609172.html

演示使用类:

User

package com.ruoyi.luoqi.reflect;

/**
 * @author luoqi
 * @File User.java
 * @Desc
 * @Create 2024/4/24 10:42
 * @ChangeList --------------------------------------------------------------------
 * Date                          Editor                     ChangeReason
 */
public class User {
    private String name;
    private int age;
    private String email;

    // 无参数构造器
    public User() {
    }

    // 带所有属性的构造器
    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    public int add(int a,int b ){
        return a + b;
    }

    private int subtract(int a,int b ){
        return a - b;
    }

    // Getter 和 Setter 方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    // 重写 toString 方法以便于打印用户信息
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                '}';
    }
}

ReflectionExample

package com.ruoyi.luoqi.reflect;

/**
 * @author luoqi
 * @File ReflectionExample.java
 * @Desc
 * @Create 2024/4/24 10:40
 * @ChangeList --------------------------------------------------------------------
 * Date                          Editor                     ChangeReason
 */
import java.lang.reflect.*;

public class ReflectionExample {
    public static void main(String[] args) {
        try {
            System.out.println("无参构造:");
            //加载 User 类
            Class<?> cls = Class.forName("com.ruoyi.luoqi.reflect.User");

            Constructor<?> constructor = cls.getConstructor();  // 获取无参公共构造函数

            System.out.println(constructor);
            System.out.println("获取公共访问字段:");
            //Class.getFields() 方法返回的是类中所有可访问的公共字段(public fields),包括从超类继承来的公共字段
            Field[] fields = cls.getFields();
            System.out.println(fields.length);
            for(Field d : fields) {
                System.out.println(d.getName());
            }
            System.out.println("---------");
            System.out.println("获取全部访问字段:");
            //获得类中声明的全部域,其中包括私有和受保护的成员,但不包括超类的成员。
            Field[] declaredFields = cls.getDeclaredFields();
            for(Field d : declaredFields) {
                System.out.println(d.getName());
            }

            System.out.println("对实例进行属性赋值操作");
            Object instance = constructor.newInstance();  // 创建 User 类的一个实例
            //反射给属性赋值
            //查找到属性
            Field pubUserName = cls.getDeclaredField("name");
            //指定给哪个userEntity对象赋值
            //这是给公有属性赋值,默认只能访问公有属性,如果要访问私有属性,会报错
            //反射没有权限访问私有属性,如果需要访问需要设置权限setAccessible
            //设置 accessible 为 true,允许访问私有字段
            pubUserName.setAccessible(true);
            pubUserName.set(instance,"test");

            System.out.println(instance);

            System.out.println("有参构造:");
            // 获取接受 String 和 int 和 String 三个参数的构造函数
            Constructor<?> constructor2 = cls.getConstructor(String.class, int.class,String.class);

            // 使用获取的构造函数创建 User 类的实例
            Object userInstance = constructor2.newInstance("John Doe", 30,"test@163.com");

            System.out.println(userInstance);
            System.out.println("---------");


            System.out.println("反射机制的三种方式创建对象:");
            //反射机制的三种方式创建对象
            //第一种方式:通过new出来的对象获取class
            User userEntity = new User();
            Class userClass = userEntity.getClass();
            // 默认执行无参构造函数
            User user1 = (User) userClass.newInstance();
            System.out.println(user1==userEntity);//false

            //第二种方式:直接获取class
            Class userClass2 = User.class;
            User user2 = (User) userClass2.newInstance();
            System.out.println(user2);

            //第三种方式:通过完整类名获取class(常用)
            Class<?> aClass = Class.forName("com.ruoyi.luoqi.reflect.User");
            User user3 = (User) aClass.newInstance();
            System.out.println(user3);

            System.out.println("方法的基础使用:");
            //方法的基础使用
            //getMethod: 用于获取类的公共方法(public methods),包括从父类继承的公共方法
            Method method = aClass.getMethod("add", int.class,int.class);
            //调用方法
            Integer invoke = (Integer)method.invoke(user3, 1, 2);
            System.out.println(invoke);

            //getDeclaredMethod 用于获取类中声明的所有方法,不考虑它们的访问级别(即可以是公共、私有、保护或包内访问)
            Method method2 = aClass.getDeclaredMethod("subtract", int.class,int.class);
            //调用方法
            //如果需要私有方法,需要加上 setAccessible
            method2.setAccessible(true);
            Integer invoke2 = (Integer)method2.invoke(user3, 2, 1);
            System.out.println(invoke2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

自定义注解使用

这部分代码是蘑菇博客中关于日志记录自定义注解的使用:OperationLogger

OperationLogger

package com.moxi.mogublog.admin.annotion.OperationLogger;

import com.moxi.mougblog.base.enums.PlatformEnum;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 标注该该注解的方法需要记录操作日志
 *
 * @author 陌溪
 * @date 2020年3月23日09:35:57
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLogger {

    /**
     * 业务名称
     */
    String value() default "";

    /**
     * 平台,默认为WEB端
     */
    PlatformEnum platform() default PlatformEnum.ADMIN;

    /**
     * 是否将当前日志记录到数据库中
     */
    boolean save() default true;
}

切面:LoggerAspect

package com.moxi.mogublog.admin.annotion.OperationLogger;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.moxi.mogublog.admin.global.RedisConf;
import com.moxi.mogublog.admin.global.SysConf;
import com.moxi.mogublog.commons.config.security.SecurityUser;
import com.moxi.mogublog.commons.entity.ExceptionLog;
import com.moxi.mogublog.utils.*;
import com.moxi.mougblog.base.global.Constants;
import com.moxi.mougblog.base.holder.RequestHolder;
import com.moxi.mougblog.base.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.json.simple.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 日志切面
 *
 * @author 陌溪
 * @date 2020年12月31日21:26:04
 */
@Aspect
@Component
@Slf4j
public class LoggerAspect {

    @Autowired
    RedisUtil redisUtil;

    @Autowired
    ThreadPoolTaskExecutor threadPoolTaskExecutor;

    /**
     * 开始时间
     */
    Date startTime;

    //使用 @Pointcut 注解定义了一个切点,用来匹配带有 OperationLogger 注解的方法。
    @Pointcut(value = "@annotation(operationLogger)")
    public void pointcut(OperationLogger operationLogger) {

    }

    //环绕通知包装了目标方法的执行,它首先保存了方法调用的开始时间,然后执行方法,并在方法执行后进行日志记录。
    @Around(value = "pointcut(operationLogger)")
    public Object doAround(ProceedingJoinPoint joinPoint, OperationLogger operationLogger) throws Throwable {

        startTime = new Date();

        //先执行业务
        Object result = joinPoint.proceed();

        try {
            // 日志收集
            //在 handle 方法中,可以通过反射获取当前执行的方法,获取方法上的 OperationLogger 注解,并根据注解的属性决定如何记录日志。
            handle(joinPoint);

        } catch (Exception e) {
            log.error("日志记录出错!", e);
        }

        return result;
    }

    //当被注解的方法抛出异常时,这个通知会捕获异常,并记录异常日志。
    @AfterThrowing(value = "pointcut(operationLogger)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, OperationLogger operationLogger, Throwable e) throws Exception {

        ExceptionLog exception = new ExceptionLog();
        HttpServletRequest request = RequestHolder.getRequest();
        String ip = IpUtils.getIpAddr(request);
        exception.setIp(ip);
        String operationName = AspectUtil.INSTANCE.parseParams(joinPoint.getArgs(), operationLogger.value());

        //从Redis中获取IP来源
        String jsonResult = redisUtil.get(RedisConf.IP_SOURCE + Constants.SYMBOL_COLON + ip);
        if (StringUtils.isEmpty(jsonResult)) {
            String addresses = IpUtils.getAddresses(SysConf.IP + SysConf.EQUAL_TO + ip, SysConf.UTF_8);
            if (StringUtils.isNotEmpty(addresses)) {
                exception.setIpSource(addresses);
                redisUtil.setEx(RedisConf.IP_SOURCE + Constants.SYMBOL_COLON + ip, addresses, 24, TimeUnit.HOURS);
            }
        } else {
            exception.setIpSource(jsonResult);
        }

        //设置请求信息
        exception.setIp(ip);

        //设置调用的方法
        exception.setMethod(joinPoint.getSignature().getName());

        exception.setExceptionJson(JSON.toJSONString(e,
                SerializerFeature.DisableCircularReferenceDetect,
                SerializerFeature.WriteMapNullValue));
        exception.setExceptionMessage(e.getMessage());

        exception.setOperation(operationName);
        exception.setCreateTime(new Date());
        exception.setUpdateTime(new Date());

        exception.insert();
    }


    /**
     * 管理员日志收集
     *
     * @param point
     * @throws Exception
     */
    private void handle(ProceedingJoinPoint point) throws Exception {

        HttpServletRequest request = RequestHolder.getRequest();

        Method currentMethod = AspectUtil.INSTANCE.getMethod(point);

        //获取操作名称
        OperationLogger annotation = currentMethod.getAnnotation(OperationLogger.class);

        boolean save = annotation.save();

        String bussinessName = AspectUtil.INSTANCE.parseParams(point.getArgs(), annotation.value());

        String ua = RequestUtil.getUa();

        log.info("{} | {} - {} {} - {}", bussinessName, IpUtils.getIpAddr(request), RequestUtil.getMethod(), RequestUtil.getRequestUrl(), ua);
        if (!save) {
            return;
        }

        // 获取参数名称和值
        Map<String, Object> nameAndArgsMap = AopUtils.getFieldsName(point);
        // 当前操作用户
        SecurityUser securityUser = (SecurityUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String paramsJson = JSONObject.toJSONString(nameAndArgsMap);
        String type = request.getMethod();
        String ip = IpUtils.getIpAddr(request);
        String url = request.getRequestURI();

        // 异步存储日志
        threadPoolTaskExecutor.execute(
                new SysLogHandle(ip, type, url, securityUser,
                        paramsJson, point.getTarget().getClass().getName(),
                        point.getSignature().getName(), bussinessName,
                        startTime, redisUtil));
    }
}

具体内容可以看一下实际代码,同时后续有其他示例,可以继续往下写。