手写Spring6中的IOC容器

Spring框架的IOC是基于Java反射机制实现的,先回顾一下java反射

1.Java反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。

要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API(1)java.lang.Class(2)java.lang.reflect,所以,Class对象是反射的根源

自定义类

package com.atguigu.reflect;

public class Car {

    //属性
    private String name;
    private int age;
    private String color;

    //无参数构造
    public Car() {
    }

    //有参数构造
    public Car(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    //普通方法
    private void run() {
        System.out.println("私有方法-run.....");
    }

    //get和set方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}

编写测试类

package com.atguigu.reflect;

import jdk.nashorn.internal.runtime.ECMAException;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLOutput;

public class TestCar {

    //1.获取Class对象的多种方式  Class对象:字节码文件
    @Test
    public void test01() throws Exception{
        //1 类名.class
        Class clazz1=Car.class;

        //2 对象.getClass();
        Class clazz2=new Car().getClass();

        //3.Class.forName("全路径“);
        Class clazz3=Class.forName("com.atguigu.reflect.Car");


        //实例化
        Car car=(Car)clazz3.getDeclaredConstructor().newInstance();

        System.out.println(car);
    }

    //2.获取构造方法
    @Test
    public void test02() throws Exception{
        Class clazz=Car.class;
        //获取所有构造 getConstructors()获取所有public的构造方法
      /*  Constructor[] constructors=clazz.getConstructors();*/


        //getDeclaredConstructors()获取所有public private的构造方法
        Constructor[] constructors=clazz.getDeclaredConstructors();
        for(Constructor c :constructors){
            System.out.println("构造方法的名称:"+c.getName()+  "参数个数:" +c.getParameterCount());
        }

        //指定有参数构造创建对象
        //1.构造是public
//        Constructor c1= clazz.getConstructor(String.class,int.class,String.class);
//        Car car1=(Car)c1.newInstance("夏利",10,"红色");
//        System.out.println(car1);

        //2.构造是private
        Constructor c2= clazz.getDeclaredConstructor(String.class,int.class,String.class);
        c2.setAccessible(true);//设置私有数据是可以访问的
        Car car2=(Car)c2.newInstance("五五",15,"红色");
        System.out.println(car2);
    }


    //3.获取属性
    @Test
    public void test03() throws Exception{
        Class clazz=Car.class;
        Car car=(Car)clazz.getDeclaredConstructor().newInstance();
        //获取所有public属性
//        Field[] fields=clazz.getFields();

        //获取所有的属性(私有属性)
        Field[] fields=clazz.getDeclaredFields();

        for(Field field:fields){

            if(field.getName().equals("name")){
                //设置允许访问
                field.setAccessible(true);
                field.set(car,"五菱宏光");
            }
            System.out.println(field.getName());
            System.out.println(car);
        }

    }


    //4.获取方法
    @Test
    public void test04() throws Exception{
        Car car=new Car("奔驰",10,"黑色");
        Class clazz=car.getClass();
        // 1 public方法
        Method[] methods=clazz.getMethods();
        for(Method m1:methods){
//            System.out.println(m1.getName());
            //执行方法
            if(m1.getName().equals("toString")) {
                String invoke=(String) m1.invoke(car);
                System.out.println("toString方法执行了:"+invoke);
            }
        }

        // 2 private方法
          Method[] methodsAll=clazz.getDeclaredMethods();
          for(Method m:methodsAll){
              if(m.getName().equals("run")){
                  m.setAccessible(true);
                   m.invoke(car);
              }
          }
    }
}

2.实现Spring的IoC

2.1 添加依赖

<dependencies>
    <!--junit5测试-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.3.1</version>
    </dependency>
</dependencies>

2.2 准备测试需要的bean

IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,下面我们就一步一步手写出Spring框架最核心的部分。

创建UserDao接口

package com.atguigu.dao;

public interface UserDao {

    void add();
}

创建UserDaoImpl实现

package com.atguigu.dao.Impl;

import com.atguigu.anno.Bean;
import com.atguigu.dao.UserDao;

@Bean
public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("dao.......");

  }
}

创建UserService接口

package com.atguigu.service;

import com.atguigu.anno.Bean;
import com.atguigu.anno.Di;


public interface UserService {
     void add();
}

创建UserServiceImpl实现类

package com.atguigu.service.impl;

import com.atguigu.anno.Bean;
import com.atguigu.anno.Di;
import com.atguigu.dao.UserDao;
import com.atguigu.service.UserService;

@Bean
public class UserServiceImpl implements UserService {

    @Di
    private UserDao userDao;

    @Override
    public void add() {
        System.out.println("service......");
        //调用dao里面的方法
         userDao.add();
    }
}

2.3 定义注解

通过注解的形式加载bean与实现依赖注入

bean注解@bean,用来创建对象

package com.atguigu.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//创建对象
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}

依赖注入注解@Di,用于注入属性

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

//注入属性
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}

2.4 定义bean容器接口

package com.atguigu.spring.core;

public interface ApplicationContext {

    Object getBean(Class clazz);
}

2.5 编写注解bean容器接口实现

AnnotationApplicationContext基于注解扫描bean,我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:

package com.atguigu.bean;

