Java开发反射机制的实战经验总结

 更新时间:2021年07月07日 15:44:05   作者:朱季谦  
反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接,但是反射使用不当会成本很高,这篇文章主要给大家介绍了关于Java开发反射机制的相关资料,需要的朋友可以参考下

前言

我在实际项目当中有经常用到反射机制,故而将学会的反射用法做一些汇总笔记,当做以后复盘所用。

存在这样一个类:

package com.example.demo;
import com.alibaba.fastjson.annotation.JSONField;
public class User {
    private String name;
    @Value( value ="age_a")
    private String age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
     public String getAge() {
        return age;
    }

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

一、创建Class的三种方式

1 - Class clazz = Class.forName("com.example.demo.User");

注意一点,这里的forName("xxx")的类名需要全名,且为接口或类,否则加载不了。

2 - User user = new User();

Class clazz2 = user.getClass();

3 - Class clazz3 = User.class;

以上三种方式,都可以获取到类User的Class对象,通过Class,即可以开始玩反射了。

二、反射获取类的所有属性和属性类型

Class clazz = User.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    System.out.println("属性名:"+field.getName());
    System.out.println("属性的类型:"+field.getGenericType().getTypeName());
}

打印输出User的属性和属性类型——

属性名:name

属性的类型:java.lang.String

属性名:age

属性的类型:java.lang.String

利用反射获取到类的字段属性后,是不是可以利用反射来创建一个对象呢?答案是肯定的。

例如,可以类似下面代码,通过反射得到的字段属性,进而创建一个对象。

Map<String,Object> fileds = new HashMap<>();
fileds.put("name","张三");
fileds.put("age","10");
Object o = User.class.newInstance();
 Field[] fields = o.getClass().getDeclaredFields();
 for (Field field : fields) {
     //设置后可用反射访问访问私有变量
     field.setAccessible(true);
     //通过反射给属性赋值
     field.set(o,fileds.get(field.getName()));
 }
 User user1 = (User) o;
 System.out.println(user1.toString());

什么场景下可能需要这样做的呢?像一些内部数据与外部数据字段的映射,就可以通过类似的字段反射方式,将源数据映射给目标数据,进而得到可以插入数据库的目标对象。

三、反射动态修改类属性的注解值

注意一点,我们在设置User类时,对其中一个字段加了注解:@Value( value ="age_a")。这是一种设置值的注解,既然是设置值,是否还可以在代码运行过程中,根据不同情况来动态修改呢?

字段上的注解,其实都存放在一个memberValues属性里,这是一个map,可以这样来获取——

Field[] fields = User.class.getDeclaredFields();
for (Field field : fields) {
    //设置后可用反射访问访问私有变量
    if ("age".equals(field.getName() )){
        field.setAccessible(true);
       //获取 annotation 这个代理实例所持有的 InvocationHandler
       InvocationHandler invocationHandler = Proxy.getInvocationHandler(field.getAnnotation(Value.class));
       // 获取 InvocationHandler 的 memberValues 字段
        Field memberValues = invocationHandler.getClass().getDeclaredField("memberValues");
        memberValues.setAccessible(true);
        Map<String, Object> values = (Map<String, Object>) memberValues.get(invocationHandler);
        System.out.println(values);
    }
}

debug打断点,可以看到——

这个Map<String,Object>存储的是该@注解里的所有属性值,这里,@Value只有一个value属性——

public @interface Value {
    String value();
}

若把它换成类似@JSONField(name="age_a"),把上边的代码稍微修改下,如:

Field[] fields = User.class.getDeclaredFields();
for (Field field : fields) {
    if ("age".equals(field.getName() )){
        field.setAccessible(true);
          InvocationHandler invocationHandler = Proxy.getInvocationHandler(field.getAnnotation(JSONField.class));
  ......
    }
}

@JSONField注解的内部属性有如下方式——

再运行刚刚的代码,可以看到,这里Map<String,Object>获取存储到的,便是这个注解里所有的属性与对应的属性值。

到了这一步,回到先前上边的问题,若要动态改变这个注解的值,怎么处理呢?

其实,很简单,只需要直接进行值设置就可以了,例如——

InvocationHandler invocationHandler = Proxy.getInvocationHandler(field.getAnnotation(Value.class));
Field memberValues = invocationHandler.getClass().getDeclaredField("memberValues");
memberValues.setAccessible(true);
Map<String, Object> values = (Map<String, Object>) memberValues.get(invocationHandler);
values.put("value","new_age");
memberValues.setAccessible(false);

只是,注意一点是,这里的key需要对应上注解里是属性值。

四、反射获取类的方法及调用方式

 Object o=User.class.newInstance();
