快捷搜索:  汽车  科技

程序员重复造轮子没意义:你知道反射机制吗

程序员重复造轮子没意义:你知道反射机制吗概念上的东西就说这么多,回到实际的coding部分:测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。Oracle文档: 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。

程序员重复造轮子没意义:你知道反射机制吗(1)

平时写代码的过程中,我们使用不同的工具框架来提升开发效率,除了基础框架之外,我们自己也想造轮子,封装各种业务平台功能;

一旦需造轮子的时候,那么就需要使用Java造轮子利器:反射;

一些项目中常见的反射应用场景:

  • 泛化调用: 提前不知道目标RPC的接口和方法,而是开发在后台输入值,根据输入的配置动态请求。 这也是提升效率的一部分,因为不可能所以得RPC接口都要亲自对接的,总要有一部分可以灵活的调用不同接口。
  • 自测入口: 我们的逻辑代码一般会散落在应用的不同位置,如果想要debug,一般会有一个核心入口,但是如果核心入口太长,想要进入我们的逻辑分支很难呢? 我们可以统一收口下测试入口,这个测试入口在开发环境下就是可以访问到所有的对象,服务,组件等功能,直接对目标逻辑进行调试即可, 这个也是需要反射.

其实上面两个场景也说明了反射应用场景的一个特点: 我不知道现在要调用哪个类和哪个方法,等到运行时才知道要调用的类和方法。

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

Oracle文档: 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。

反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。

测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。

概念上的东西就说这么多,回到实际的coding部分:

程序员重复造轮子没意义:你知道反射机制吗(2)

反射使用1. 准备测试对象

import io.mybatis.provider.Entity; import javax.persistence.Id; import java.io.Serializable; @Entity.Table(value = "Blog" autoResultMap = true) public class Blog implements Serializable { @Id @Entity.Column private Long id; @Entity.Column private String title; public Blog() { } private Blog(Long id) { this.id = id; } public Blog(Long id String title) { this.id = id; this.title = title; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } @Override public String toString() { return "Blog{" "id=" id " title='" title '\'' '}'; } } 复制代码2. 拿到Class信息

有三种方式可以拿到Class信息:

  • 第一种方法有限制条件:需要导入类的包;
  • 第二种:需要知道对象的全路径,也是最常使用的方式
  • 第三种方法Blog对象,仅仅想拿到Class信息,一般不需要反射。

并且在同一个运行环境中,三种方式拿到的Class对象是同一个,每个类只生成一个Class对象,Class对象一般在JVM的metaspace区域中。

@Test public void testClass() throws ClassNotFoundException { Class<Blog> blogClass = Blog.class; Class<?> blogClassForName = Class.forName("me.aihe.bizim.dal.bean.Blog"); Class<? extends Blog> blogClassForGet = new Blog().getClass(); System.out.println(blogClassForGet == blogClass); System.out.println(blogClassForGet == blogClassForName); } 复制代码3. 关键API

拿到Class信息之后,我们基本上就可以对这个对象做各自想要的操作了;

反射中有两种命名方式:

  • getXXX 获取公共的对象,即标记为public的
  • getDeclaredXXX 获取已经声明的,也就是对象中所有的XXX

setAccessible 函数用于动态获取访问权限,一般对象如果声明为private的需要setAccessible(true)才可以进行调用。

构造方法操作 - Constructor

如果类的构造方法一定要传参数,可以根据获取到的Constructor来创建对象;

