首页 > 其他 > 详细

自定义IoC容器

时间:2020-07-31 17:18:49      阅读:88      评论:0      收藏:0      [点我收藏+]

自定义IoC容器

  • 创建注解
  • 提取标记对象
  • 实现容器
  • 依赖注入

下面分步实现

一、创建四个常用的注解@Controller、@Service、@Component、@Repository

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}

二、提取被注解标记的类

(1). 指定范围,获取范围内的所有类

  1. 获取类加载器

     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    
  2. 通过类加载器获取到加载的资源信息

     //通过包名获取资源的地址
     URL url = classLoader.getResource(packageName.replace(".", "/"));
     //这里查看URL的getResource源码可以发现,URL是通过‘/‘来进行分割的,所以要将包名中的‘.‘替换成‘/‘
    
     /**
      * Finds the resource with the given name.  A resource is some data
      * (images, audio, text, etc) that can be accessed by class code in a way
      * that is independent of the location of the code.
      *
      * <p> The name of a resource is a ‘<tt>/</tt>‘-separated path name that
      * identifies the resource.
      *
      * <p> This method will first search the parent class loader for the
      * resource; if the parent is <tt>null</tt> the path of the class loader
      * built-in to the virtual machine is searched.  That failing, this method
      * will invoke {@link #findResource(String)} to find the resource.  </p>
      *
      * @apiNote When overriding this method it is recommended that an
      * implementation ensures that any delegation is consistent with the {@link
      * #getResources(java.lang.String) getResources(String)} method.
      *
      * @param  name
      *         The resource name
      *
      * @return  A <tt>URL</tt> object for reading the resource, or
      *          <tt>null</tt> if the resource could not be found or the invoker
      *          doesn‘t have adequate  privileges to get the resource.
      *
      * @since  1.1
      */
     public URL getResource(String name) {
         URL url;
         if (parent != null) {
             url = parent.getResource(name);
         } else {
             url = getBootstrapResource(name);
         }
         if (url == null) {
             url = findResource(name);
         }
         return url;
     }
    
  3. 依据不同的资源类型,采用不同的方式获取资源的集合

     //首先过滤出文件类型的资源
     //然后通过extractClassFile函数递归提取所需的所有文件
     //这里只file类型的资源
     if(url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){
         classSet = new HashSet<>();
         File packageDirectory = new File(url.getPath());
         if(!packageDirectory.isDirectory()){
             return null;
         }
         extractClassFile(classSet, packageDirectory, packageName);
     }
     
    
     /**
      * 递归获取目标package下面的所有class文件
      * @param emptyClassSet 装载目标类的集合
      * @param fileSource 文件或者目录
      * @param packageName 包名
      */
     private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
     	//如果不是文件夹,直接返回
         if(!fileSource.isDirectory()){
             return;
         }
         //如果是文件夹,则列出所有文件和子文件夹,获取到的是文件夹类型的File数组
         File[] files = fileSource.listFiles(new FileFilter() {
             @Override
             public boolean accept(File file) {
     			//如果是文件夹,则被过滤提取出来
                 if(file.isDirectory()){
                     return true;
                 }else {
                     //获取文件的绝对值路径
                     String absoluteFilePath = file.getAbsolutePath();
                     if(absoluteFilePath.endsWith(".class")){
                         //class文件直接加载
                         addToClassSet(absoluteFilePath);
                     }
                     return false;
                 }
             }
    
             private void addToClassSet(String absoluteFilePath) {
                 //1.从class文件的绝对值路径中提取包含了package的类名
     			//将现在的‘/‘或‘\‘替换为包名中的‘.‘
                 absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
                 String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
                 className = className.substring(0, className.lastIndexOf("."));
                 //2.通过反射获取Class对象,并加入classSet里
                 Class<?> targetClass = loadClass(className);
                 emptyClassSet.add(targetClass);
             }
         });
    
         if(files != null){
             //递归调用
             for (File file : files) {
                 extractClassFile(emptyClassSet, file, packageName);
             }
         }
     }
    
     //loadClass通过反射获取Class对象
     /**
      * 获取class对象
      * @param className 类名
      * @return class对象
      */
     public static Class<?> loadClass(String className){
         try {
             return Class.forName(className);
         } catch (ClassNotFoundException e) {
             log.error("load class error ", e);
             throw new RuntimeException(e);
         }
     }
    

三、实现统一容器管理

  1. 首先建立容器

容器的组成部分:

  • 保存Class对象及其实例
  • 容器的加载
  • 容器的对Class及实例的操作

第一步:需要用同一个容器实例将所有目标管理起来,所以容器需要写成单例模式,这里采用装备内部枚举类的饿汉模式来抵御序列化和反射的攻击:

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

    /**
     * 获取bean容器实例
     * @return 单例BeanContainer
     */
    public static BeanContainer getInstance(){
        return ContainHolder.HOLDER.instance;
    }

    private enum ContainHolder{
        HOLDER;

        private final BeanContainer instance;
        ContainHolder(){
            instance = new BeanContainer();
        }

    }
}

