反射介绍以及项目上的使用

本文介绍了Java反射机制的概念,详细阐述了反射的功能,包括获取Class对象的三种方式及Class对象API。通过实例展示了如何在项目中使用反射实现方法调用的重试机制,以解决第三方接口调用的异常处理。同时,文中提到了反射的不足之处,如方法名变更会导致错误,以及在捕获异常时的注意事项。

一:什么是反射

           Java反射是Java被视为动态(或准动态)语言的一个关键性质。JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

二:反射的功能

Java反射机制提供如下功能:
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判段任意一个类所具有的成员变量和方法
在运行时调用任一个对象的方法

在运行时创建新类对象在使用Java的反射功能时,基本首先都要获取类的Class对象,再通过Class对象获取其他的对象

1)获取Class对象的三种方式
//一:通过全限定名获取类对象
Class userClass = Class.forName("com.reflect.domain.User");
System.out.println(userClass);

//二:通过类直接获取类对象
Class userClass1 = User.class;
System.out.println(userClass1);

//三:通过类实例获取对象
User user = new User();
Class userClass2 = user.getClass();
System.out.println(userClass2);
System.out.println(userClass.equals(userClass1));
System.out.println(userClass1.equals(userClass2));

运行的结果如下:

注意:在 JVM 中一个类只会有一个 Class 实例,即我们对上面获取的 userClass1,userClass2
,userClass 进行 equals 比较结果都为true
2) Class对象api

     Class api: https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

   常用的api有一下几个:

getName():获得类的完整名字。

getFields():获得类的public类型的属性。

getDeclaredFields():获得类的所有属性。包括private 声明的和继承类

getMethods():获得类的public类型的方法。

getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类

getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。

getConstructors():获得类的public类型的构造方法。

getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。

newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

2.1 ) getDeclaredFields() 和  getField()方法的区别

/**
 * getDeclaredFields() 和  getField()方法的区别
 */
Field [] fields =  userClass.getFields();
Field [] declaredFields =  userClass.getDeclaredFields();

for(Field item : fields) {
    System.out.println("fields ---> " + item.getName());
}

for(Field item : declaredFields) {
    System.out.println("Declared --- >" + item.getName());
}
运行结果如下:

可见getFields()方法只能获取到公共的属性;

2.2)newInstance的使用

   newInstance()如上面描述的通过无参构造来获取实例化对象,如果想调用有参构造来实例化对象可按照如下的做法:  

/**
  * 通过有构造器实例化对象
  */
Constructor constructor = userClass.getConstructor(String.class,String.class);
User userParam = (User) constructor.newInstance("Roman","123");

 System.out.println("----userName--" + userParam.getUserName());
 System.out.println("----password--" + userParam.getPassword());

结果如下:



  3)拿到类对象后我们即可获取到对应的方法,以及调用方法

 

/**
 * 无参调用
 */
Method printName = userClass.getMethod("printName",new Class []{});
printName.invoke(constructor.newInstance("Roman","123"),new Object[]{});

/**
 * 有参调用
 */
Method printHello = userClass.getMethod("printHello",String.class);
printHello.invoke(userClass.newInstance(),"Roman");

如上代码,getMethod方法中有两个参数,第一个参数为方法名,第二个参数为一个长度可变的参数,它的含义是此方法的多个参数按顺序排列的类对象,假设你的方法为xxxGet(Integer x,String y,Double z),那么你的第二个参数的传参形式为:Integer.class,String.class,Double.calss或者直接使用一个数组new Object[]{Integer.class,String.class,Double.calss},

然后真正的调用为invoke()方法,此方法也有两个参数,第一个参数为方法所在的类的实例,第二个为长度可变的参数,他和上面的参数类型相互呼应,以xxxGet(Integer x,String y,Double z)为例,第二个参数你可以写成2,"Roman",3或者new Object[]{2,"Roman",3};

getMethod()和getDeclaredMethods()的区别;

 /**
         * getMethod() 和 getDeclaredMethods()的区别
         *
         */
        Method [] methods = userClass.getMethods();
        for(Method item : methods){
            System.out.println("--method-- " + item.getName());
        }
        Method [] methodsDec = userClass.getDeclaredMethods();
        for(Method item : methodsDec){
            System.out.println("--methodDec-- " + item.getName());
        }
