【Java 基础】反射

简介: 【Java 基础】反射

反射是框架的灵魂。动态代理、很多框架(SoringIOC、AOP等)中都用到了反射。


概述:


JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法(包括私有的);对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的);


这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。


要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.


1. java.lang.Class


Java 中 的java.lang.Class 类是 Java 反射机制的基础,我们想要在运行期间获取一个类的相关信息,就需要使用 Class 类。


JVM 会为每个类都创建一个 Class 对象,在程序运行时, JVM 会首先检查要加载的类对应的 Class 对象是否已经加载。如果没有加载,那么 JVM 会根据类名查找 .class 文件,并将其Class对象载入。




Student 类


@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private void eat(String name){
        System.out.println(name + " 吃东西。");
    }
    public int getBirthsDay(Integer age){
        return LocalDateTime.now().getYear() - age;
    }
}


1.1 获取 Class 对象


  1. 方式 1


调用对象的 getClass() 方法


Student student = new Student();
Class clazz = student.getClass();


  1. 方式 2


根据类名.class 获取


Class clazz = Student.class();


  1. 方式 3


根据Class类静态方法(全路径名)


Class clazz = Class.forName("com.snow.Student");


注意:在运行期间,一个类,只有一个Class对象产生。


三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都用第三种,一个字符串可以传入也可写在配置文件中等多种方法。



1.2 通过反射创建对象


使用反射创建对象有 2 种方式:


  1. 使用 Class 对象的 newInstance() 方法,其原理是通过类中定义的无参构造函数来创建对象的。


Student student = clazz.newInstance();


  1. 使用 java.lang.redlect.Constructor 类中的newInstance() 方法,这种方式既可以获取无参数的也可以获取有参数的。


//  调用无参
Student student1 = clazz1.getConstructor().newInstance();
System.out.println(student1);
//  调用有参
Student student2 
  = clazz1.getConstructor(Integer.class, String.class, Integer.class)
  .newInstance(10, "snow", 100);
System.out.println(student2);


1.3 通过反射获取类的属性、方法和注解等


除了 newInstance()方法,Class 类还有其他方法,可以在运行期间获得一个类的方法、属性和注解等。



1.3.1 反射获取构造方法


方法 说明
Constructor<?>[] getConstructors() 返回所有构造器对象的数组(只能拿public的)
Constructor<?>[] getDeclaredConstructors() 返回所有构造器对象的数组,存在就能拿到
Constructor getConstructor(Class<?>… parameterTypes) 返回单个构造器对象(只能拿public的)
Constructor getDeclaredConstructor(Class<?>… parameterTypes) 返回单个构造器对象,存在就能拿到


1.3.2 反射通过构造器创建对象


方法 说明
T newInstance(Object… initargs) 根据指定的构造器创建对象(参数为属性赋值)
public void setAccessible(boolean flag) 设置为true,表示取消访问检查,进行暴力反射


1.3.3 反射获取成员方法


方法 说明
Method[] getMethods() 返回所有成员方法对象的数组(只能拿public的)
Method[] getDeclaredMethods() 返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象(只能拿public的)
Method getDeclaredMethod(String name, Class<?>… parameterTypes) 返回单个成员方法对象,存在就能拿到(第一个是方法名称,后面的是参数类型)


Method 类中用于触发执行的方法


//  参数1:方法名 参数2:形参类型
Method eat = clazz1.getDeclaredMethod("eat", String.class);
//  设置此方法可访问(只有私有方法才需要加这个)
eat.setAccessible(true);
//  运行方法 参数1:执行该方法的对象 参数2:实际参数
eat.invoke(student1, "snow");


Method getBirthsDay = clazz1.getDeclaredMethod("getBirthsDay", Integer.class);
Integer year = (Integer) getBirthsDay.invoke(student1, 25);
System.out.println(year);


1.3.4 反射获取属性


