Spring底层之多例模式及依赖注入机制的实现

上篇文章<<浅析Spring注解实例化实现机制>>,我们介绍了Spring是如何通过注解来创建实例的,但是并没有通过Scope注解实现单/多例模式,也没有通过Autowired注解来实现依赖注入的问题,那今天我们就用代码实现Spring是如何解决这两个问题的。

1、思路分析

Spring底层核心机制实现

2、代码实现

2.1 自定义注解

ComponentScan

package com.zhl.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * TODO
 *
 * @Author zhl
 * @Date 2023/4/1 16:26
 * @Desc: 相当于扫描包的标签  <context:component-scan base-package="xxxx"/>
 *  这里的value需要我们传入要扫描的包路径
 **/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    String value() default "";
}

Component

package com.zhl.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * TODO
 *
 * @Author zhl
 * @Date 2023/4/1 16:58
 * @Desc:
 **/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}

Scope

package com.zhl.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * TODO
 *
 * @Author zhl
 * @Date 2023/4/1 17:12
 * @Desc: 指定bean的作用范围 singleton prototype
 **/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
    // 指定singleton 还是 prototype
    String value() default "";
}

Autowired

package com.zhl.spring.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * TODO
 *
 * @Author zhl
 * @Date 2023/4/1 18:02
 * @Desc:
 **/