//通过反射获取到User的setAge方法,后面的String.class表示这个setAge方法的参数类型,若有多个,则按顺序列出
//同时,若为其他类型,如List,Long,则为List.class,Long.class
 Method m =  (Method) o.getClass().getMethod("setAge",String.class);
 m.invoke(o,"name");
 User user = (User) o;
 System.out.println(user);

打印可见,age已为name,说明setAge调用成功了。

这类使用场景,在代理当中出现比较多。

最后,通过反射实现一个Map转成对象的封装工具——

   public Object MapToObject(Object object,Map<String, Object> map) throws IllegalAccessException {
        Class cla =  object.getClass();
        Field[] fields = cla.getDeclaredFields();
        for(Field field:fields){
            field.setAccessible(true);
            if("serialVersionUID".equals(field.getName()))continue;
            if(map.get(field.getName())!=null) {
                Object value=map.get(field.getName());
                value=convertValType(value,field.getType());
                field.set(object, value);
            }
        }
        return object;
    }


    private static Object convertValType(Object value, Class<?> fieldTypeClass) {
        Object o = null;
        if (Long.class.getName().equals(fieldTypeClass.getName())
                || long.class.getName().equals(fieldTypeClass.getName())) {
            o = Long.parseLong(value.toString());
        } else if (Integer.class.getName().equals(fieldTypeClass.getName())
                || int.class.getName().equals(fieldTypeClass.getName())) {
            o = Integer.parseInt(value.toString());
        } else if (Float.class.getName().equals(fieldTypeClass.getName())
                || float.class.getName().equals(fieldTypeClass.getName())) {
            o = Float.parseFloat(value.toString());
        } else if (Double.class.getName().equals(fieldTypeClass.getName())
                || double.class.getName().equals(fieldTypeClass.getName())) {
            o = Double.parseDouble(value.toString());
        } else {
            retVal = o;
        }
        return retVal;
    }

总结

到此这篇关于Java反射机制的文章就介绍到这了,更多相关Java开发反射机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • maven加载依赖报错的原因分析及解决方法

    maven加载依赖报错的原因分析及解决方法

    通常我们在项目中引入第三方依赖包时,为了避免其版本迭代问题,经常会使用本地的包,这篇文章主要给大家介绍了关于maven加载依赖报错的原因分析及解决方法的相关资料,需要的朋友可以参考下
    2023-10-10
  • Spring实例化bean过程解析及完整代码示例

    Spring实例化bean过程解析及完整代码示例

    这篇文章主要介绍了Spring实例化bean过程解析及完整代码示例,简单分析实例化bean过程并且分享了相关实例,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化

    Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验

    这篇文章主要介绍了Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化的相关知识,需要的朋友可以参考下
    2017-12-12
  • springboot如何实现国际化配置

    springboot如何实现国际化配置

    这篇文章主要介绍了springboot如何实现国际化配置问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • Spring基于AspectJ的AOP开发案例解析

    Spring基于AspectJ的AOP开发案例解析

    这篇文章主要介绍了Spring的基于AspectJ的AOP开发,AspectJ是一个基于Java语言的AOP框架,使用AspectJ需要导入Spring AOP和AspectJ相关jar包,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-05-05
  • 全面解析Java中的引用类型

    全面解析Java中的引用类型

    在Java中对象以引用来指向JVM的内存区块,这里我们总结了强引用、软引用、弱引用和假象引用(幽灵引用),下面就具体来全面解析Java中的引用类型:
    2016-05-05
  • 使用Java实现5种负载均衡算法实例

    使用Java实现5种负载均衡算法实例

    负载均衡指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助,这篇文章主要给大家介绍了关于使用Java实现5种负载均衡算法的相关资料,需要的朋友可以参考下
    2021-09-09
  • Java 详解包装类Integer与int有哪些共通和不同

    Java 详解包装类Integer与int有哪些共通和不同

    这篇文章主要介绍的是 Java中int和Integer的区别,Java 是一种强数据类型的语言,因此所有的属性必须有一个数据类型,下面文章基于Java详细int和Integer有何区别,需要的朋友可以参考一下
    2022-04-04
  • Spring Boot如何优化内嵌的Tomcat示例详解

    Spring Boot如何优化内嵌的Tomcat示例详解

    spring boot默认web程序启用tomcat内嵌容器,监听8080端口,下面这篇文章主要给大家介绍了关于Spring Boot如何优化内嵌Tomcat的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-09-09
  • SpringBoot处理请求参数中包含特殊符号

    SpringBoot处理请求参数中包含特殊符号

    今天写代码遇到了一个问题,请求参数是个路径“D:/ExcelFile”,本文就详细的介绍一下该错误的解决方法,感兴趣的可以了解一下
    2021-06-06

最新评论