import com.atguigu.anno.Bean;
import com.atguigu.anno.Di;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.SQLOutput;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class AnnotationApplicationContext implements ApplicationContext{

   //创建一个map集合,放bean的实例对象
    private   Map<Class,Object> beanFactory=new HashMap<>();
    private static String rootPath;

    //返回对象
    @Override
    public Object getBean(Class clazz) {

        return beanFactory.get(clazz);
    }

    //创建有参数构造,传递包的路径,设置包的扫描规则
    //当前包及其子包,那个类有@Bean的注解,把这个类通过反射进行实例化
    public AnnotationApplicationContext(String basePackage) throws Exception {
        try {
            String packageDirName =
                    basePackage.replaceAll("\\.", "\\\\");
            Enumeration<URL> dirs =
                    Thread.currentThread().getContextClassLoader().
                            getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");
                //获取包前面路径的部分,字符串截取
               rootPath = filePath.substring(0, filePath.length() - packageDirName.length());

                //包的扫描
                loadBean(new File(filePath));
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

2.6  java类标识Bean注解

@Bean
public class UserServiceImpl implements UserService

@Bean
public class UserDaoImpl implements UserDao

2.7 依赖注入实现

package com.atguigu.bean;

import com.atguigu.anno.Bean;
import com.atguigu.anno.Di;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.SQLOutput;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class AnnotationApplicationContext implements ApplicationContext{

   //创建一个map集合,放bean的实例对象
    private   Map<Class,Object> beanFactory=new HashMap<>();
    private static String rootPath;

    //返回对象
    @Override
    public Object getBean(Class clazz) {

        return beanFactory.get(clazz);
    }

    //创建有参数构造,传递包的路径,设置包的扫描规则
    //当前包及其子包,那个类有@Bean的注解,把这个类通过反射进行实例化
    public AnnotationApplicationContext(String basePackage) throws Exception {
        try {
            String packageDirName =
                    basePackage.replaceAll("\\.", "\\\\");
            Enumeration<URL> dirs =
                    Thread.currentThread().getContextClassLoader().
                            getResources(packageDirName);
            while (dirs.hasMoreElements()) {
                URL url = dirs.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");
                //获取包前面路径的部分,字符串截取
               rootPath = filePath.substring(0, filePath.length() - packageDirName.length());

                //包的扫描
                loadBean(new File(filePath));
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        loadDi();

    }




    //包扫描的过程,实例化
    private  void loadBean(File fIle) throws Exception {
        //1 判断当前的内容是否个文件夹
        if(fIle.isDirectory()){
            // 2 获取文件夹里面的所有内容
            File[] childrenfiles = fIle.listFiles();

            // 3 判断文件夹内容为空,直接返回
            if(childrenfiles==null||childrenfiles.length==0){
                return;
            }
            //4 如果文件夹不为空,遍历文件夹的所有内容
               for(File child:childrenfiles){
          
            // 4.1 遍历得到每个File对象,继续判断,如果还是一个文件夹,递归
                     if(child.isDirectory()){
                         //递归
                         loadBean(child);
                     }else {
                         // 4.2 遍历得到的File对象不是一个文件夹,是一个文件,
                         // 4.3得到包的路径+类的名称-字符串的截取过程
                         String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
                         //4.4 判断当前文件是否是.class类型
                            if(pathWithClass.contains(".class")){
                                // 4.5 如果是.class 类型,把路径\替换成. 把.class去掉
                                //com.atguigu.service.UserServiceImpl
                                String allName = pathWithClass.replaceAll("\\\\", ".")
                                        .replace(".class", "");

                                //4.6 判断类上面是否有@bean的注解,如果有进行实例化过程
                                //4.6.1 获取类的class对象
                                Class<?> clazz = Class.forName(allName);
                                //4.6.2 判断不是接口才进行实例化
                                  if(!clazz.isInterface()){
                                        //4.6.3 判断类上面是否有注解@bean
                                      Bean annotation = clazz.getAnnotation(Bean.class);
                                      if(annotation!=null){
                                          //4.6.4 进行实例化
                                          Object instance = clazz.getConstructor().newInstance();
                                          //4.7 把对象实例化后,放到map集合beanFactory
                                          //4.7.1 判断当前类如果有接口,让接口的class作为key,如果没有接口,自己的class作为key
                                          if(clazz.getInterfaces().length>0){
                                              beanFactory.put(clazz.getInterfaces()[0],instance);
                                          }else {
                                              beanFactory.put(clazz,instance);
                                          }
                                      }
                                  }

                            }


                     }


           }
                   
           

        }

    }




    private void loadDi() throws Exception {
        //实例化对象在beanFactory的map集合中
        //1.遍历beanFactory的map集合
        Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
        for (Map.Entry<Class,Object> entry:entries) {
            //2. 获取map集合中的每个对象(value),每个对象的属性获取到
            Object obj = entry.getValue();


            //获取对象的class
            Class<?> clazz = obj.getClass();

            //获取每个对象中的属性
            Field[] declaredFields = clazz.getDeclaredFields();

            //3.  遍历得到的每个对象属性数组,得到每个属性
                for(Field field:declaredFields){
                    //4.  判断属性上面是否有@Di注解
                    Di annotation = field.getAnnotation(Di.class);
                    if(annotation!=null){
                        //属性如果是私有的,设置可以设置值
                          field.setAccessible(true);
                    //5. 如果有@Di注解,把对象设置(注入)
                        field.set(obj,beanFactory.get(field.getType()));
                    }

                }


        }




    }


}

2.8 实现属性注入


    @Di
    private UserDao userDao;

如果不写依赖注入的@Di注解,那么执行测试程序会报错,因为当前userDao是个空对象,但是写完依赖注解的@Di后,即可以正常测试和使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值