@Test public void testConstruct() throws ClassNotFoundException NoSuchMethodException InvocationTargetException InstantiationException IllegalAccessException { Class<?> aClass = Class.forName("me.aihe.bizim.dal.bean.Blog"); Constructor<?>[] constructors = aClass.getConstructors(); Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { System.out.println("getConstructors:" constructor); } for (Constructor<?> constructor : declaredConstructors) { System.out.println("getDeclaredConstructors:" constructor); } // 使用构造方法 Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Long.class); declaredConstructor.setAccessible(true); Object blog = declaredConstructor.newInstance(1L); System.out.println(blog); } 复制代码属性操作 - Field

拿到了对象的属性之后,可以通过Field进行set和get操作,在更新数据库的时候,如果要带上创建人修改人,可以根据请求上下文中的用户,同feild方式写入到数据库的对象中(对象的命名保持统一)

@Test public void testFiled() throws ClassNotFoundException IllegalAccessException NoSuchFieldException { Class<?> aClass = Class.forName("me.aihe.bizim.dal.bean.Blog"); Field title = aClass.getDeclaredField("title"); title.setAccessible(true); Blog obj = new Blog(); System.out.println(obj); title.set(obj "测试"); System.out.println(obj); System.out.println(title.get(obj)); } 复制代码

一个可以往某个对象根据fieldName设置值,和获取值的工具方法:

public static Object getFiledValue(Object obj String filedName){ try { Class<?> clazz = obj.getClass(); Field field = null; do { try { field = clazz.getDeclaredField(filedName); if (field != null){ field.setAccessible(true); break; } }catch (Exception e){ } clazz = clazz.getSuperclass(); } while (!clazz.equals(Object.class)); if (field != null){ Object o = field.get(obj); return o; } }catch (Exception e){ log.error("getFiledValue error" e); return null; } return null; } public static void setField(Object obj String filedName Object value){ Class<?> clazz = obj.getClass(); Field field = null; do { try { field = clazz.getDeclaredField(filedName); if (field != null){ field.setAccessible(true); } }catch (Exception e){ } clazz = clazz.getSuperclass(); } while (!clazz.equals(Object.class)); if (field != null){ try { field.set(obj value); }catch (Exception e){ log.error("setField error " e); } } } 复制代码方法操作 - Method

和属性操作类似,不同的是方法可能有多个参数;

@Test public void testMethod() throws ClassNotFoundException IllegalAccessException NoSuchMethodException InvocationTargetException { Class<?> aClass = Class.forName("me.aihe.bizim.dal.bean.Blog"); Method setTitle = aClass.getMethod("setTitle" String.class); Method getTitle = aClass.getMethod("getTitle"); Blog obj = new Blog(); System.out.println(obj); setTitle.invoke(obj "测试"); System.out.println(obj); System.out.println(getTitle.invoke(obj)); } 复制代码注解操作 - Annoation

注解可以加在字段上,方法上,类上,有时候我们拿到了对象或者方法也不知道做什么,通过注解,可以很好的做想做的事情。当然注解已经不算是反射的范畴了,但是可以让反射的功能更加强大。

  • 比如Spring中标记了@RequestMapping注解的方法,我们知道这个是用来处理Http请求的,就可以在框架内做一些处理。
  • 一些自定义的注解,在启动的时候扫描到这些注解,将这些对象放到某个集合中统一处理。

获取类、方法、属性上的注解:

@Test public void testAnnotation() throws ClassNotFoundException NoSuchMethodException NoSuchFieldException { Class<?> aClass = Class.forName("me.aihe.bizim.dal.bean.Blog"); Annotation[] annotations = aClass.getAnnotations(); Method getTitle = aClass.getMethod("getTitle"); Annotation[] annotations1 = getTitle.getAnnotations(); Field title = aClass.getDeclaredField("title"); Annotation[] annotations2 = title.getAnnotations(); for (Annotation annotation : annotations) { System.out.println("类注解:" annotation); } for (Annotation annotation : annotations1) { System.out.println("方法注解:" annotation); } for (Annotation annotation : annotations2) { System.out.println("属性注解:" annotation); } } 复制代码4. 泛型操作 - Type

有时候在代码中写的是泛型,不确定具体是什么对象,也可以通过泛型拿到泛型信息: 泛型类型相对比较复杂。

泛型常用API

在类、方法、构造器、属性上都可以获取到泛型:

java.lang.Class中的相关方法:

  • Type[] getGenericInterfaces() 返回类实例的接口的泛型类型
  • Type getGenericSuperclass() 返回类实例的父类的泛型类型

java.lang.reflect.Constructor中的相关方法:

  • Type[] getGenericExceptionTypes() 返回构造器的异常的泛型类型
  • Type[] getGenericParameterTypes() 返回构造器的方法参数的泛型类型

java.lang.reflect.Method中的相关方法:

  • Type[] getGenericExceptionTypes() 返回方法的异常的泛型类型
  • Type[] getGenericParameterTypes() 返回方法参数的泛型类型
  • Type getGenericReturnType() 返回方法返回值的泛型类型

java.lang.reflect.Field中的相关方法:

  • Type getGenericType() 返回属性的泛型类型
获取泛型具体的Class

@Test public void testGeneric(){ BlogImpl blog = new BlogImpl(); Class kClass = blog.getKClass(); Class vClass = blog.getVClass(); System.out.println(kClass); System.out.println(vClass); } public static abstract class BaseClass<K V>{ public Class getKClass(){ Type genericSuperclass = this.getClass().getGenericSuperclass(); ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); Class tClass = (Class) actualTypeArguments[0]; return tClass; } public Class getVClass(){ Type genericSuperclass = this.getClass().getGenericSuperclass(); ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); Class tClass = (Class) actualTypeArguments[1]; return tClass; } public abstract V execute(K k); } public static class BlogImpl extends BaseClass<Blog Long>{ @Override public Long execute(Blog blog) { return blog.getId(); } } 复制代码