方法 说明
Field[] getFields() 返回所有成员变量对象的数组(只能拿public的)
Field[] getDeclaredFields() 返回所有成员变量对象的数组,存在就能拿到
Field getField(String name) 返回单个成员变量对象(只能拿public的)
Field getDeclaredField(String name) 返回单个成员变量对象,存在就能拿到


Filed类 用于取值,赋值的方法:


方法 说明
void set(Object obj, Object value) 赋值
Object get(Object obj) 获取值。


Field[] declaredFields = clazz1.getDeclaredFields();
System.out.println(Arrays.toString(declaredFields));


2. 工具类操作


import org.springframework.util.ReflectionUtils;
String serviceClass = "orderUserServiceImpl";
String methodName = "getOne";
int id = 1;
//  获取方法
Method method 
     = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), methodName, Integer.class);
//用spring bean获取操作前的参数,此处需要注意:传入的id类型与bean里面的参数类型需要保持一致
//  获取对象
Object obj 
    = ReflectionUtils.invokeMethod(method, SpringContextUtil.getBean(serviceClass), id);


借助到的 SpringContextUtil 工具类:


import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext  applicationContext;
    /**
     * 实现ApplicationContextAware接口的回调方法,设置上下文环境
     */
    public void setApplicationContext(ApplicationContext applicationContext){
        SpringContextUtil.applicationContext = applicationContext;
    }
    public static ApplicationContext getApplicationContext(){
        return applicationContext;
    }
    /**
     * 获取对象
     * @return  Object 一个以所给名字注册的bean的实例 (service注解方式,自动生成以首字母小写的类名为bean name)
     */
    public static Object getBean(String name) throws BeansException {
        return applicationContext.getBean(name);
    }
}


3. 反射是如何破坏单例模式的


反射可以在运行期间获取并调用一个类的任何方法(包括私有方法)。所以使用反射可以破坏单例模式的。


SO : 如何避免单例对象被反射破坏:


只需要改造构造函数,使其在反射调用时识别对象是不是被创建即可:


private Student(){
    if(student != null){
        throw new RuntimeException(".....");
    }
}


4. 反射结合注解实现操作日志


注解相关内容:https://blog.csdn.net/m0_60915009/article/details/130677945


自定义注解:


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SnowLog {
  //  操作类型
    public String operateType();
    //  操作模块
    public String operateTarget();
    //  操作对象id
    public String operateTargetIdExpression();
}


切面类


import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import com.google.common.base.CaseFormat;
@Aspect
@Component
@Slf4j
public class SnowLogAspect {
    //  环绕通知
    @Around("@annotation(com.baga.web.controller.log.SnowLog)")
    public Object log(ProceedingJoinPoint point) throws Exception{
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        SnowLog snowLog = method.getAnnotation(SnowLog.class);
        Object response = null;
        try {
            //  执行目标方法
            response = point.proceed();
        } catch (Throwable throwable) {
            throw new Exception(throwable);
        }
        if(StringUtils.isBlank(snowLog.operateTargetIdExpression())){
            return null;
        }
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(snowLog.operateTargetIdExpression());
        EvaluationContext context = new StandardEvaluationContext();
        //  获取参数值
        Object[] args = point.getArgs();
        //  获取运行时参数名称
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        //  将参数绑定到 context 中
        if(parameterNames != null){
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
        }
        //  将方法的 resp 当做变量放到 context 中,变量名称为该类名转换为小写字母
        //  开头的驼峰形式
        if(response != null){
            context.setVariable(
                    CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,
                            response.getClass().getSimpleName()), response);
        }
        //  解析表达式,获取结果
        String operateTargetId = String.valueOf(expression.getValue(context));
        //  执行日志记录
        handleSnowLog(snowLog.operateType(), snowLog.operateTarget(), operateTargetId);
        return response;
    }
    private void handleSnowLog(String operateType, String operateTarget, String operateTargetId){
        log.info("operateType : {}, operateTarget : {}, operateTargetId : {}", operateType, operateTarget, operateTargetId);
    }
}


