【总结】自定义SpringIOC

一、手写SpringIOC容器简介

已经实现的功能有

  1. 提供注解@Service、@Component、@Controller、@Repositor,标记需要加载到IOC容器管理的类
  2. 提供了一个ClassUtil工具类,支持加载指定包下及其子包所有.class文件,支持注入属性,支持通过反射class加载实例化对象(仅支持默认构造
  3. 提供IOC容器BeanContainer,支持通过ClassUtil工具类,加载被注解标记的类,目前仅支持单例模式以及非懒加载的方式
  4. 提供IOC容器增产改的方法,获取IOC的Bean的方式有:通过class直接加载;获取某一个标记的注解所有class集合;获取全部的class集合;获取父类或者接口的实现类结合Class
  5. 提供依赖注入的注解@Autowired,仅支持注入到属性上,该注解提供一个value,用户可以指定获取具体的接口实现类的名称
  6. 提供完成所有标记在IOC容器中对象属性依赖注入的类DependencyInjector,将扫描@Autowired,根据用户定义的注解value或者默认值注入到属性中

使用BeanContainer的loadBeans方法,指定框架需要管理的包路径,将相关类加入到IOC容器中,再利用DependencyInjector的doIoc方法实现属性的依赖注入。

实现的思路为

  1. 创建注解
  2. 提取标记对象
  3. 实现容器
  4. 依赖注入

提取标记对象

  1. 指定范围,获取范围内全部的类
  2. 遍历所有类,获取被注解标记的类并加载到容器里

二、创建注解

//设置是作用在类上面的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
//设置是作用在类上面的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
//设置是作用在类上面的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
//设置是作用在类上面的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}

三、提取标记对象

1. 指定范围加载

  • 首先需要获取到一个类加载器,通过类加载器加载用户指定包的资源,类加载器详解,查看这个文章:类加载器总结

  • 获取类加载的方式,可以通过当前的线程获取

/**
 * 获取类加载器
 *
 * @return 当前类加载器
 */
public static ClassLoader getClassLoader() {
    return Thread.currentThread().getContextClassLoader();
}
  • 由于用户传递过来的只是个包名称,而对于不同的机器,项目所在的位置不同,所以就需要通过类加载器获取到资源的具体路径
URL url = classLoader.getResource(packageName.replace(".", "/"));
  • getResource()接收的是一个以反斜杠分割的路径,而一般用户提供的都是以“.”分割的,所以我们需要替换成反斜杠
  • classLoader.getResource()方法返回的是一个URL,可以获取到协议,域名,端口号,路径,请求参数,如:https[协议]😕/www.xzzz2020.cn[域名]:80[端口号]/public/simplespring[路径]?id=xxx[请求参数]
  • 而我们需要关注的是这样的URL:file:///F:/study/Spring,该资源可以定位本机的唯一资源,所以需要通过URL类中两个方法getProtocol()和getPath()获取定位包的文件协议和绝对路径
  • 接着主要是递归调用目录所有.class文件,利用反射Class.forName方法创建实例,并加入到集合中

完整代码如下所示:

/**
 * 类加载工具类
 * @author xzzz2020
 * @version 1.0
 * @date 2020/7/31 13:20
 */

@Slf4j
public final class ClassUtil {

    private static final String FILE_PROTOCLO = "file";

    private ClassUtil() {
    }

    /**
     * 获取包下类的集合
     *
     * @param packageName 包名
     * @return 获取类的集合
     */
    public static Set<Class<?>> extractPackageClass(String packageName) {
        //1. 获取类的加载器,为了获取项目发布的实际路径
        ClassLoader classLoader = getClassLoader();
        //2. 通过类加载器获取到加载的资源信息
        URL url = classLoader.getResource(packageName.replace(".", "/"));
        if (url == null) {
            log.warn("unable to retrieve anything from package: " + packageName);
            return null;
        } else {
            log.info("load path is "+url.getProtocol()+"://"+url.getPath());
        }
        //3. 根据不同的资源类型,采用不同的方式获取资源的集合
        Set<Class<?>> classSet = null;
        //过滤出文件类型的资源
        if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCLO)){
            classSet = new HashSet<>();
            File packageDirectory = new File(url.getPath());//获取文件的实际路径
            //递归遍历该目录下所有的class文件
            extractClassFile(classSet,packageDirectory,packageName);
        }
        return classSet;
    }

    /**
     * 递归遍历该目录下所有的class文件
     * @param emptyClassSet 装载目标类的集合
     * @param fileSource 文件或目录
     * @param packageName 加载的包路径
     */
    private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
        if (!fileSource.isDirectory()){

        }else {
            //列出当前目录的所有文件和文件夹
            File[] files = fileSource.listFiles(new FileFilter() {
                /**
                 * 该方法是过滤当前目录下的所有文件和目录
                 * @param file 文件目录
                 * @return 如果是目录,返回true,如果是.class文件,加载到容器中,其他的返回false
                 */
                @Override
                public boolean accept(File file) {
                    if (file.isDirectory()){
                        return true;
                    }else {
                        //获取文件的绝对值路径
                        String absoluteFilePath = file.getAbsolutePath();
                        if (absoluteFilePath.endsWith(".class")){
                            //如果是class文件,直接加载
                            addToClassSet(absoluteFilePath);
                        }
                        //TODO 此处可以加入针对其他类型资源的处理
                    }
                    return false;
                }

                /**
                 * 根据class文件的绝对值路径,获取并生成class对象,并放入classSet中
                 * @param absoluteFilePath 文件的绝对值路径
                 */
                private void addToClassSet(String absoluteFilePath) {
                    //1.从class文件的绝对值路径里提取出包含了package的类名
                    //如/Users/baidu/imooc/springframework/sampleframework/target/classes/cn/xzzz2020/entity/dto/MainPageInfoDTO.class
                    //需要弄成cn.xzzz2020.entity.dto.MainPageInfoDTO
                    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){//如果file的数组为空,使用foreach,还是会抛出空指针异常
                for (File file : files) {
                    extractClassFile(emptyClassSet,file,packageName);
                }
            }

        }
    }

    /**
     * 获取类加载器
     *
     * @return 当前类加载器
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 获取class对象
     * @param className class全限定类名
     * @return
     */
    public static Class<?> loadClass(String className){
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            log.error("Load class error " + e);
            throw new RuntimeException();
        }
    }
    
    
    /**
     * 实例化类对象
     * @param clazz 需要实例化的class
     * @param accessible 是否允许使用私有的构造函数
     * @param <T> 对象
     * @return
     */
    public static <T> T newInstance(Class<?> clazz,boolean accessible){
        try {
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(accessible);
            return (T) constructor.newInstance();
        } catch (Exception e) {
            log.error("newInstance "+clazz+" error "+e);
            throw new RuntimeException();
        }
    }
    
    /**
     * 设置类的属性值
     * @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 (Exception e) {
            log.error("setFiled error ",e);
            throw new RuntimeException(e);
        }
    }

}

四、实现IOC容器

容器一般由以下组成部分:

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

容器首先是单例的,但是传统的单例只能避免多线程下重复创建的问题,面对反射的强大能力,很容易就被创建一个新的对象

而Java提供了一个枚举类的形式,可以从源码层面避免创建多个对象。

1. 枚举的单例

//设置私有构造器
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class BeanContainer {

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

    private enum BeanContainerHolder{
        HOLDER;
        private BeanContainer instance;
        BeanContainerHolder(){
            instance = new BeanContainer();
        }

        public BeanContainer getInstance() {
            return instance;
        }
    }
}

2. 保存Class对象及其实例的载体

为了方便调用,将采用map的方式创建载体,但是不是使用HashMap,而是ConcurrentHashMap以保证高并发下的容器线程安全。

//存放所有被标记的map
private final Map<Class<?>,Object> beanMap = new ConcurrentHashMap<>();

3. 容器加载

/**
 * 加载Bean到容器中
 * @param packageName 包名称
 */