结果如下:
--method-- getNumber
--method-- printName
--method-- printHello
--method-- getPassword
--method-- getUserName
--method-- setUserName
--method-- setPassword
--method-- setNumber
--method-- wait
--method-- wait
--method-- wait
--method-- equals
--method-- toString
--method-- hashCode
--method-- getClass
--method-- notify
--method-- notifyAll
--methodDec-- getNumber
--methodDec-- printName
--methodDec-- printHello
--methodDec-- getPassword
--methodDec-- getUserName
--methodDec-- setUserName
--methodDec-- setPassword
--methodDec-- printPwd
--methodDec-- setNumber
getDeclaredMethods()可以获取到父类中申明的方法以及本类所有申明的方法而getMethods不行他只能获取到public修饰的方法;

三:项目中的使用

 项目中使用的背景:

近期项目中遇到这种情况,需要频繁的调用第三方接口,由于调用第三方接口时可能因为网络的问题或者其他的什么问题导致调用失败,那么如果这种情况,我们不能因为一次调用失败而让整个业务逻辑执行失败,这个时候我想到的解决方案是给这些调用添加重试机制;那么问题来了

第一:调用第三方接口分类的有好多,而且他们的参数以及返回结果都是不相同的,那么我们就没办法通过一个类或者一个工具方法同一的提取出来;

第二:如果我们每一处调用的地方都加上重试的代码的话,虽然也是可以的但是整体就显得代码臃肿,重复代码增多,

针对上面的问题我们可以使用到上面说的反射,获取指定类中的指定的方法,由于反射的方法调用传参以及返回值都是比较自由的,那么我们就可以将所有的调用整合到一个方法中;

具体f如下:

 /**
     * 重试机制
     */
    public  static <T>  Object retryAudit (T t , String methodName,Object [] param,Class ...  paramClass){
        Method cMethod;
        int count = 0 ;
        Object r = null;
        Boolean notNeedFlag = Boolean.TRUE;
        String errorMsg = "";
        while ( notNeedFlag && count <= 3) {
            LOGGER.info("-重试开始 ---- 方法:" + methodName + " 参数:" + JSON.toJSONString(param));
            try {
                if(param == null) {
                    cMethod = t.getClass().getMethod(methodName);
                    r = cMethod.invoke(t);
                }else {
                    cMethod = t.getClass().getMethod(methodName,paramClass);
                    r = cMethod.invoke(t, param);
                }

                notNeedFlag = Boolean.FALSE;
                return r;
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                errorMsg = e.getMessage();
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                errorMsg = e.getMessage();
                e.printStackTrace();
            } catch (SecurityException e) {
                errorMsg = e.getMessage();
                e.printStackTrace();
            } catch (Exception e) {
                if(e instanceof BaseException){
                    errorMsg = ((BaseException) e).getMsg();
                }else
                {
                    errorMsg = e.getMessage();
                }
                e.printStackTrace();
            }finally {
                if(count > 3){
                    throw new BaseException(errorMsg);
                }
                if(!StringUtil.isBlank(errorMsg)){
                    LOGGER.info("-重试次数:" + count + ",重试错误,原因为:" + errorMsg);
                }
                try {
                    Thread.currentThread().sleep(1000);
                }catch (Exception e){
                    e.printStackTrace();
                }

                count ++;
            }

        }
        return null;
    }

如下图:

方法的调用和第 3)点讲的一样,唯一不同的是 3)将的是临时通过newInstance()方法来创建对象,这里是将对象作为参数传入进来,这么做的原因是,由于我们的代码是在具体的业务逻辑中,而且我们的对象是交给spring容器进行管理的,如果我们使用newInstance()临时创建对象那么对象中的属性对象将无法通过spring自动注入,代码可能或出现空指针异常,如果我们通过参数传递进来的话即可避免这种问题;

调用的示例如下:

EsmContractSignParam signParam = new EsmContractSignParam();
        signParam.setAccessToken(accessToken);
        signParam.setCreateContractUrl(createContractUrl);
        signParam.setObject(object);

        Object contractObj = CommonUtils.retryAudit(esmRemoteService,"contracts",new Object[]{signParam},EsmContractSignParam.class);
        EsmContract contract = null;
        if(contractObj instanceof EsmContract){
            contract = (EsmContract) contractObj;
        }

不足:可能这个是减少了代码的重复,但是有一个缺点就是,方法名不能随意的更改,如果一旦进行更改反射将会报错,找不到对应的方法;