业务-添加


@PostMapping("/addStudyReview")
@SnowLog(operateTarget = "温故知新", operateType = "添加", operateTargetIdExpression = "#baseResult.data.id")
public BaseResult addStudyReview(@Validated(Default.class) @RequestBody StudyReviewAddModel addModel){
  StudyReview studyReview = studyReviewService.addStudyReview(addModel);
  return BaseResult.success(studyReview);
}


业务-修改


@PutMapping("/updateStudyReview")
@SnowLog(operateTarget = "温故知新", operateType = "修改", 
         operateTargetIdExpression = "#updateModel.id")
public BaseResult updateStudyReview(@Validated(Default.class) @RequestBody StudyReviewUpdateModel updateModel){
    studyReviewService.updateStudyReview(updateModel);
    return BaseResult.success();
}


业务-删除


@DeleteMapping("/end/{id}")
@SnowLog(operateTarget = "温故知新", operateType = "终结", 
         operateTargetIdExpression = "#id")
public BaseResult end(@PathVariable("id") Integer id){
StudyReview studyReview = studyReviewService.getById(id);
    if(studyReview == null || "1".equals(studyReview.getIsClose())){
        return BaseResult.success();
    }
    studyReview.setIsClose("1");
    studyReviewService.updateById(studyReview);
    return BaseResult.success();
}


业务


@PostMapping("/test")
@SnowLog(operateTarget = "温故知新", operateType = "测试", 
         operateTargetIdExpression = "#id")
public BaseResult test(Integer id, Integer age, String name){
  return BaseResult.success();
}
相关文章
|
20天前
|
存储 Java
[Java]反射
本文详细介绍了Java反射机制的基本概念、使用方法及其注意事项。首先解释了反射的定义和类加载过程,接着通过具体示例展示了如何使用反射获取和操作类的构造方法、方法和变量。文章还讨论了反射在类加载、内部类、父类成员访问等方面的特殊行为,并提供了通过反射跳过泛型检查的示例。最后,简要介绍了字面量和符号引用的概念。全文旨在帮助读者深入理解反射机制及其应用场景。
13 0
[Java]反射
|
2月前
|
安全 Java 索引
Java——反射&枚举
本文介绍了Java反射机制及其应用,包括获取Class对象、构造方法、成员变量和成员方法。反射允许在运行时动态操作类和对象,例如创建对象、调用方法和访问字段。文章详细解释了不同方法的使用方式及其注意事项,并展示了如何通过反射获取类的各种信息。此外,还介绍了枚举类型的特点和使用方法,包括枚举的构造方法及其在反射中的特殊处理。
62 9
Java——反射&枚举
|
1月前
|
安全 Java 测试技术
🌟Java零基础-反射:从入门到精通
【10月更文挑战第4天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
25 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
29天前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
16 0
|
2月前
|
Java 程序员 编译器
Java的反射技术reflect
Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
|
2月前
|
存储 安全 Java
扫盲java基础-反射(一)
扫盲java基础-反射(一)
|
2月前
|
Java
扫盲java基础-反射(二)
扫盲java基础-反射(二)
|
4月前
|
安全 Java 测试技术
day26:Java零基础 - 反射
【7月更文挑战第26天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
34 5
|
3月前
|
缓存 安全 Java
【Java 第十篇章】反射
Java 反射技术让程序能在运行时动态获取类信息并操作对象,极大提升了灵活性与扩展性。本文将介绍反射的基本概念、原理及应用,包括如何使用 `Class`、`Field`、`Method` 和 `Constructor` 类进行动态操作。此外,还将探讨反射在动态加载、框架开发与代码测试中的应用场景,并提醒开发者注意性能与安全方面的问题,帮助你更合理地运用这一强大工具。
28 0