//使用@NoArgsConstructor(access = AccessLevel.PRIVATE)添加私有的构造函数

首先在容器中定义两个变量,其中beanMap用来存放被标记的目标对象的Map,BEAN_ANNOTATION是被加载的注解的列表,例子中只写了四个

private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION =
            Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);

第二步:开始扫描加载bean,其中synchronized防止多线程访问冲突,:

/**
 * 扫描加载所有Bean
 * @param packageName 包名
 */
public synchronized void loadBeans(String packageName){
    //判断bean容器是否被加载过
    if(loaded){
        log.warn("BeanContainer has been loaded.");
        return;
    }
	//通过上面讲解的extractPackageClass方法获取所有类对象
    Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
	//进行判空
    if(ValidationUtil.isEmpty(classSet)){
        log.warn("extract nothing from packageName" + packageName);
        return;
    }
    for (Class<?> clazz : classSet) {
        for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
            //如果类上面标记了定义的注解
            if(clazz.isAnnotationPresent(annotation)){
                //将目标类本身作为键,实例作为值,放入到beanMap中
                beanMap.put(clazz, ClassUtil.newInstance(clazz, true));
                break;
            }
        }
    }
    loaded = true;
}

//beanMap.put(clazz, ClassUtil.newInstance(clazz, true));
//这里有一个newInstance方法利用反射创建实例
/**
 * 实例化Class
 * @param clazz Class
 * @param accessible 是否支持创建出私有clazz对象的实例
 * @param <T> clazz的类型
 * @return 类的实例
 */
public static <T> T newInstance(Class<T> clazz, boolean accessible){
    try {
        Constructor<T> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(accessible);
        return constructor.newInstance();
    } catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
        log.error("newInstance error", e);
        throw new RuntimeException(e);
    }
}

第三步:实现容器的操作方式

  • 增加、删除操作

      /**
       * 添加一个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获取对应实例

      /**
       * 根据Class对象获取Bean实例
       * @param clazz Class对象
       * @return 相应Bean实例
       */
      public Object getBean(Class<?> clazz){
          return beanMap.get(clazz);
      }
    
  • 获取所有的Class及其实例

      /**
       * 获取容器管理的所有对象的集合
       * @return Class集合
       */
      public Set<Class<?>> getClasses(){
          return beanMap.keySet();
      }
    
      /**
       * 获取所有Bean集合
       * @return Bean集合
       */
      public Set<Object> getBeans(){
          return new HashSet<>(beanMap.values());
      }
    
  • 通过注解获取被标注的Class

      /**
       * 根据注解筛选出Bean的Class集合
       * @param annotation 注解
       * @return Class集合
       */
      public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){
          //1.获取beanMap的所有对象
          Set<Class<?>> keySet = getClasses();
          if(ValidationUtil.isEmpty(keySet)){
              log.warn("nothing in beanMap");
              return null;
          }
          //2.通过注解筛选出被注解标记的对象,并添加到classSet里
          Set<Class<?>> classSet = new HashSet<>();
          for (Class<?> clazz : keySet) {
              //类是否被相关注解标记
              if(clazz.isAnnotationPresent(annotation)){
                  classSet.add(clazz);
              }
          }
          return classSet.size() > 0?classSet:null;
      }
    
  • 通过超类获取对应子类的Class

      /**
       * 通过接口或者父类获取实现类或子类的Class集合,不包括其本身
       * @param interfaceOrClass 接口或父类
       * @return Class集合
       */
      public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){
          //1.获取beanMap的所有对象
          Set<Class<?>> keySet = getClasses();
          if(ValidationUtil.isEmpty(keySet)){
              log.warn("nothing in beanMap");
              return null;
          }
          //2.判断keySet里面的元素是否是传入的接口或者类的子类,并添加到classSet里
          Set<Class<?>> classSet = new HashSet<>();
          for (Class<?> clazz : keySet) {
              if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){
                  classSet.add(clazz);
              }
          }
          return classSet.size() > 0?classSet:null;
      }
    
  • 通过容器载体保存Class数量

      /**
       * Bean实例数量
       * @return 数量
       */
      public int size(){
          return beanMap.size();
      }
    

这样一来,我们的容器已经实现完毕,下面进行测试:

//定义一个测试接口
public interface DemoService {
	String getName();
}

//接口的实现类
@Service
public class DemoServiceImpl implements DemoService {
    @Override
    public String getName() {
        return "DemoServiceImpl";
    }
}

//以及调用接口的Controller
@Controller
public class ControllerDemo {

    private DemoService demoService;

    String getServiceName(){
        return demoService.getName();
    }
}

//编写测试类进行调试
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BeanContainerTest {

    private static BeanContainer beanContainer;

    @BeforeAll
    static void init(){
        beanContainer = BeanContainer.getInstance();
    }

    @DisplayName("根据类获取其实例:getBeanTest")
    @Order(1)
    @Test
    public void getBeanTest(){
        beanContainer.loadBeans("com.joker");
        ControllerDemo controller = (ControllerDemo) beanContainer.getBean(ControllerDemo.class);
        Assertions.assertNotNull(controller);
    }
}