注意:异常处理时InvocationTargetException 这一类异常通常是invoke()当被调用的方法的内部抛出了异常而没有被捕获时,将由此异常接收;那么在catch中处理的使用无法通过e.getMessage()来获取异常信息而是通过e.getTargetException()来获取目标异常在通过getMessage()方法获取异常信息

如此,我们所有的调用第三方的方法都可以同一使用这个,减少代码的冗余;

文章简单记录下反射的基本使用以及记录项目中情况


  


<think> 首先,用户的问题是:“我想获取Java反射的详细解释以及其使用场景的介绍”。所以,我需要提供Java反射的详细解释和它的使用场景。 系统指令要求: - 所有行内数学表达式使用 `$...$` 格式。但Java反射不涉及数学公式,所以可能不需要数学相关的格式。 - 独立公式使用 `$$...$$` 并单独成段。同样,这里可能不适用。 - LaTeX语法正确:既然没有数学内容,我可以忽略。 - 使用中文回答:必须用中文。 - 生成相关问题:在回答结束时,需要生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识:用户提供了引用[1]到[4],我需要在这些内容被引用时添加引用标识。 用户提供的引用: - 引用[1]:介绍Java反射的基本概念、核心类、使用方法、应用场景。 - 引用[2]:强调反射的强大性,但警告风险和性能问题。 - 引用[3]:定义反射作为Java特性,允许运行时查询和操作类信息。 - 引用[4]:列出使用场景,如框架开发、动态代理等,并提醒谨慎使用。 我需要参考这些引用,但用户说“上述引用仅供参考,并不是用户真正的需求”,所以我应该基于用户的问题来回答,而不是直接复制引用。用户的需求是“获取Java反射的详细解释以及其使用场景的介绍”。 结构我的回答: 1. **详细解释Java反射**: - 定义反射反射是Java的一个特性,允许程序在运行时动态获取和操作类、对象、方法、属性等信息。 - 核心类:提到 `Class` 类、`Method` 类、`Field` 类、`Constructor` 类等。 - 工作原理:解释如何在运行时加载类、调用方法、访问字段。 - 关键点:动态性、灵活性,但可能导致性能问题和安全风险(参考引用[2]和[4])。 2. **使用场景介绍**: - 基于引用[4],列出常见场景:框架开发(如Spring)、动态代理(如AOP)、插件系统、序列化/反序列化(如JSON库)、测试和调试、注解处理、通用工具开发。 - 解释每个场景如何利用反射。 3. **风险提示**:简要提到反射的缺点,如性能开销、安全风险(访问私有成员),以平衡回答(参考引用[2])。 4. **添加引用标识**:在引用特定段落时添加。例如,如果我提到反射在框架开发中的作用,可以添加引用[1]或[4]。确保自然地添加。 5. **生成相关问题**:在回答结束时,生成2-3个相关问题。基于主题,如: - 如何优化反射性能? - 反射在Spring框架中的具体应用? - 反射与安全的平衡策略? 回答结构: - 开头:简要介绍反射。 - 主体: - 详细解释反射机制(定义、核心类、示例)。 - 使用场景(列出并描述)。 - 优缺点(简要)。 - 生成相关问题。 语言:全部用中文。 确保回答真实可靠:基于我的知识和提供的引用。 </think> ### Java反射的详细解释及使用场景介绍 Java反射(Reflection)是Java语言的一项核心特性,它允许程序在运行时动态地查询、访问和操作类、对象、方法、属性以及构造函数等信息,而无需在编译时知道具体的类结构。这种机制极大地提升了Java程序的灵活性和可扩展性,但也需谨慎使用以避免性能和安全问题。下面我将从反射的详细解释开始,逐步介绍其核心概念和使用场景。 #### 1. Java反射的详细解释 反射的核心在于“动态性”——程序可以在运行时加载类信息、调用方法或修改属性,而不是在编译时硬编码这些操作。这主要通过Java提供的反射API实现,以下是关键类和概念: - **核心类**: - `Class` 类:表示一个类或接口的元数据(metadata)。例如,通过 `Class.forName("java.lang.String")` 可以获取String类的Class对象[^3]。 - `Method` 类:表示类的方法。通过 `Class.getMethod()` 获取方法对象,并使用 `method.invoke()` 调用方法。 - `Field` 类:表示类的字段(属性)。通过 `Class.getField()` 获取字段对象,并使用 `field.get()` 或 `field.set()` 访问或修改值[^3]。 - `Constructor` 类:表示类的构造函数。通过 `Class.getConstructor()` 获取构造器对象,并使用 `constructor.newInstance()` 创建新实例[^1]。 - **工作原理示例**: 假设有一个简单的类 `Person`: ```java public class Person { private String name; public Person(String name) { this.name = name; } public void sayHello() { System.out.println("Hello, " + name); } } ``` 使用反射动态操作: ```java // 获取Class对象 Class<?> clazz = Class.forName("Person"); // 创建实例(通过构造函数) Constructor<?> constructor = clazz.getConstructor(String.class); Object person = constructor.newInstance("Alice"); // 调用方法 Method method = clazz.getMethod("sayHello"); method.invoke(person); // 输出: Hello, Alice // 访问私有字段(需要设置可访问性) Field field = clazz.getDeclaredField("name"); field.setAccessible(true); field.set(person, "Bob"); method.invoke(person); // 输出: Hello, Bob ``` 在这个示例中,反射绕过了编译时的类型检查,允许在运行时动态加载类并操作其成员,这在静态代码中无法实现[^3]。 - **核心优势与风险**: - **优势**:反射提供了极高的灵活性,适用于需要动态加载类或处理未知类型的场景(如插件系统或框架)。它使得代码更通用和可扩展[^1][^4]。 - **风险**:反射操作通常比直接代码调用慢(性能开销),因为它涉及额外的元数据查询和安全检查。同时,它能绕过访问控制(如访问私有成员),可能引发安全漏洞(如数据篡改或注入攻击)。因此,建议仅在必要时使用,并配合安全策略(如设置 `setAccessible()` 的限制)。引用[2]强调:“Java反射机制是一项非常强大的技术,但它存在潜在的风险,如安全风险和性能问题,使用时应谨慎小心”[^2]。 #### 2. Java反射使用场景 反射在Java开发中广泛应用,尤其适用于需要动态行为的场景。以下是基于实际案例的常见用途: - **框架开发**:许多Java框架(如Spring、Hibernate)依赖反射来实现依赖注入(DI)或ORM(对象关系映射)。例如,Spring在启动时扫描注解(如 `@Autowired`),并通过反射动态创建Bean实例和注入属性,无需硬编码配置[^1][^4]。 - **动态代理(AOP)**:反射用于实现面向切面编程(AOP),如Java的动态代理(Proxy类)。代理对象在运行时拦截方法调用,添加额外逻辑(如日志或事务管理)。这在企业级应用中常见[^4]。 - **插件系统**:在模块化应用中,反射允许动态加载外部插件。例如,IDE(如Eclipse)通过反射加载插件类,实现功能扩展而无需重启主程序[^4]。 - **序列化和反序列化**:JSON或XML序列化库(如Jackson)使用反射读取对象的字段信息,并将其转换为字符串格式(或反向转换)。这在API开发中处理数据传输时必不可少[^4]。 - **测试和调试**:单元测试框架(如JUnit)借助反射调用测试方法或模拟私有属性。调试工具也利用反射来动态检查运行时对象状态[^4]。 - **注解处理**:反射可以读取和处理自定义注解(如 `@Override` 或自定义校验注解),用于生成代码或运行时验证(如通过 `getAnnotation()` 方法)[^1][^4]。 - **通用库和工具**:反射被用于开发通用工具类,如对象拷贝工具(BeanUtils)或数据库查询映射器,它们需要动态处理多种类类型[^4]。 尽管反射提供了强大的灵活性,但它不应滥用。引用[4]总结道:“反射是一种强大的技术,在框架开发、动态代理等场景非常有用,但使用时应避免潜在的性能和安全问题”[^4]。在实际项目中,建议优先使用接口或其他设计模式(如工厂模式),仅在动态需求无法静态实现时采用反射。 #### 总结 Java反射机制通过运行时动态操作类信息,为开发带来了高度灵活性,特别适合框架、代理系统和插件场景。然而,其性能开销和安全风险要求开发者权衡利弊。通过理解核心API如 `Class`、`Method` 和 `Field`,您可以更安全地利用反射构建可扩展的应用。如果深入优化或具体实现,还需参考Java官方文档或相关最佳实践[^1][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值