public synchronized void loadBeans(String packageName){
    //判断是否重复加载
    if (isLoaded()){
        log.warn("BeanContainer has been loaded.");
        return;
    }
    //获取指定包下面的class对象
    Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
    //判断能否加载对象
    if (ValidationUtil.isEmpty(classSet)){
        log.warn("This package "+packageName+" has nothing.");
        return;
    }
    for (Class<?> clazz :classSet){
        for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
            //如果类上面标记了自定义的注解,就放入到容器中
            if (clazz.isAnnotationPresent(annotation)){
                beanMap.put(clazz,ClassUtil.newInstance(clazz,true));
            }
        }
    }
    loaded = true;
}

4. 容器的操作(增删改查)

/**
 * 判断是否过容器
 * @return true代表已经加载过容器了
 */
public boolean isLoaded() {
    return loaded;
}
/**
 * 获取加载到容器中bean实例的个数
 * @return
 */
public int getBeansSize(){
    return beanMap.size();
}
/**
 * 添加对象实例到容器中
 * @param clazz 指定对象
 * @param bean 指定实例
 * @return 添加的对象
 */
public Object addBean(Class<?> clazz,Object bean){
    return beanMap.put(clazz, bean);
}
/**
 * 删除容器中bean的实例
 * @param clazz 指定对象
 * @return 删除的对象
 */
public Object removeBean(Class<?> clazz){
    return beanMap.remove(clazz);
}
/**
 * 获取指定对象的实例
 * @param clazz 指定对象
 * @return 指定实例
 */
public Object getBean(Class<?> clazz){
    return beanMap.get(clazz);
}
/**
 * 获取容器中所有对象的集合
 * @return 对象集合
 */
public Set<Class<?>> getClasses(){
    return beanMap.keySet();
}
/**
 * 获取容器中所有实例化对象集合
 * @return 实例化对象集合
 */
public Set<Object> getBeans(){
    return new HashSet<>(beanMap.values());
}
/**
 * 根据注解获取对象集合
 * @param annotation 注解
 * @return
 */
public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){
    //1.获取beanMap的所有class对象
    Set<Class<?>> keySet = getClasses();
    if(ValidationUtil.isEmpty(keySet)){
        log.warn("nothing in beanMap");
        return null;
    }
    //2.通过注解筛选被注解标记的class对象,并添加到classSet里
    Set<Class<?>> classSet = new HashSet<>();
    for (Class<?> clazz : keySet) {
        if (clazz.isAnnotationPresent(annotation)){
            classSet.add(clazz);
        }
    }
    return classSet.size()>0?classSet:null;
}
/**
 * 通过接口或者父类获取实现类或者子类的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("nothing in beanMap");
        return null;
    }
    //2.通过keySet的元素是否是传入的接口或者子类,如果是,并添加到classSet里
    Set<Class<?>> classSet = new HashSet<>();
    for (Class<?> clazz : keySet) {
        //isAssignableFrom可以判断是否是同一个类或者其超类
        if (interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){
            classSet.add(clazz);
        }
    }
    return classSet.size()>0?classSet:null;
}

五、依赖注入

1. 依赖注入的注解

/**
 * @author xzzz2020
 * @version 1.0
 * @date 2020/7/31 17:06
 */

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    String value() default "";
}

2. 对有注解的属性进行依赖注入

/**
 * 提供依赖注入
 *
 * @author xzzz2020
 * @version 1.0
 * @date 2020/7/31 17:12
 */
@Slf4j
public class DependencyInjector {
    //Bean容器
    private BeanContainer beanContainer;

    public DependencyInjector() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 执行依赖注入
     */
    public void doIoc() {
        if (ValidationUtil.isEmpty(beanContainer.getClasses())) {
            log.warn("empty classset in BeanContainer");
            return;
        }
        //1.遍历Bean容器中所有的Class对象
        Set<Class<?>> classSet = beanContainer.getClasses();
        for (Class<?> clazz : classSet) {
            //2.遍历Class对象的所有成员变量
            Field[] fields = clazz.getDeclaredFields();
            if (ValidationUtil.isEmpty(fields)){
                continue;
            }
            //3.找出被Autowired标记的成员变量
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)){
                    //获取属性上面的注解,来解析属性
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    String value = autowired.value();
                    //4.获取这些成员变量的类型
                    Class<?> fieldClazz = field.getType();
                    //5.获取这些成员变量的类型在容器里对应的实例
                    Object fieldValue = getFieldInstance(fieldClazz,value);
                    //获取不到对象,注入失败
                    if (fieldValue == null){
                        throw new RuntimeException("unable to inject relevant type,target fieldClass is:" + fieldClazz.getName() + " in " +clazz.getSimpleName() );
                    }
                    //6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
                    Object targetBean = beanContainer.getBean(clazz);
                    ClassUtil.setField(field,targetBean,fieldValue,true);
                }
            }

        }

    }

    /**
     * 根据class对象获取其对象或者实现类
     * @param fieldClazz 属性定义的对象
     * @return
     */
    private Object getFieldInstance(Class<?> fieldClazz,String value) {
        Object fieldValue = beanContainer.getBean(fieldClazz);
        if (fieldValue!=null){
            return fieldClazz;
        }else {
           Class<?> implClass = getImplementClass(fieldClazz,value);
           if (implClass!=null){
               return beanContainer.getBean(implClass);
           }else {
               return null;
           }
        }
    }

    /**
     * 获取接口的实现类
     * @param fieldClazz 接口
     * @return
     */
    private Class<?> getImplementClass(Class<?> fieldClazz,String value) {
        Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClazz);
        if (!ValidationUtil.isEmpty(classSet)){
            if (ValidationUtil.isEmpty(value)){//代表用户没有指定具体是那个实现类
                if (classSet.size()==1){
                    return classSet.iterator().next();
                }else {//如果该接口有多个实现类,则抛出异常
                    throw new RuntimeException("multiple implemented classes for " + fieldClazz.getName() + " please set @Autowired's value to pick one");
                }
            }else {//用户指定具体实现子类
                for (Class<?> clazz : classSet) {
                    if (value.equals(clazz.getSimpleName())){//是用户的指定类
                        return clazz;
                    }
                }
            }
        }
        return null;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值