MST

星途 面试题库

面试题:设计并优化一个基于Java反射机制的自定义框架功能

假设你要设计一个自定义的依赖注入框架,要求使用Java反射机制来实现。阐述整体设计思路,包括如何处理循环依赖,怎样优化反射带来的性能损耗,并给出关键代码片段示例。
12.7万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

整体设计思路

  1. 定义依赖关系:使用注解或配置文件来标记需要注入的依赖。例如,可以定义一个@Inject注解,用于标记需要依赖注入的字段。
  2. 扫描组件:通过包扫描机制,利用反射获取所有需要进行依赖注入的类。遍历指定包下的所有类文件,加载并分析类的注解信息。
  3. 创建对象实例:使用反射创建类的实例。对于每个需要注入的类,通过Class.forName()获取类对象,然后使用newInstance()方法创建实例(Java 9 之后推荐使用Constructor.newInstance())。
  4. 处理依赖注入:递归处理依赖关系,当发现某个类的字段被@Inject注解标记时,先创建该依赖类的实例,然后通过反射设置该字段的值。
  5. 处理循环依赖
    • 使用三级缓存
      • 一级缓存(singletonObjects):用于存储完全初始化好的单例对象,即已经完成依赖注入的对象。
      • 二级缓存(earlySingletonObjects):用于存储早期暴露的单例对象,即已经创建但还未完成依赖注入的对象。
      • 三级缓存(singletonFactories):用于存储单例对象工厂,当一级和二级缓存都没有找到对象时,尝试从这里获取对象。
      • 在创建对象时,首先将对象工厂放入三级缓存,然后从三级缓存获取对象放入二级缓存,最后完成依赖注入后放入一级缓存。在注入依赖时,如果发现循环依赖,优先从二级缓存获取对象,避免死循环。
  6. 优化反射带来的性能损耗
    • 缓存反射信息:将反射获取的ConstructorField等信息缓存起来,避免每次创建对象或注入依赖时重复获取。可以使用ConcurrentHashMap来存储这些信息,例如Map<Class<?>, Constructor<?>> constructorCache
    • 使用字节码增强:在运行时通过字节码增强库(如ASM、Javassist)动态生成代码来代替反射操作。例如,使用Javassist生成获取和设置对象字段值的代码,性能比反射更好。

关键代码片段示例

  1. 定义@Inject注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Inject {
}
  1. 依赖注入框架核心代码
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class DependencyInjector {
    private static final Map<Class<?>, Object> singletonObjects = new HashMap<>();
    private static final Map<Class<?>, Object> earlySingletonObjects = new HashMap<>();
    private static final Map<Class<?>, Supplier<?>> singletonFactories = new HashMap<>();

    public static <T> T getInstance(Class<T> clazz) {
        if (singletonObjects.containsKey(clazz)) {
            return (T) singletonObjects.get(clazz);
        }
        if (earlySingletonObjects.containsKey(clazz)) {
            return (T) earlySingletonObjects.get(clazz);
        }
        Supplier<?> supplier = singletonFactories.get(clazz);
        if (supplier != null) {
            Object earlyInstance = supplier.get();
            earlySingletonObjects.put(clazz, earlyInstance);
            singletonFactories.remove(clazz);
            return (T) earlyInstance;
        }
        try {
            Constructor<T> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            T instance = constructor.newInstance();
            singletonFactories.put(clazz, () -> instance);
            earlySingletonObjects.put(clazz, instance);
            injectDependencies(instance);
            singletonObjects.put(clazz, instance);
            earlySingletonObjects.remove(clazz);
            return instance;
        } catch (Exception e) {
            throw new RuntimeException("Failed to create instance of " + clazz, e);
        }
    }

    private static void injectDependencies(Object instance) throws Exception {
        Class<?> clazz = instance.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Inject.class)) {
                field.setAccessible(true);
                Class<?> dependencyType = field.getType();
                Object dependency = getInstance(dependencyType);
                field.set(instance, dependency);
            }
        }
    }
}
  1. 使用示例
class ServiceB {
}

class ServiceA {
    @Inject
    private ServiceB serviceB;

    public ServiceA() {
    }

    public void doSomething() {
        System.out.println("ServiceA is using ServiceB: " + serviceB);
    }
}

public class Main {
    public static void main(String[] args) {
        ServiceA serviceA = DependencyInjector.getInstance(ServiceA.class);
        serviceA.doSomething();
    }
}