@Target({ElementType.FIELD,ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
    public @interface Autowired {
    boolean required() default true;
}

2.2 Bean对象

MonsterDao


package com.zhl.spring.component;

import com.zhl.spring.annotation.Component;

/**
 * TODO
 * @Author zhl
 * @Date 2023/4/1 16:30
 * @Desc:
 **/
@Component
public class MonsterDao {
    public void sayHi(){
        System.out.println("hello~~,sir");
    }
}

MonsterService

/**
 * Author: zhl
 * Date:   2023/4/1 16:31
 * Desc:
 */

package com.zhl.spring.component;

import com.zhl.spring.annotation.Autowired;
import com.zhl.spring.annotation.Component;
import com.zhl.spring.annotation.Scope;

/**
 * TODO
 *
 * @Author zhl
 * @Date 2023/4/1 16:31
 * @Desc:
 **/

@Component(value = "service01")
@Scope(value = "prototype")
public class MonsterService {

    @Autowired
    private MonsterDao monsterDao;

    public void sayHello(){
        monsterDao.sayHi();
    }

}

2.3 创建容器类ZhlSpringApplicationContext

/**
 * Author: zhl
 * Date:   2023/4/1 16:33
 * Desc:
 */

package com.zhl.spring.ioc;

import com.zhl.spring.annotation.Autowired;
import com.zhl.spring.annotation.Component;
import com.zhl.spring.annotation.ComponentScan;
import com.zhl.spring.annotation.Scope;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;

/**
 * TODO
 *
 * @Author zhl
 * @Date 2023/4/1 16:33
 * @Desc: 这个类相当于IOC容器,传入配置类的class对象,获取到包路径然后进行容器的初始化
 **/
public class ZhlSpringApplicationContext {

   private Class configClass;

   private final ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

   private final ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();


   public void BeanDefinitionByScan(Class configClass){
      this.configClass = configClass;
      // 1. 获取注解的value值
      // 1.1 首先获取到注解对象
      ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
      String value = componentScan.value(); // value=com.zhl.spring.component
      // 2. 通过类加载器拿到资源路径
      ClassLoader classLoader = ZhlSpringApplicationContext.class.getClassLoader();
      URL resource = classLoader.getResource(value.replace(".", "/"));
      String resourcePath = resource.getPath(); // /C:/App/develop/spring-learning/spring5-zhl/target/classes/com/zhl/spring/component
      // 3. 通过资源路径拿到每个bean的绝对路径
      File file = new File(resourcePath);
      if (file.isDirectory()){
         File[] files = file.listFiles();
         for (File document : files) {
            String absolutePath = document.getAbsolutePath(); // C:\App\develop\spring-learning\spring5-zhl\target\classes\com\zhl\spring\component\MonsterDao.class
            // 4. 通过绝对路径拿到每个bean的类名
            String className = absolutePath.substring(absolutePath.lastIndexOf("\\") + 1, absolutePath.indexOf(".class"));
            // 5. 将包路径和类名进行拼接得到全类名,为下面反射处理准备
            String fullClassName = value + "." + className; // com.zhl.spring.component.MonsterDao
            try {
               // 6. 将包路径下所有的bean进行反射
               Class<?> aClass = classLoader.loadClass(fullClassName);
               // 7. 根据bean的class对象判断bean上是否有Component注解,
               // 在这里我们只举例component注解,其余Service Repository注解原理和Component一样
               if (aClass.isAnnotationPresent(Component.class)){
                  // 8. 如果有Component注解,我们要取出它的value值当作容器的key
                  Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
                  String beanName = componentAnnotation.value();
                  // 9. 如果Component注解没有传入值,那么将类名小写当做key
                  if ("".equals(beanName)){
                     beanName= StringUtils.uncapitalize(className);
                  }
                  BeanDefinition beanDefinition = new BeanDefinition();
                  beanDefinition.setaClass(aClass);
                  // 10. 判断bean上是否注有Scope注解
                  if (aClass.isAnnotationPresent(Scope.class)){
                     Scope scopeAnnotation = aClass.getDeclaredAnnotation(Scope.class);
                     String scopeValue = scopeAnnotation.value();
                     if ("".equals(scopeValue)){
                        scopeValue = "singleton";
                     }
                     if ("singleton".equals(scopeValue) || "prototype".equals(scopeValue)){
                        beanDefinition.setScope(scopeAnnotation.value());
                     }else {
                        new Throwable("注解Scope属性值错误~~");
                     }
                  }else { // 如果没有标注Scope注解,默认创建单例对象
                     beanDefinition.setScope("singleton");
                  }

                  // 11. 将bean信息放入beanDefinitionMap中
                  beanDefinitionMap.put(beanName,beanDefinition);
                  // 12. 如果Scope="singleton", 那么我们实例化对象,放入单例池中
                  if (beanDefinition.getScope().equals("singleton")){
                     singletonObjects.put(beanName,createBean(beanDefinition));
                  }
               }
            } catch (Exception e) {
               throw new RuntimeException(e);
            }
         }
      }
   }


   /**
    * 在容器的构造方法中要实现两个目标
    * 1、把标有注解的bean的信息放入beanDefinitionMap中
    * 2、如果是scope="singleton",那就是创建单例对象,并放入singletonObjects中,
    *   如果scope="prototype",在genBean时创建对象
    * @param configClass 传入ZhlSpringConfig 的class对象
    */

   public ZhlSpringApplicationContext(Class configClass) {
      // 1.容器初始化----把bean信息放入BeanDefinitionMap中
      BeanDefinitionByScan(configClass);

   }

   public Object createBean(BeanDefinition beanDefinition){
      // 1. 获取class对象
      Class aClass = beanDefinition.getaClass();
      Object instance = null;
      try {
         instance =  aClass.newInstance();
         // 2. 如果存在依赖注入,遍历所有字段,完成注入
         for (Field declaredField : aClass.getDeclaredFields()) {
            // 3. 判断当前字段是否标有Autowired注解
            if (declaredField.isAnnotationPresent(Autowired.class)){
               // 4. 获取字段名
               String beanName = declaredField.getName();
               // 5. 根据字段名获取bean对象
               Object bean = getBean(beanName);
               // 6. 完成组装
               declaredField.setAccessible(true);
               declaredField.set(instance,bean);
            }
         }
      } catch (InstantiationException e) {
         throw new RuntimeException(e);
      } catch (IllegalAccessException e) {
         throw new RuntimeException(e);
      }
      return instance;
   }
   public Object getBean(String key){
      if (beanDefinitionMap.containsKey(key)){
         BeanDefinition beanDefinition = beanDefinitionMap.get(key);
         if ("singleton".equals(beanDefinition.getScope())){
           return singletonObjects.get(key);
         }else {
            return createBean(beanDefinition);
         }
      }
      return null;
   }
}

BeanDefinition

/**
 * Author: zhl
 * Date:   2023/4/1 16:35
 * Desc:
 */

package com.zhl.spring.ioc;

/**
 * TODO
 *
 * @Author zhl
 * @Date 2023/4/1 16:35
 * @Desc: 用于封装记录bean的信息
 **/
public class BeanDefinition {

    private Class aClass;

    private String scope;

    public BeanDefinition() {
    }

    public BeanDefinition(Class aClass, String scope) {
        this.aClass = aClass;
        this.scope = scope;
    }

    public Class getaClass() {
        return aClass;
    }

    public void setaClass(Class aClass) {
        this.aClass = aClass;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }


    @Override
    public String toString() {
        return "BeanDefinition{" +
                "aClass=" + aClass +
                ", scope='" + scope + '\'' +
                '}';
    }
}

ZhlSpringConfig

/**
 * Author: zhl
 * Date:   2023/4/1 16:27
 * Desc:
 */

package com.zhl.spring.ioc;

import com.zhl.spring.annotation.ComponentScan;

/**
 * TODO
 *
 * @Author zhl
 * @Date 2023/4/1 16:27
 * @Desc: 这是一个配置类,作用相当于Spring的ApplicationContext.xml 容器配置文件
 **/
@ComponentScan(value = "com.zhl.spring.component")
public class ZhlSpringConfig {

}

2.4 测试类

/**
 * Author: zhl
 * Date:   2023/4/1 16:38
 * Desc:
 */

package com.zhl.spring;

import com.zhl.spring.component.MonsterDao;
import com.zhl.spring.component.MonsterService;
import com.zhl.spring.ioc.ZhlSpringApplicationContext;
import com.zhl.spring.ioc.ZhlSpringConfig;

/**
 * TODO
 *
 * @Author zhl
 * @Date 2023/4/1 16:38
 * @Desc: 测试类
 **/
public class AppMain {
    public static void main(String[] args) {
        ZhlSpringApplicationContext ioc = new ZhlSpringApplicationContext(ZhlSpringConfig.class);
        System.out.println("测试单例多例对象的创建");
        MonsterDao monsterDao1 = (MonsterDao) ioc.getBean("monsterDao");
        MonsterDao monsterDao2 = (MonsterDao) ioc.getBean("monsterDao");
        System.out.println(monsterDao1);
        System.out.println(monsterDao2);
        System.out.println("------------------------------------");
        MonsterService monsterService1 = (MonsterService) ioc.getBean("service01");
        MonsterService monsterService2 = (MonsterService) ioc.getBean("service01");
        System.out.println(monsterService1);
        System.out.println(monsterService2);

        System.out.println("测试依赖注入");
        MonsterService monsterService = (MonsterService) ioc.getBean("service01");
        monsterService.sayHello();
        
    }
}

测试结果:

测试单例多例对象的创建
com.zhl.spring.component.MonsterDao@49476842
com.zhl.spring.component.MonsterDao@49476842
------------------------------------
com.zhl.spring.component.MonsterService@2626b418
com.zhl.spring.component.MonsterService@5a07e868
测试依赖注入
hello~~,sir

Process finished with exit code 0

可以看到MonsterService类我们标注了@Scope(value = “prototype”)注解,返回的对象也是2个不同的对象,它是在每次getBean时,重新创建对象然后返回;MonsterDao因为Scope的值等于"singleton",所以返回的对象是一样的,因为它是在容器初始化的时候就进行了对象实例化,放在了singletonObjects中,genBean时只需要从singletonObjects获取返回即可。另外依赖注入也是测试成功的,这个主要是在创建对象的时候用到了反射,上面的代码中写的很详细哦~~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值