mini-ssm Lab2 - BeanDefinition

30 阅读3分钟

mini-ssm Lab2 - BeanDefinition

mini-ssm github

在 Lab1 中,我们通过 ClassUtil 实现了包扫描,获取了所有的 Class 对象。接下来,我们将根据这些 Class 对象生成 BeanDefinition 集合。

为什么需要 BeanDefinition

在 Spring 框架中,BeanDefinition 是用来描述 Bean 配置信息的类。我们需要 BeanDefinition 的原因有:

  1. 并非所有的 Class 都要被 Spring 管理。
  2. 可以通过 BeanDefinition 封装 Bean 的元数据信息,如 Class 对象、是否懒加载、作用域(scope)、Bean 名称等。

类定义

以下是 BeanDefinition 的定义:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class BeanDefinition {
    private String beanClassName;
    private Class<?> beanClass;
    private boolean lazyInit = false;
    private String factoryBeanName;
    private String scope = "SINGLETON";
}

BeanDefinition 中,beanClassName 保存类的全限定名,factoryBeanName 保存 Bean 的名称(默认首字母小写)。lazyInit 用于表示是否懒加载,scope 定义了 Bean 的作用域(默认为 SINGLETON)。

选择哪些 Bean 需要被管理

通常,在 Spring 中被 @Component 注解标记的类需要被管理,如 @Controller@Service@Configuration 这些注解类,它们都标记了 @Component。为此,我们定义了一个 BeanLoader 接口,用于确定哪些类需要加载:

public interface BeanLoader {
    boolean isLoad(Class<?> clz);
    default BeanDefinition loadBean(Class<?> clz){
        return BeanDefinition.builder()
                .beanClassName(clz.getName())
                .beanClass(clz)
                .factoryBeanName(StringUtil.toLowerFirstCase(clz.getSimpleName()))
                .build();
    }
}

ComponentBeanLoader 实现

为了标识需要加载的 @Component Bean,我们实现了 ComponentBeanLoader

public class ComponentBeanLoader implements BeanLoader {
    @Override
    public boolean isLoad(Class<?> clz) {
        for (Annotation annotation : clz.getAnnotations()) {
            Class<? extends Annotation> annotationType = annotation.annotationType();
            if (annotationType.isAnnotationPresent(Component.class) || annotation instanceof Component) {
                return true;
            }
        }
        return false;
    }

    @Override
    public BeanDefinition loadBean(Class<?> clz) {
        BeanDefinition beanDefinition = BeanLoader.super.loadBean(clz);
        Component component = AnnotationUtil.getMergedAnnotation(clz, Component.class);
        if (component == null || StringUtil.isEmpty(component.value())) {
            return beanDefinition;
        }
        beanDefinition.setFactoryBeanName(component.value());
        return beanDefinition;
    }
}

通过实现 BeanLoader,我们可以控制哪些类需要被加载,并将符合条件的类转换为 BeanDefinition

使用 SPI 实现解耦

在 Spring 中,可以使用服务提供者接口 (SPI) 加载自定义的 BeanLoader,实现解耦和自定义扩展。

BeanDefinitionReader

BeanDefinitionReader 负责加载 BeanLoader,并将 Class 集合转换为 BeanDefinition 集合。

public class BeanDefinitionReader {
    @Getter
    private final Properties config = new Properties();
    private final List<BeanLoader> beanLoaders = new ArrayList<>();
    @Getter
    private final Set<Class<?>> registerBeanClasses;

    public BeanDefinitionReader(String scanPackage) {
        registerBeanClasses = ClassUtil.getClassSet(scanPackage);
        ServiceLoader<BeanLoader> loaders = ServiceLoader.load(BeanLoader.class);
        for (BeanLoader loader : loaders) {
            beanLoaders.add(loader);
        }
    }

    public List<BeanDefinition> loadBeanDefinitions() {
        List<BeanDefinition> result = new ArrayList<>();
        for (Class<?> beanClass : registerBeanClasses) {
            for (BeanLoader beanLoader : beanLoaders) {
                if (beanLoader.isLoad(beanClass)) {
                    BeanDefinition beanDefinition = beanLoader.loadBean(beanClass);
                    result.add(beanDefinition);
                }
            }
        }
        return result;
    }

    public void addBeanLoader(BeanLoader beanLoader) {
        beanLoaders.add(beanLoader);
    }
}

BeanDefinitionReader 会扫描配置文件中指定的包路径,利用 BeanLoader 加载相应的 BeanDefinition。通过 ServiceLoader 机制,我们可以动态加载 BeanLoader,实现了更灵活的扩展。

AliasFor 注解处理

ComponentBeanLoader 中,我们使用 AnnotationUtil.getMergedAnnotation() 处理注解。这是因为直接使用 clz.getAnnotation(Component.class) 无法获取 @Service 等注解标记的 @Component

以下是 AnnotationUtil 实现:

public abstract class AnnotationUtil {
    @SuppressWarnings("unchecked")
    public static <A extends Annotation> A getMergedAnnotation(Class<?> element, Class<A> annotationType) {
        Annotation annotation = findAnnotation(element, annotationType);
        if (annotation == null || annotation.annotationType() == annotationType) {
            return (A) annotation;
        }
        return mergeAttributes(annotation, annotationType);
    }

    private static <A extends Annotation> Annotation findAnnotation(Class<?> element, Class<A> annotationType) {
        A annotation = element.getAnnotation(annotationType);
        if (annotation != null) {
            return annotation;
        }
        for (Annotation declaredAnnotation : element.getAnnotations()) {
            Annotation metaAnnotation = findAnnotation(declaredAnnotation.annotationType(), annotationType);
            if (metaAnnotation != null) {
                return declaredAnnotation;
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private static <A extends Annotation> A mergeAttributes(Annotation annotation, Class<A> annotationType) {
        Map<String, Object> attributes = new HashMap<>();
        for (Method method : annotation.getClass().getInterfaces()[0].getDeclaredMethods()) {
            try {
                AliasFor aliasFor = method.getAnnotation(AliasFor.class);
                if (aliasFor != null) {
                    Object value = method.invoke(annotation);
                    String aliasName = "".equals(aliasFor.value()) ? method.getName() : aliasFor.value();
                    attributes.put(aliasName, value);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (attributes.isEmpty()) {
            return null;
        }
        return (A) Proxy.newProxyInstance(
                annotationType.getClassLoader(),
                new Class<?>[]{annotationType},
                (proxy, method, args) -> attributes.get(method.getName())
        );
    }
}

注解的原理

注解是特殊接口,继承 Annotation,实例是由 JDK 动态代理生成的。AnnotationUtil 使用 getMergedAnnotation 查找注解及其元注解,并合并其属性。