实现简单spring ioc框架

spring

项目地址:点击跳转

spring配置

  • 注解配置如果要生效需要加配置文件,加上componet-scan

  • 自己创建的maven项目无法启动,使用骨架创建

  • 踩坑:测试遇到错误Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.22.2:test

    • 解决方案:在pom.xml中加入以下配置,在applicationContext.xml中添加头部

      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
              <testFailureIgnore>true</testFailureIgnore>
            </configuration>
          </plugin>
        </plugins>
      </build>
      
    • 添加头部

      <?xml version="1.0" encoding="UTF-8" ?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
      						http://www.springframework.org/schema/beans/spring-beans.xsd
      						http://www.springframework.org/schema/context
      						http://www.springframework.org/schema/context/spring-context.xsd">
      
          <context:component-scan base-package="com.hodor"></context:component-scan>
      </beans>
      
  • 使用注入容器的时候添加value指定名字

  • 使用@Qualifier引入指定名字的对象

原理

程序启动的时候,将对象存入容器中

手写IOC

思路:

  1. 项目启动的时候,解析applicationContext.xml文件,解析XML最常见的方式是Do4j工具,解析com.hodor

  2. com.hodor扫描路径下的所有类:class文件(到target目录下扫描,扫描src目录是没用的,结果如com.hodor.service.OrderService),应该是递归扫描

  3. 根据class的路径获取类的全路径com.hodor.service.impl.**等

  4. 获取所有的class的全路径,判断循环的这个类是否有@Controller或者@Service等注解,如果类上有上述的注解,这个类需要反射创建对象,然后把对象存入IOC集合中(concurrentHashMap 线程安全)

  5. 遍历全路径的类,反射类中的属性,使用反射检测属性上是否有@Autowired注解,如果有就需要属性的注入,匹配到零个或者多个都抛出异常

  6. 对外提供一个获取IOC容器的API 如context.getBean(OrderController.class),重载多种方式获取对象

实现步骤

在这里插入图片描述

总实现类
/**
     * 2. 获取需要扫描的包的文件路径
     * 3. 裁取类的全路径如 com.hodor.controller.OrderController
     * 4. 反射创建类
     * 5. 实现对象的依赖注入
     * @param basePackage 传入值为com.hodor
     */
    private void loadClasses(String basePackage) {
        //2. 获取需要扫描的包的文件路径 E:\code\git\my-springIOC\target\classes\com\hodor
        //扫描con.hodor下的包
        //将.替换为\
        basePackage = basePackage.replace(".", File.separator);
        System.out.println("replace<========>" + basePackage);
        URL url = Thread.currentThread().getContextClassLoader().getResource("");
        System.out.println("url<=======>" + url);
        String replace_url = url.toString().replace("file:/", "");
//        System.out.println("replace url<=======>" + replace_url);
        if(replace_url.contains("test-")) {
            replace_url = replace_url.replace("test-", "");
        }
        File file = new File(replace_url, basePackage);
        System.out.println("file<=========>" + file);

        //3. 裁取类的全路径如 com.hodor.controller.OrderController
        findAllClasses(file);

        //4. 反射创建类
        doInitInstance();

        //5. 实现对象的依赖注入
        doDi();
        System.out.println("iocNameContainer<=====>" + iocNameContainer);
        System.out.println("iocContainer<=====>" + iocContainer);
        System.out.println("iocInterface<=====>" + iocInterface);
    }
声明的几个类型的容器方便getBean重载获取对象
//传入配置文件名
    private String springConfig;

    //存放所有的全类名
    private List<String> classPathes = new ArrayList<>();

    //存放对象名和对应的实例,就是springIOC容器,根据名字查找
    private Map<String, Object> iocNameContainer = new ConcurrentHashMap<>();

    //ioc容器,以class文件作为key
    private Map<Class<?>, Object> iocContainer = new ConcurrentHashMap<>();

    //springIoc容器,对象实现的接口作为key,接口的是实现类作为value
    private Map<Class<?>, List<Object>> iocInterface = new ConcurrentHashMap<>();
