隨著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 {@Overridepublic <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);}}@Overridepublic <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 {@Idprivate 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;@BeforeEachpublic void setUp() {this.mapper = new ReflectionMapper();}@Testpublic 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"));}@Testpublic 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 {//…@Overridepublic 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 {@Idprivate 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;@BeforeEachpublic void setUp() {this.mapper = new ProcessorMapper();}@Testpublic 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"));}@Testpublic 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將比本機代碼更加高效,性能的定義非常復雜,并且不僅僅考慮應用程式啟動時間,
你需不需要一個充滿技術氛圍的學習交流群?掃碼就行:

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/275771.html
標籤:其他
