IOC容器之对Bean的操作

2023-09-22 源码探究Spring

# 定义四个注解

四个注解名称不同,其他的包括包名都相同

package org.simpleframework.core.annotation;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component/Controller/Repository/Service {
}

# 创建Bean工作类实现Bean的加载

  1. 构造方法借助枚举实现单例,这样相比传统的饿汉式和懒汉式可以直接防止反射/序列化攻击,核心原理的枚举类不支持无参构造,反射拿不到构造出来的对象,攻击也就无从谈起。
  2. loadBeans:用于加载指定包下的所有被注解修饰的类,将其加入bean的map集合
  3. isLoaded():由于loadBeans()方法可能会被多次调用,为防止重复调用设置一个变量,如果开始加载则标记已进行过bean的初始化。
package org.simpleframework.core;

@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanContainer {


    // 存放所有bean
    private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();

    // 加载bean的注解列表
    private static final List<Class<? extends Annotation>> BEAN_ANNOTATION = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);


    /**
     * 加载Bean,synchronized防止被多线程同时执行
     * @param packageName 包名
     */
    public synchronized void loadBeans(String packageName) {
        if (isLoaded()) {
            log.warn("Bean容器已被加载,请勿重复加载");
            return;
        }

        Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
        if (ValidationUtil.isEmpty(classSet)) {
            log.warn("该包下没有加载到任何类:{}", packageName);
            return;
        }
        for (Class<?> clazz : classSet) {
            for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
                // 如果类上有该注解,则加入容器beanMap集合
                if (clazz.isAnnotationPresent(annotation)) {
                    beanMap.put(clazz, ClassUtil.newInstance(clazz, true));
                }
            }
        }
        loaded = true;
    }


    /**
     * 容器是否已经加载过bean
     */
    private boolean loaded = false;

    /**
     * 是否已加载过Bean
     * @return 是否已加载
     */
    public boolean isLoaded() {
        return loaded;
    }


    private enum ContainerHolder {
        HOLDER;
        private final BeanContainer instance;

        ContainerHolder() {
            instance = new BeanContainer();
        }
    }

    public static BeanContainer getInstance() {
        return ContainerHolder.HOLDER.instance;
    }


    /**
     * 获取bean的数量
     */
    public int size() {
        return beanMap.size();
    }
}

# 测试

自行创建测试类,给类加上注解,输出加上注解类的数量,数量一致代表测试成功;

public class BeanContainerTest {

    @Test
    public void loadBeansTest() {
        BeanContainer.getInstance().loadBeans("com.xk857");
        System.out.println(BeanContainer.getInstance().size());
        Assertions.assertEquals(6, BeanContainer.getInstance().size());
    }
}

# 实现对容器的操作

上文实现了对容器的初始化加载,加下来将完善BeanContainer类实现对容器的操作,即增加和删除等操作,这里重点介绍两个方法:

  1. getClassesByAnnotation( annotation ):查找集合内使用例如@Controller修饰的类
  2. getClassesBySuper( Class ):通过接口或者父类获取实现类或者子类的Class集合,不包括其本身
    • class1.isAssignableFrom(class2):判断class1是否是class2的父类或父接口
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanContainer {

    // 存放所有bean
    private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();

    /**
     * 添加一个class对象及其Bean实例
     * @param clazz Class对象
     * @param bean  Bean实例
     * @return 原有的Bean实例, 没有则返回null
     */
    public Object addBean(Class<?> clazz, Object bean) {
        return beanMap.put(clazz, bean);
    }
    
  /**
     * 移除一个IOC容器管理的对象
     * @param clazz Class对象
     * @return 删除的Bean实例, 没有则返回null
     */
    public Object removeBean(Class<?> clazz) {
        return beanMap.remove(clazz);
    }

   /**
     * 根据Class对象获取Bean实例
     * @param clazz Class对象
     * @return Bean实例
     */
    public Object getBean(Class<?> clazz) {
        return beanMap.get(clazz);
    }
    
   /**
     * 获取容器管理的所有Class对象的集合,即返回bean的所有key
     */
    public Set<Class<?>> getClasses(){
        return beanMap.keySet();
    }
    
   /**
     * 获取所有Bean集合,即获取所有bean的value
     */
    public Set<Object> getBeans(){
        return new HashSet<>(beanMap.values());
    }
    
   /**
     * 筛选出指定注解Bean的Class集合
     * @param annotation 注解
     * @return Class集合
     */
    public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation) {
        // 1.获取beanMap的所有class对象
        Set<Class<?>> keySet = getClasses();
        if (ValidationUtil.isEmpty(keySet)) {
            log.warn("bean为空");
            return null;
        }

        // 2.通过注解筛选被注解标记的class对象,并添加到classSet里
        Set<Class<?>> classSet = new HashSet<>();
        for (Class<?> clazz : keySet) {
            // 类是否有相关的注解标记
            if (clazz.isAnnotationPresent(annotation)) {
                classSet.add(clazz);
            }
        }
        return classSet.isEmpty() ? null : classSet;
    }
    
   /**
     * 通过接口或者父类获取实现类或者子类的Class集合,不包括其本身
     * @param interfaceOrClass 接口Class或者父类Class
     * @return Class集合
     */
    public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass) {
        // 1.获取beanMap的所有class对象
        Set<Class<?>> keySet = getClasses();
        if (ValidationUtil.isEmpty(keySet)) {
            log.warn("bean为空");
            return null;
        }

        // 2.判断keySet里的元素是否是传入的接口或者类的子类,如果是,就将其添加到classSet里
        Set<Class<?>> classSet = new HashSet<>();
        for (Class<?> clazz : keySet) {
            // 判断keySet里的元素是否是传入的接口或者类的子类
            if (interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)) {
                classSet.add(clazz);
            }
        }
        return classSet.isEmpty() ? null : classSet;
    }
}
点击展开完整测试代码
public class BeanContainerTest {

    @Test
    public void loadBeansTest() {
        BeanContainer.getInstance().loadBeans("com.xk857");
        System.out.println(BeanContainer.getInstance().size());
        Assertions.assertEquals(6, BeanContainer.getInstance().size());
    }

    @Test
    @DisplayName("根据类获取实例")
    public void getBeanTest() {
        BeanContainer.getInstance().loadBeans("com.xk857");
        Assertions.assertNotNull(BeanContainer.getInstance().getBean(MainPageController.class));
        Assertions.assertNull(BeanContainer.getInstance().getBean(HeadLine.class));
    }

    @Test
    @DisplayName("根据注解获取对应实例")
    public void getClassesByAnnotationTest() {
        BeanContainer.getInstance().loadBeans("com.xk857");
        Set<Class<?>> classesBySuper = BeanContainer.getInstance().getClassesByAnnotation(Controller.class);
        System.out.println(classesBySuper.size());
    }
    
    @Test
    @DisplayName("根据接口获取实现类")
    public void getClassesBySuperTest() {
        BeanContainer.getInstance().loadBeans("com.xk857");
        Set<Class<?>> classesBySuper = BeanContainer.getInstance().getClassesBySuper(HeadLineService.class);
        for (Class<?> aClass : classesBySuper) {
            System.out.println(aClass.getName());
        }
    }
}
上次更新: 5 个月前