1. 解析扫描的包路径
  • 自定义注解和反射
    在这里插入图片描述

  • 自定义配置文件,扫描配置文件,找到配置文件中对应的包的位置,使用dom4j进行xml解析

    • 在java中解析xml的方式有4种,dom4j使用最多

    • 测试代码种获取不到资源考虑是需要在test包下建立resource包

    • **踩坑:**不能使用当前线程的classloader类获取流,否则获取不到

      is = Thread.class.getClassLoader().getResourceAsStream(springconfig);
      
    • 使用以下代码更具扩展性,但是获取到的url和执行类所在的位置有关

      is = Thread.currentThread().getContextClassLoader().getResourceAsStream(springconfig);
      
    • 解析的类和xml文件如下

<?xml version="1.0" encoding="UTF-8" ?>
<beans>
    <component-scan base-package="com.hodor"/>
</beans>
public class SpringConfigPaser {

    //获取配置文件中的扫描包属性
    public static String getBasePackage(String springconfig) {
        String basePackage = "";
        InputStream is = null;
        try {
            SAXReader reader = new SAXReader();
            //使用当前线程的类加载器得到得到流对象
//            is = SpringConfigPaser.class.getClassLoader().getResourceAsStream(springconfig);
            is = Thread.currentThread().getContextClassLoader().getResourceAsStream(springconfig);
            Document document = reader.read(is);
            //得到根节点beans
            Element rootElement = document.getRootElement();
            Element element = rootElement.element("component-scan");
            Attribute attribute = element.attribute("base-package");
            basePackage = attribute.getText();
        } catch (DocumentException e) {
            e.printStackTrace();
        } finally {
            if(is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return basePackage;
    }
}

2和3. 递归扫描所有的类
  • 找到component-scan对应的属性包下的所有类
  • 扫描的是编译后target包下的class包中的文件
  • file:/E:/code/git/my-springIOC/target/classes/org/springframework/container/
/**
     * 3. 裁取类的全路径如 com.hodor.controller.OrderController
     * 扫描文件路径下所有的class文件,输出文件的全路径,需要进行替换才能用于反射创建对象
     * 将类的全路径存入集合中
     */
    private void findAllClasses(File file) {
        File[] files = file.listFiles();
        for (File f : files) {
            if(f.isDirectory()) {
                findAllClasses(f);
            } else {
                String path = f.getPath();
                if(path.endsWith(".class")) {
                    int index1 = path.lastIndexOf("\\classes");
                    int index2 = path.lastIndexOf(".class");
                    path = path.substring(index1 + 9, index2).replace("\\", ".");
                    classPathes.add(path);
                    //得到类的路径,用于反射创建对象
//                    System.out.println(path);
                }
            }
        }
        //拿到文件的路径    E:\code\git\my-springIOC\target\classes\com\hodor\service\impl\OrderServiceImpl.class
        //通过反射创建类    只需要com后面的路径,类似于com.hodor.service.impl.OrderServiceImpl
    }
4. 反射创建类
  • 框架底层调用的是无参构造
  • 判断是否带注解,有注解的放入ConcurrentHashMap中
  • 判断注解是否带value,带value的注解创建对象使用value作为对象名,不能有同name的对象
  • 使用多个ConcurrentHashMap实现不同的方式从容器中获取对象(不同的getBean方式)
/**
     * 4. 反射创建类
     * 判断有注解的才需要实例化
     * 需要考虑注解中有value的情况,就不能使用默认的类名作为对象名
     */
    private void doInitInstance() {
        try {
            for (String classPath : classPathes) {
                Class<?> c = Class.forName(classPath);
                //判断实例化的这个类是否带了注解
                if(c.isAnnotationPresent(Controller.class) || c.isAnnotationPresent(Service.class)) {
                    //实例化
                    Object instance = c.newInstance();
                    Controller controllerAnno = c.getAnnotation(Controller.class);
                    if(controllerAnno != null) {
                        String value = controllerAnno.value();
                        String simpleName = "";
                        //如果注解有值就使用指定的值作为名字创建对象
                        if(value == null || "".equals(value)) {
                            simpleName = c.getSimpleName();
                            simpleName = simpleName.toLowerCase().charAt(0) + simpleName.substring(1);
                        } else {
                            simpleName = value;
                        }
//                        System.out.println("simpleName<=======>" + simpleName);
                        //扩展可以使用三种不同的方式获取对象
                        //通过对象名字获取对象,重复的对象名就报错
                        if(iocNameContainer.containsKey(simpleName)) {
                            throw new Exception("The bean name had already existed in the container");
                        }
                        iocNameContainer.put(simpleName, instance);
                        //通过class文件过去对象
                        iocContainer.put(c, instance);

                        //通过接口获取对象
                        Class<?>[] interfaces = c.getInterfaces();
                        for (Class<?> anInterface : interfaces) {
                            List<Object> byInterface = iocInterface.get(anInterface);
                            if(byInterface != null) {
                                byInterface.add(instance);
                                iocInterface.put(anInterface, byInterface);
                            } else {
                                List<Object> byInstance = new ArrayList<>();
                                byInstance.add(instance);
                                iocInterface.put(anInterface, byInstance);
                            }
                        }
                    }

                    Service serviceAnnotation = c.getAnnotation(Service.class);
                    if(serviceAnnotation != null) {
                        String simpleName = "";
                        String value = serviceAnnotation.value();
                        if(value == null || "".equals(value)) {
                            simpleName = c.getSimpleName();
                            simpleName = simpleName.toLowerCase().charAt(0) + simpleName.substring(1);
                        } else {
                            simpleName = value;
                        }
//                        System.out.println("simpleName<=======>" + simpleName);
                        if(iocNameContainer.containsKey(simpleName)) {
                            throw new Exception("The bean name had already existed in the container");
                        }
                        iocNameContainer.put(simpleName, instance);
                        iocContainer.put(c, instance);

                        Class<?>[] interfaces = c.getInterfaces();
                        for (Class<?> anInterface : interfaces) {
                            List<Object> byInterface = iocInterface.get(anInterface);
                            if(byInterface != null) {
                                byInterface.add(instance);
                                iocInterface.put(anInterface, byInterface);
                            } else {
                                List<Object> byInstance = new ArrayList<>();
                                byInstance.add(instance);
                                iocInterface.put(anInterface, byInstance);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
5. @Autowired注入
  • 官方实现的效果:可以指定name也可以不指定,但是不指定的时候如果有多个实现类(类属性为接口)会报错

  • 注入容器中的对象需要处理Autowired注解,否者类中用到该注解的属性默认为空,如OrderController中注入的OrderService

  • getBean用不同的方式获取对象,遍历iocContainer中的对象,遍历每个对象中的属性,如果带有@Auto注解属性就需要从容器中找到一个赋值,找不到就报错

  • 注入的时查找的顺序

    • 通过value的值从容器中获取对象
    • 通过class从容器中获取对象
    • 通过interface从容器中获取对象(如果注入的属性是接口且有多个实现类且未命名value会报错)
  • 可以扩展dao层,使用假数据测试,不声明新注解使用自定义的@Service或者@Controller能实现

/**
     * 5. 实现依赖注入
     */
    private void doDi() {
        Set<Class<?>> classes = iocContainer.keySet();
        if(classes != null) {
            //通过class遍历所有的对象
            for (Class<?> aClass : classes) {
                //获取声明的属性的集合
                Field[] declaredFields = aClass.getDeclaredFields();
                if(declaredFields != null) {
                    for (Field declaredField : declaredFields) {
                        if(declaredField.isAnnotationPresent(Autowired.class)) {
                            //类中的属性需要依赖注入,给属性赋值,三种情况都要考虑(根据名字、class、接口从容器中找对象)
                            Autowired autowired = declaredField.getAnnotation(Autowired.class);
                            String value = autowired.value();
                            Object bean = null;
                            //如果@Autowired的属性不为空
                            if(!"".equals(value)) {
                                bean = getBean(value);
                                if(bean == null) {
                                    throw new RuntimeException("No bean of this type name '" + value + "' available;expected at least 1 bean in the container");
                                }
                            } else {
                                //获取字段的类型
                                Class<?> keyClass = declaredField.getType();
                                //根据class文件获取,比如直接声明属性是接口实现类的情况,或者直接声明属性不带接口(如本例的controller)
                                bean = getBean(keyClass);
                                if(bean == null) {
                                    //如果根据class找不到,就根据接口类匹
                                    Object beanByInterface = getBeanByInterface(keyClass);
                                    if(beanByInterface == null) {
                                        throw new RuntimeException("No qualifying bean of type '" + aClass + "' available");
                                    }
                                }
                            }

                            try {
                                //注入的属性在容器中匹配到了,通过反射注入属性,设置权限为可以设置
                                declaredField.setAccessible(true);
                                //通过class方式获取到对象比较合适,三种方式获取到的对象是相同的(如果能获取到)
                                declaredField.set(iocContainer.get(aClass), bean);
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值