如下图发现ControllerDemo已经成功从容器中获取到了,但是其内部的DemoService却是null,那是因为我们只实现了容器的管理,还没实现依赖的注入

技术分享图片技术分享图片

四、实现依赖注入

  • 定义相关的注解标签

这里以@Autowired注解示例

/**
 * Autowired目前仅支持成员变量的注入
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {

    String value() default "";
}
  • 实现创建被注解标记的成员变量实例,并将其注入到成员变量里

      public void doIoc(){
    
          if(ValidationUtil.isEmpty(beanContainer.getClasses())){
              log.warn("empty classSet in BeanContainer.");
              return;
          }
          //1.遍历Bean容器中所有的Class对象
          for (Class<?> clazz : beanContainer.getClasses()) {
              //2.遍历Class对象的所有成员变量
              Field[] fields = clazz.getDeclaredFields();
              if(ValidationUtil.isEmpty(fields)){
                  continue;
              }
              for (Field field : fields) {
                  //3.找出被Autowired标记的成员变量
                  if(field.isAnnotationPresent(Autowired.class)){
                      Autowired autowired = field.getAnnotation(Autowired.class);
                      String autowiredValue = autowired.value();
                      //4.获取这些成员变量的类型
                      Class<?> fieldClass = field.getType();
                      //5.获取这些成员变量的类型在容器里对应的实例
                      Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
                      if(fieldValue == null){
                          throw new RuntimeException("unable to inject relevant type, target field is:" + fieldClass.getName());
                      }
                      //6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
                      Object targetObject = beanContainer.getBean(clazz);
                      ClassUtil.setField(field, targetObject, fieldValue, true);
                  }
              }
          }
      }
    
      //5.获取这些成员变量的类型在容器里对应的实例
      /**
       * 根据Class对象在beanContainer里获取其实例或者实现类
       * @param fieldClass Class对象
       * @param autowiredValue 注解中的值
       * @return 实例或实现类
       */
      private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
          Object fieldValue = beanContainer.getBean(fieldClass);
          if(fieldValue != null){
              return fieldValue;
          }
          Class<?> implementedClass = getImplementClass(fieldClass, autowiredValue);
          if (implementedClass != null){
              return beanContainer.getBean(implementedClass);
          }
          return null;
      }
    
      /**
       * 获取接口的实现类
       * @param fieldClass 接口
       * @param autowiredValue 注解中的值
       * @return 实现类
       */
      private Class<?> getImplementClass(Class<?> fieldClass, String autowiredValue) {
          Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
          if(ValidationUtil.isEmpty(classSet)){
              return null;
          }
          if(ValidationUtil.isEmpty(autowiredValue)){
              if(classSet.size() == 1){
                  return classSet.iterator().next();
              }
              //如果多于两个实现类且未被指定,抛出异常
              throw new RuntimeException("multiple implement classes for " + fieldClass.getName() + "please set @Autowired‘s value to pick one");
          }
          for (Class<?> clazz : classSet) {
              if(autowiredValue.equals(clazz.getSimpleName())){
                  return clazz;
              }
          }
          //没有被容器管理
          return null;
      }
    
      //6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
      ClassUtil.setField(field, targetObject, fieldValue, true);
      //写在了ClassUtil类中
      /**
       * 设置类的属性值
       * @param field 成员变量
       * @param target 类实例
       * @param value 成员变量的值
       * @param accessible 是否允许设置私有属性
       */
      public static void setField(Field field, Object target, Object value, boolean accessible){
          field.setAccessible(accessible);
          try {
              field.set(target, value);
          } catch (IllegalAccessException e) {
              log.error("set field error", e);
              throw new RuntimeException(e);
          }
      }
    
  • 依赖注入的使用

上述依赖注入程序编写完毕,只需在ControllerDemo的Service变量上面加一个注解,即使用@Autowired:

@Controller
public class ControllerDemo {

    @Autowired
    private DemoService demoService;

    public String getServiceName(){
        return demoService.getName();
    }
}

然后编写测试类:

public class DependencyInjectorTest {

    @DisplayName("依赖注入:doIoc")
    @Test
    public void doIocTest(){
        BeanContainer beanContainer = BeanContainer.getInstance();
        beanContainer.loadBeans("com.joker");
        Assertions.assertTrue(beanContainer.isLoaded());
        ControllerDemo controllerDemo = (ControllerDemo) beanContainer.getBean(ControllerDemo.class);
        Assertions.assertNotNull(controllerDemo);
        new DependencyInjector().doIoc();
        Assertions.assertNotNull(controllerDemo.getServiceName());
    }
}

调试运行结果如图:

技术分享图片

结果无误,此篇结。

自定义IoC容器

原文:https://www.cnblogs.com/zqm-sau/p/13410459.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!