探索Java无反射机制,如何节省启动内存资源消耗!
Posted 训练营资料福利官
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了探索Java无反射机制,如何节省启动内存资源消耗!相关的知识,希望对你有一定的参考价值。
随着Java更新的新特性,例如体系结构决策及其要求。当前,云计算通常要求应用程序除了初始内存量少之外还要拥有更好的启动性。因此,有必要重新设计框架的制作方式,以消除反射的瓶颈。
在框架中,反射在发挥着重要作用,无论是经典的ORM还是JAX-RS之类的REST API 。通过大量的减少各种操,从而使Javaer的工作变得更轻松。
对于终端用户(这里指的是使用这些框架的用户)整个过程只需在类中添加一些符号,所有操作即可正常运行。它们的类元数据将被读取并用于促进某些进程。当前,执行这种类型的最流行的方法是通过内省,从而使Java的动态语言概念轻而易举地生产出来。
由于创建了大量资源,并提供了此类工作的示例和文档,因此在框架内使用反射API简化了此类工作。但是,由于某些原因,我们在这里讨论两个问题:启动应用程序延迟和内存消耗。
启动应用程序延迟:所有处理和数据结构将在执行时执行。想象一下一个依赖项注入引擎,它需要逐级扫描,检查范围,依赖项等等。因此,需要分析的类别越多,所需的处理就越多,并且大大的增加响应时间。
内存消耗:每个类都需要遍历以在Class中搜索元数据,有一个ReflectionData 缓存加载了该类的所有信息,即搜索诸如getSimpleName()之类的简单信息,所有元数据信息都将通过SoftReference加载和引用,这需要花费一些时间才能从内存中取出。
总之:反射方法在初始内存消耗和启动应用程序延迟都存在问题。这是因为在应用程序启动后就立即执行数据,分析和解析器处理。随着类数量的增加,内存和运行时消耗趋于增加。
解决这些问题的方法是,使框架在编译时而不是在运行时执行这些操作:
-
当应用程序启动时,元数据和系统将准备就绪。
-
无需调用反射类,包括ReflectionData,从而减少了启动时的内存消耗。
-
无需担心Type Erasure的影响。
避免反射的另一点是,我们可以更轻松地使用AoT,并通过GraalVM创建本机代码,这是一个令人兴奋的可能性,尤其是对于无服务器概念。该程序运行一次,然后将整个资源返回给操作系统。
演示代码
在解释了读数类型的概念后,下一步将是创建一个简单的工具,该工具将Java类从某些表示将要映射的实体的表示法转换为Map。将被转换的属性,以及将是唯一标识符的字段。让我们按照下面的代码所示执行所有操作:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity
String value() default "";
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column
String value() default "";
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Id
String value() default "";
为了简化与反射或其他选项的比较,将创建一个接口,该接口负责与Map的相互转换。
import java.util.Map;
public interface Mapper
<T> T toEntity(Map<String, Object> map, Class<T> type);
<T> Map<String, Object> toMap(T entity);
为了比较这两种解决方案,第一个实现将是通过反射实现的。一点是,有几种处理反射的策略,例如,结合使用带有Introspector的“ java.beans”包;但是,在此示例中,我们将以最简单的方式进行操作以展示其工作原理。
public class ReflectionMapper implements Mapper
@Override
public <T> T toEntity(Map<String, Object> map, Class<T> type)
Objects.requireNonNull(map, "Map is required");
Objects.requireNonNull(type, "type is required");
final Constructor<?>[] constructors = type.getConstructors();
try
final T instance = (T) constructors[0].newInstance();
for (Field field : type.getDeclaredFields())
write(map, instance, field);
return instance;
catch (InstantiationException | IllegalAccessException | InvocationTargetException exception)
throw new RuntimeException("An error to field the entity process", exception);
@Override
public <T> Map<String, Object> toMap(T entity)
Objects.requireNonNull(entity, "entity is required");
Map<String, Object> map = new HashMap<>();
final Class<?> type = entity.getClass();
final Entity annotation = Optional.ofNullable(
type.getAnnotation(Entity.class))
.orElseThrow(() -> new RuntimeException("The class must have Entity annotation"));
String name = annotation.value().isBlank() ? type.getSimpleName() : annotation.value();
map.put("entity", name);
for (Field field : type.getDeclaredFields())
try
read(entity, map, field);
catch (IllegalAccessException exception)
throw new RuntimeException("An error to field the map process", exception);
return map;
private <T> void read(T entity, Map<String, Object> map, Field field) throws IllegalAccessException
final Id id = field.getAnnotation(Id.class);
final Column column = field.getAnnotation(Column.class);
final String fieldName = field.getName();
if (id != null)
String idName = id.value().isBlank() ? fieldName : id.value();
field.setAccessible(true);
final Object value = field.get(entity);
map.put(idName, value);
else if (column != null)
String columnName = column.value().isBlank() ? fieldName : column.value();
field.setAccessible(true);
final Object value = field.get(entity);
map.put(columnName, value);
private <T> void write(Map<String, Object> map, T instance, Field field) throws IllegalAccessException
final Id id = field.getAnnotation(Id.class);
final Column column = field.getAnnotation(Column.class);
final String fieldName = field.getName();
if (id != null)
String idName = id.value().isBlank() ? fieldName : id.value();
field.setAccessible(true);
final Object value = map.get(idName);
if (value != null)
field.set(instance, value);
else if (column != null)
String columnName = column.value().isBlank() ? fieldName : column.value();
field.setAccessible(true);
final Object value = map.get(columnName);
if (value != null)
field.set(instance, value);
构建了映射器之后,下一步就是做一个小例子。因此,让我们创建一个Animal实体。
@Entity("animal")
public class Animal
@Id
private String id;
@Column("native_name")
private String name;
public Animal()
public Animal(String id, String name)
this.id = id;
this.name = name;
public String getId()
return id;
public void setId(String id)
this.id = id;
public String getName()
return name;
public void setName(String name)
this.name = name;
public class ReflectionMapperTest
private Mapper mapper;
@BeforeEach
public void setUp()
this.mapper = new ReflectionMapper();
@Test
public void shouldCreateMap()
Animal animal = new Animal("id", "lion");
final Map<String, Object> map = mapper.toMap(animal);
Assertions.assertEquals("animal", map.get("entity"));
Assertions.assertEquals("id", map.get("id"));
Assertions.assertEquals("lion", map.get("native_name"));
@Test
public void shouldCreateEntity()
Map<String, Object> map = new HashMap<>();
map.put("id", "id");
map.put("native_name", "lion");
final Animal animal = mapper.toEntity(map, Animal.class);
Assertions.assertEquals("id", animal.getId());
Assertions.assertEquals("lion", animal.getName());
这样,就演示了反射实现的实现。如果希望在其他项目中使用这种类型的工具,则可以创建一个小项目并像添加其他依赖项一样,而且这些操作和读取都将在运行时执行。
要注意的是,在反射中,有一些选项和策略可以使用它,例如,创建这些元数据的内部缓存,以避免不断使用ReflectionData或从此信息中避免在执行时编译类。
但是,最重要的是,整个过程将在执行时发生。为了使处理移至编译,我们将使用Java Annotation Processor API。
要在流程中成为实体的类需要扩展AbstractProcessor类,使用SupportedAnnotationTypes批注定义在编译时将读取哪些类,以及定义代码核心的流程方法。此方法将执行所有分析。最后一步是将该类注册为SPI,并且代码将在编译时准备运行。
@SupportedAnnotationTypes("org.soujava.medatadata.api.Entity")
public class EntityProcessor extends AbstractProcessor
//…
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv)
final List<String> entities = new ArrayList<>();
for (TypeElement annotation : annotations)
roundEnv.getElementsAnnotatedWith(annotation)
.stream().map(e -> new ClassAnalyzer(e, processingEnv))
.map(ClassAnalyzer::get)
.filter(IS_NOT_BLANK).forEach(entities::add);
try
if (!entities.isEmpty())
createClassMapping(entities);
createProcessorMap();
catch (IOException exception)
error(exception);
return false;
//…
重要的一点是,Java批注处理的配置比反射需要更多的配置步骤。但是,在开始的步骤中,后续步骤往往与反射API相似。可以通过pom.xml文件中的annotationProcessorPaths标记来完成对此库类型的依赖。一个很大的优点是这些依赖项仅在编译范围内可见。也就是说,可以添加依赖项来生成类,例如使用Mustache,而不必担心这些依赖项在运行时。
将依赖项添加到项目中并执行后,将在target/generated-sources文件夹内生成类。在该示例中,所有类的生成都归功于Mustache项目。
@Generated(value= "Soujava ClassMappings Generator", date = "2021-01-21T13:08:48.618494")
public final class ProcessorClassMappings implements ClassMappings
private final List<EntityMetadata> entities;
public ProcessorClassMappings()
this.entities = new ArrayList<>();
this.entities.add(new org.soujava.metadata.example.PersonEntityMetaData());
this.entities.add(new org.soujava.metadata.example.AnimalEntityMetaData());
this.entities.add(new org.soujava.metadata.example.CarEntityMetaData());
通常,此库的最终用户的功能不会有太大变化,因为用户将继续在实体中进行注释,但是,所有处理逻辑都已被带到了编译时。@Entity("animal")
public class Animal
@Id
private String id;
@Column("native_name")
private String name;
public Animal()
public Animal(String id, String name)
this.id = id;
this.name = name;
public String getId()
return id;
public void setId(String id)
this.id = id;
public String getName()
return name;
public void setName(String name)
this.name = name;
public class ProcessorMapperTest
private Mapper mapper;
@BeforeEach
public void setUp()
this.mapper = new ProcessorMapper();
@Test
public void shouldCreateMap()
Animal animal = new Animal("id", "lion");
final Map<String, Object> map = mapper.toMap(animal);
Assertions.assertEquals("animal", map.get("entity"));
Assertions.assertEquals("id", map.get("id"));
Assertions.assertEquals("lion", map.get("native_name"));
@Test
public void shouldCreateEntity()
Map<String, Object> map = new HashMap<>();
map.put("id", "id");
map.put("native_name", "lion");
final Animal animal = mapper.toEntity(map, Animal.class);
Assertions.assertEquals("id", animal.getId());
Assertions.assertEquals("lion", animal.getName());
以上,我们讨论了反射中的优点和缺点。用Java注释处理器介绍了一个示例,并展示了Java AOT的优势,并将其转换为本机。
每种选择都会产生不利条件。删除应用程序时,所有的JIT优化都会丢失,并且已存在的一种说法,随着技术的发展,JVM将比本机代码更加高效。性能的定义非常复杂,并且不仅仅考虑应用程序启动时间。
你需不需要一个充满技术氛围的学习交流群?扫码就行:
以上是关于探索Java无反射机制,如何节省启动内存资源消耗!的主要内容,如果未能解决你的问题,请参考以下文章