相关推荐recommended
spring6-实现简易版IOC容器
作者:mmseoamin日期:2023-12-13

手写简易版IOC容器

      • 1、回顾Java反射
      • 2、实现Spring的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 org.junit.jupiter.api.Test;
        import java.lang.reflect.Constructor;
        import java.lang.reflect.Field;
        import java.lang.reflect.Method;
        public class TestCar {
            //1、获取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.getConstructor().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(car);
                    }
                    System.out.println(field.getName());
                }
            }
            //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());
                    //执行方法 toString
                    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) {
                    //执行方法 run
                    if(m.getName().equals("run")) {
                        m.setAccessible(true);
                        m.invoke(car);
                    }
                }
            }
        }
        

        2、实现Spring的IoC

        我们知道,IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写出这样的代码呢?下面我们就一步一步写出Spring框架最核心的部分。

        ①搭建子模块

        搭建模块:guigu-spring,搭建方式如其他spring子模块

        ②准备测试需要的bean

        添加依赖

        
            
            
                org.junit.jupiter
                junit-jupiter-api
                5.3.1
            
        
        

        创建UserDao接口

        package com.atguigu.spring6.test.dao;
        public interface UserDao {
            public void print();
        }
        

        创建UserDaoImpl实现

        package com.atguigu.spring6.test.dao.impl;
        import com.atguigu.spring.dao.UserDao;
        public class UserDaoImpl implements UserDao {
            @Override
            public void print() {
                System.out.println("Dao层执行结束");
            }
        }
        

        创建UserService接口

        package com.atguigu.spring6.test.service;
        public interface UserService {
            public void out();
        }
        

        创建UserServiceImpl实现类

        package com.atguigu.spring.test.service.impl;
        import com.atguigu.spring.core.annotation.Bean;
        import com.atguigu.spring.service.UserService;
        @Bean
        public class UserServiceImpl implements UserService {
        //    private UserDao userDao;
            @Override
            public void out() {
                //userDao.print();
                System.out.println("Service层执行结束");
            }
        }
        

        ③定义注解

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

        bean注解

        package com.atguigu.spring.core.annotation;
        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 {
        }
        

        依赖注入注解

        package com.atguigu.spring.core.annotation;
        import java.lang.annotation.ElementType;
        import java.lang.annotation.Retention;
        import java.lang.annotation.RetentionPolicy;
        import java.lang.annotation.Target;
        @Target({ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface Di {
        }
        

        说明:上面两个注解可以随意取名

        ④定义bean容器接口

        package com.atguigu.spring.core;
        public interface ApplicationContext {
            Object getBean(Class clazz);
        }
        

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

        AnnotationApplicationContext基于注解扫描bean

        package com.atguigu.spring.core;
        import java.util.HashMap;
        public class AnnotationApplicationContext implements ApplicationContext {
            //存储bean的容器
            private HashMap beanFactory = new HashMap<>();
            @Override
            public Object getBean(Class clazz) {
                return beanFactory.get(clazz);
            }
            /**
             * 根据包扫描加载bean
             * @param basePackage
             */
            public AnnotationApplicationContext(String basePackage) {
                
            }
        }
        

        ⑥编写扫描bean逻辑

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

        package com.atguigu.spring.core;
        import com.atguigu.spring.core.annotation.Bean;
        import java.io.File;
        import java.util.HashMap;
        public class AnnotationApplicationContext implements ApplicationContext {
            //存储bean的容器
            private HashMap beanFactory = new HashMap<>();
            private static String rootPath;
            @Override
            public Object getBean(Class clazz) {
                return beanFactory.get(clazz);
            }
            /**
             * 根据包扫描加载bean
             * @param basePackage
             */
            public AnnotationApplicationContext(String basePackage) {
               try {
                    String packageDirName = basePackage.replaceAll("\\.", "\\\\");
                    Enumeration 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);
                }
            }
            private  void loadBean(File fileParent) {
                if (fileParent.isDirectory()) {
                    File[] childrenFiles = fileParent.listFiles();
                    if(childrenFiles == null || childrenFiles.length == 0){
                        return;
                    }
                    for (File child : childrenFiles) {
                        if (child.isDirectory()) {
                            //如果是个文件夹就继续调用该方法,使用了递归
                            loadBean(child);
                        } else {
                            //通过文件路径转变成全类名,第一步把绝对路径部分去掉
                            String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
                            //选中class文件
                            if (pathWithClass.contains(".class")) {
                                //    com.xinzhi.dao.UserDao
                                //去掉.class后缀,并且把 \ 替换成 .
                                String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
                                try {
                                    Class aClass = Class.forName(fullName);
                                    //把非接口的类实例化放在map中
                                    if(!aClass.isInterface()){
                                        Bean annotation = aClass.getAnnotation(Bean.class);
                                        if(annotation != null){
                                            Object instance = aClass.newInstance();
                                            //判断一下有没有接口
                                            if(aClass.getInterfaces().length > 0) {
                                                //如果有接口把接口的class当成key,实例对象当成value
                                                System.out.println("正在加载【"+ aClass.getInterfaces()[0] +"】,实例对象是:" + instance.getClass().getName());
                                                beanFactory.put(aClass.getInterfaces()[0], instance);
                                            }else{
                                                //如果有接口把自己的class当成key,实例对象当成value
                                                System.out.println("正在加载【"+ aClass.getName() +"】,实例对象是:" + instance.getClass().getName());
                                                beanFactory.put(aClass, instance);
                                            }
                                        }
                                    }
                                } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                }
            }
        }
        

        ⑦java类标识Bean注解

        @Bean
        public class UserServiceImpl implements UserService
        
        @Bean
        public class UserDaoImpl implements UserDao 
        

        ⑧测试Bean加载

        package com.atguigu.spring;
        import com.atguigu.spring.core.AnnotationApplicationContext;
        import com.atguigu.spring.core.ApplicationContext;
        import com.atguigu.spring.test.service.UserService;
        import org.junit.jupiter.api.Test;
        public class SpringIocTest {
            @Test
            public void testIoc() {
                ApplicationContext applicationContext = new AnnotationApplicationContext("com.atguigu.spring.test");
                UserService userService = (UserService)applicationContext.getBean(UserService.class);
                userService.out();
                System.out.println("run success");
            }
        }
        

        控制台打印测试

        ⑨依赖注入

        只要userDao.print();调用成功,说明就注入成功

        package com.atguigu.spring.test.service.impl;
        import com.atguigu.spring.core.annotation.Bean;
        import com.atguigu.spring.core.annotation.Di;
        import com.atguigu.spring.dao.UserDao;
        import com.atguigu.spring.service.UserService;
        @Bean
        public class UserServiceImpl implements UserService {
            @Di
            private UserDao userDao;
            @Override
            public void out() {
                userDao.print();
                System.out.println("Service层执行结束");
            }
        }
        

        执行第八步:报错了,说明当前userDao是个空对象

        ⑩依赖注入实现

        package com.atguigu.spring.core;
        import com.atguigu.spring.core.annotation.Bean;
        import com.atguigu.spring.core.annotation.Di;
        import java.io.File;
        import java.lang.reflect.Field;
        import java.util.HashMap;
        import java.util.Map;
        public class AnnotationApplicationContext implements ApplicationContext {
            //存储bean的容器
            private HashMap beanFactory = new HashMap<>();
            private static String rootPath;
            @Override
            public Object getBean(Class clazz) {
                return beanFactory.get(clazz);
            }
            /**
             * 根据包扫描加载bean
             * @param basePackage
             */
            public AnnotationApplicationContext(String basePackage) {
                try {
                    String packageDirName = basePackage.replaceAll("\\.", "\\\\");
                    Enumeration 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 fileParent) {
                if (fileParent.isDirectory()) {
                    File[] childrenFiles = fileParent.listFiles();
                    if(childrenFiles == null || childrenFiles.length == 0){
                        return;
                    }
                    for (File child : childrenFiles) {
                        if (child.isDirectory()) {
                            //如果是个文件夹就继续调用该方法,使用了递归
                            loadBean(child);
                        } else {
                            //通过文件路径转变成全类名,第一步把绝对路径部分去掉
                            String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
                            //选中class文件
                            if (pathWithClass.contains(".class")) {
                                //    com.xinzhi.dao.UserDao
                                //去掉.class后缀,并且把 \ 替换成 .
                                String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
                                try {
                                    Class aClass = Class.forName(fullName);
                                    //把非接口的类实例化放在map中
                                    if(!aClass.isInterface()){
                                        Bean annotation = aClass.getAnnotation(Bean.class);
                                        if(annotation != null){
                                            Object instance = aClass.newInstance();
                                            //判断一下有没有接口
                                            if(aClass.getInterfaces().length > 0) {
                                                //如果有接口把接口的class当成key,实例对象当成value
                                                System.out.println("正在加载【"+ aClass.getInterfaces()[0] +"】,实例对象是:" + instance.getClass().getName());
                                                beanFactory.put(aClass.getInterfaces()[0], instance);
                                            }else{
                                                //如果有接口把自己的class当成key,实例对象当成value
                                                System.out.println("正在加载【"+ aClass.getName() +"】,实例对象是:" + instance.getClass().getName());
                                                beanFactory.put(aClass, instance);
                                            }
                                        }
                                    }
                                } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                }
            }
            private void loadDi() {
                for(Map.Entry entry : beanFactory.entrySet()){
                    //就是咱们放在容器的对象
                    Object obj = entry.getValue();
                    Class aClass = obj.getClass();
                    Field[] declaredFields = aClass.getDeclaredFields();
                    for (Field field : declaredFields){
                        Di annotation = field.getAnnotation(Di.class);
                        if( annotation != null ){
                            field.setAccessible(true);
                            try {
                                System.out.println("正在给【"+obj.getClass().getName()+"】属性【" + field.getName() + "】注入值【"+ beanFactory.get(field.getType()).getClass().getName() +"】");
                                field.set(obj,beanFactory.get(field.getType()));
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
        

        执行第八步:执行成功,依赖注入成功