获取泛型的Class信息

private static Class<?> getDetailClass(Type type int i) { if (type instanceof ParameterizedType) { // 处理泛型类型 ParameterizedType parameterizedType = (ParameterizedType)type; Type actualTypeArgument = parameterizedType.getActualTypeArguments()[i]; return getDetailClass(actualTypeArgument i); } else if (type instanceof GenericArrayType) { // 处理数组泛型 return (Class<?>)((GenericArrayType)type).getGenericComponentType(); } else if (type instanceof TypeVariable) { // 处理泛型擦除对象<R> return (Class<?>)getDetailClass(((TypeVariable)type).getBounds()[0] 0); } else { return (Class<?>)type; } } 复制代码5. 反射工具类

在commons-lang3包中,也已经封装了对应的反射工具,如果已经引入了这个包,直接使用即可:

程序员重复造轮子没意义:你知道反射机制吗(3)

泛型的性能为什么比直接调用差?

可以看到泛型性能比直接调用差很多,为什么泛型的性能会比正常调用差?

  • 泛型在执行的时候会校验方法、字段名称,校验是否有对应的权限,会比直接调用多一部分逻辑。
  • 无法被jit优化,JIT可以帮助java的字节码到原生的机器码层面上,这样的话减少了java字节码的再解析操作,而反射方法是无法被jit优化的。
  • 调用过程中的封装与解封操作,invoke 方法的参数是 Object[] 类型,在调用的时候需要进行一次封装。产生了额外的开销。

性能测试代码:

import org.apache.commons.lang3.time.StopWatch; import org.junit.jupiter.api.Test; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class PerformanceTest { private static int COUNT = 100000; @Test public void testReflect() throws ClassNotFoundException NoSuchMethodException InstantiationException IllegalAccessException InvocationTargetException { StopWatch started = StopWatch.createStarted(); for (int i = 0; i < COUNT; i ) { Class<?> aClass = Class.forName("me.aihe.bizim.dal.bean.Blog"); Method setTitle = aClass.getMethod("setTitle" String.class); Object obj = aClass.newInstance(); setTitle.invoke(obj "测试"); } System.out.println("泛型 " COUNT "次耗时(ms):" started.getTime()); } @Test public void testDirectUse() throws ClassNotFoundException NoSuchMethodException InstantiationException IllegalAccessException InvocationTargetException { StopWatch started = StopWatch.createStarted(); for (int i = 0; i < COUNT; i ) { Blog blog = new Blog(); blog.setTitle("测试"); } System.out.println("直接调用 " COUNT "次耗时(ms):" started.getTime()); } } 复制代码

程序员重复造轮子没意义:你知道反射机制吗(4)

总结
  1. 反射在Java的框架中非常常见,如果说自己想要封装一些通用框架,需要了解反射相关的知识。
  2. 本文主要是知识层面的内容,介绍了反射常用的API,泛型的一些API;
  3. 最后就可以验证下泛型是比直接调用性能差一些,主要原因是要校验权限,无法JIT优化,自动装箱拆箱等导致的。
  4. 泛型的优点是强大且灵活,缺点是难用并且慢...

希望能对大家有所帮助

原文链接:https://juejin.cn/post/7148813463308435463#comment

猜您喜欢: