手把手教你写一个简单的ioc容器

Ioc

IOC(控制反转) 就是 依赖倒置原则的一种代码设计思路。就是把原先在代码里面需要实现的对象创建、对象之间的依赖,反转给容器来帮忙实现。
Spring IOC容器通过xml,注解等其它方式配置类及类之间的依赖关系,完成了对象的创建和依赖的管理注入。实现IOC的主要设计模式是工厂模式。
手把手教你写一个简单的ioc容器_第1张图片

一、主要实现的功能

  • 创建自定义注解@WqxBean,该注解的功能是:被该注解标记的类,会被注册到ioc容器中
  • 创建自定义注解@Di,该注解的功能是:被该注解标记的属性,将会从ioc容器中取出对应的实例化对象,使用该对象将被标记的属性初始化。

二、实现的步骤

  • 1.创建模块 wqx-spring
  • 2.创建两个测试需要用到的类service,dao
  • 3.创建两个注解 @WqxBean @Di
  • 4.创建ioc容器接口
  • 5.实现ioc容器接口

2.1 创建模块

手把手教你写一个简单的ioc容器_第2张图片

2.2 创建两个测试需要用到的接口及其实现类service,dao

interface UserDao.class

package wqx.dao;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public interface UserDao {
    public void run();
}

class UserDaoImpl.class

package wqx.dao.impl;

import wqx.anno.WqxBean;
import wqx.dao.UserDao;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
@WqxBean
public class UserDaoImpl implements UserDao {
    @Override
    public void run() {
        System.out.println("userDao-run...");
    }
}

interface UserService.class

package wqx.service;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public interface UserService {
    public void add();
}

class UserServiceImpl.class

package wqx.service.impl;

import wqx.anno.Di;
import wqx.anno.WqxBean;
import wqx.dao.UserDao;
import wqx.dao.impl.UserDaoImpl;
import wqx.service.UserService;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
@WqxBean
public class UserServiceImpl implements UserService {
    @Di
    UserDaoImpl userDaoImpl;

    @Override
    public void add() {
        System.out.println("add...");
        userDaoImpl.run();
    }
}

2.3 创建两个自定义注解

@WqxBean

package wqx.anno;

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

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:该自定义注解用于注册javabean进ioc容器,效果类似于@Component
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface WqxBean {
}

@Di

package wqx.anno;

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

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:该注解用于依赖注入,效果类似于@Resource
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Di {
}

2.4 创建BeanFactory接口的子接口ApplicationContext

2.4.1 IoC容器在Spring的实现

Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IoC 容器。Spring 提供了IoC 容器的两种实现方式:

①BeanFactory

这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

②ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。

③ApplicationContext的主要实现类

手把手教你写一个简单的ioc容器_第3张图片

类型名 简介
ClassPathXmlApplicationContext 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContext ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。
WebApplicationContext 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

ApplicationContext
接口中创建了一个map集合,这个map集合就是ioc容器,在实现类中如果扫描到目标包下的类有标记@WqxBean注解,则将该类的实例化对象放进ioc容器,key为该类的Class对象,value为该类的实例化对象。

package wqx.BeanFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public interface ApplicationContext  {
    //创建Map集合存放Bean对象(ioc容器
    Map<Class<?>, Object> iocContainer = new HashMap<>();
    public Object getBean(Class<?> clazz);
}

2.5 实现ApplicationContext接口

实现ApplicationContext接口之后,在实现类中,我们需要做三件事

  • 实现getBean()方法,提供通过对象的Class对象获取实例化对象的方法
  • 完成@WqxBean注解功能的实现
  • 完成@Di注解功能的实现

在开发中,我们一般使用以下方式获取容器中的bean(将配置文件传入构造器函数),所以我们也可以为我们创建的AnnotationApplicationContext类创建一个构造器,构造器参数为要被扫描的包,在构造器中我们会扫描该包及其子包,如果发现包中的类上有@WqxBean注解,则将该类添加进ioc容器。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
     /**
      * Singleton:单实例(在容器启动完成之前就已经创建好了,保存在容器中了,任何时候获取都是获取之前创建好的那个对象)
      */
      User user = (User) applicationContext.getBean("UserSingleton");
      User user1 = (User) applicationContext.getBean("UserSingleton");

完整代码:

package wqx.BeanFactory.impl;

import wqx.BeanFactory.ApplicationContext;
import wqx.anno.Di;
import wqx.anno.WqxBean;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public class AnnotationApplicationContext implements ApplicationContext {
    //1.通过Class类型直接获取Bean对象
    @Override
    public Object getBean(Class<?> clazz) {
        return iocContainer.get(clazz);
    }

    //2.设置包扫描规则
    //当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化
    //创建有参构造函数,通过构造器传递包路径
    public AnnotationApplicationContext(String basePackage) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        String packagePath = basePackage.replaceAll("\\.", "\\\\");
        Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);//获取PackagePath的绝对路径
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            //需要将url中被转码的部分解码
            String filePath = URLDecoder.decode(url.getFile(), "utf-8");
            //获取包名前面的物理磁盘路径名
            String rootPath = filePath.substring(0, filePath.length() - basePackage.length());
            //1.扫描传入的文件路径下的所有文件,找到带有@WqxBean注解的类,将该类注册进ioc容器
            loadBean(filePath, rootPath);
        }
        //依赖注入
        loadDi();
    }


    //扫描类上有@WqxBean注解的类,使用反射创建该类的对象并存入map集合
    private void loadBean(String filePath, String rootPath) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        File file = new File(filePath);
        //1.判断是否是文件夹
        if (file.isDirectory()) {
            //1.1 如果是文件夹,则判断是否为空
            File[] files = file.listFiles();
            if (files == null || files.length == 0) {
                //1.1.1 如果为空,则直接退出
                return;
            }
            //1.2 如果不为空,则遍历文件夹
            for (File f : files) {
                if (f.isDirectory()) {
                    //1.3如果遇到文件夹,则递归进入
                    loadBean(f.getAbsolutePath(), rootPath);
                } else {
                    //2.如果是文件,则扫描是否存在注解
                    //2.1 获取文件路径,判断文件是否为.class类型
                    if (f.getAbsolutePath().endsWith(".class")) {
                        //2.2 将文件路径中的\替换为. 并去除.class,得到全类名
                        String packageClassName = f.getAbsolutePath().substring(rootPath.length() - 1, f.getAbsolutePath().length() - ".class".length()).replaceAll("\\\\", "\\.");
                        //2.3 通过反射获取类上的注解,判断是否存在 @WqxBean
                        Class<?> clazz = Class.forName(packageClassName);
                        WqxBean annotation = clazz.getAnnotation(WqxBean.class);
                        //2.4 如果存在@WqxBean注解,则使用反射创建该对象并将其存放进Map集合中
                        if (!clazz.isInterface() && annotation != null) {
                            //存入ioc容器,key为类的class对象
                            Object o = clazz.getConstructor().newInstance();
                            iocContainer.put(clazz,o);
                        }
                    }
                }
            }
        }
    }

    /**
     * 实现注解注入
     */
    private void loadDi() throws IllegalAccessException {
        Set<Map.Entry<Class<?>, Object>> entries = iocContainer.entrySet();
        //1.从容器中取出所有k-v对象
        for (Map.Entry<Class<?>, Object> entry : entries) {
            //2.获取容器中已经实例化好的对象
            Object obj = entry.getValue();
            //3.获取clazz对象的所有属性
            //3.1 获取obj实例化对象的Class对象clazz
            Class<?> clazz = entry.getKey();
            //3.2 通过clazz对象获取obj对象的所有属性
            Field[] declaredFields = clazz.getDeclaredFields();
            //4.遍历所有属性,判断属性上是否有@Di注解
            for (Field declaredField : declaredFields) {
                Di di = declaredField.getAnnotation(Di.class);
                //4.1 如果有Di注解,那么将容器中的对应value值赋给他,value值是一个被实例化的对象
                if (di != null) {
                    declaredField.setAccessible(true);//私有属性被修改前,Accessible需要被设置为true
                    //4.2 从ioc容器中获取declaredField属性对应的实例化对象
                    Object o = iocContainer.get(declaredField.getType());
                    //4.3 将实例化对象赋值给declareField代表的属性
                    //public void set(Object obj, Object value) obj:被修改的字段所属的对象 value:被修改的字段的新值
                    declaredField.set(obj,o);
                }
            }
        }
    }
}

2.6 测试

我们已经在UserDaoImpl类和UserServiceImpl类上添加了***@WqxBean***注解,那么这两个类会被注册进spring容器。

手把手教你写一个简单的ioc容器_第4张图片
以及在UserServiceImpl中使用@Di将UserDaoImpl注入进来,可以在UserServiceImpl中使用UserDaoImpl的方法了
手把手教你写一个简单的ioc容器_第5张图片
测试类

package wqx.Test;

import wqx.BeanFactory.ApplicationContext;
import wqx.BeanFactory.impl.AnnotationApplicationContext;
import wqx.service.UserService;
import wqx.service.impl.UserServiceImpl;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

/**
 * @author Watching
 * * @date 2023/9/5
 * * Describe:
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    	//通过构造方法创建Beanfactory对象,并将要扫描的包的包名传进构造函数
        ApplicationContext applicationContext = new AnnotationApplicationContext("wqx");
        //通过getBean(Class clazz)方法获取目标对象
        UserService userService= (UserServiceImpl) applicationContext.getBean(UserServiceImpl.class);
        System.out.println(userService);//输出该对象
        //调用userService的add方法,add方法中使用USerDaoimpl调用了userDaoImpl的方法
        userService.add();
    }
}

结果:
成功
手把手教你写一个简单的ioc容器_第6张图片

你可能感兴趣的:(java,rpc,开发语言)