简单的实现一个spring的ioc容器创建对象和DI注入属性

发布于:2023-01-11 ⋅ 阅读:(225) ⋅ 点赞:(0)

很久前写的小demo了可能有不少bug  代码逻辑方法不太精简优雅  也没有做三级缓存解决循环依赖的问题

代码:https://gitee.com/remedios0904/ByRemedios.git

先创建点自定义注解

91494fdc9d534464a2c2a556ad9f8135.png

 自动注入注解 标记型的即可

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutoWired {
}

 标记型注解

@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
}
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
}
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
}
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRepository {
}

配置类注解 要启动bean实例化和注入必须加一个这个

@Retention(RetentionPolicy.RUNTIME)
public @interface MyConfiguration {
}

包扫描注解 用来决定哪个包及其子包的类可以被实例化

@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponentScan {
    String value() default "";
}

属性注入注解

@Retention(RetentionPolicy.RUNTIME)
public @interface MyValue {
    String value() default "";
}

配置类 必须要加 不加无法实例化类 扫描也必须加 不然也无法实例化  其实这里可以改成springboot类型的自动获取当前包自动初始化不用扫描的懒得改了

9ecc8560ebed4dcb8e8627b3cdad29ad.png

定义几个类  

beanA  有个成员属性beanB 属性选择了指定值注入和配置文件注入

@MyController
public class BeanA {

    public  void print(){
        System.out.println("回忆是一条没有归途的路");
        beanB.print();
    }

    public String getName() {
        return name;
    }

    @MyValue("${joker.name}")
    @MyAutoWired
    private String name;

    public String getLover() {
        return dream;
    }

    @MyValue("毁掉一切")
    @MyAutoWired
    private String dream;


    @MyAutoWired
    BeanB beanB;
}

beanB

@MyComponent
public class BeanB {
    //绝对自律带来绝对残忍
    public void print(){
        System.out.println("百年之后没有你也没有我");
    }

    public String getName() {
        return name;
    }

    @MyAutoWired
   @MyValue("${猪脑过载.name}")
    private String name;

    public Integer getAge() {
        return age;
    }

    @MyValue("${猪脑过载.age}")
    @MyAutoWired
    private Integer age;
}

book

@MyRepository
public class Book {


    //BookInfo
    @MyValue("${jis.bookName}")
    @MyAutoWired
    private String bookName;
    @MyValue("${jis.author}")
    @MyAutoWired
    private String author;
    @MyValue("${jis.price}")
    @MyAutoWired
    private Double price;
    @MyValue("${jis.description}")
    @MyAutoWired
    private String description;


    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                ", description='" + description + '\'' +
                '}';
    }

    public String getDescription() {
        return description;
    }

    public Double getPrice() {
        return price;
    }

    public String getAuthor() {
        return author;
    }

    public String getBookName() {
        return bookName;
    }


}

beanC不在扫描包底下 所以无法初始化

1ada74e7848949edab26c9bec209791e.png

接口和其实现类 接口的话只要有实现类就可以实例化实现类 即通过接口也可以获得实现类对象

d916b68a8859488db3002bfbd7c1f9ed.png

properties文件 主要是bean的属性注入用 名字随意 只要@value填写对应的文件名.key即可注入value

6c04943b1b8b45a78ebd347e7d77284d.png

初始化bean和属性注入的类

b8fa90bfc9ec455880aece0f57c9d67f.png

public final class MyAnnotationConfigApplicationContext {
    static Map<Class<?>, Object> beans = new LinkedHashMap<>();
    static HashSet<String> propList = new HashSet<>();
    static Properties props = new Properties();
    static String packagePath;
    static String parentPath;
    //私有构造
    private MyAnnotationConfigApplicationContext() {
    }

    static {
//        SAXReader saxReader = new SAXReader();
        File checkPackage = new File(Thread.currentThread().getContextClassLoader().getResource("").getPath());
        getPackageName(checkPackage);
//        纯注解 不用读取配置文件中的信息了 所以读取配置文件的不需要了
        parentPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        //获取需要初始化bean的path
        packagePath = parentPath + packagePath.replace(".", "/");
        //根据path创建File对象
        File file = new File(packagePath);
        //获取指定包下所有类对象的全类名
        getClass(file);
        //读取配置文件
        getProperties();
        //自动注入
        autoWired();
    }
    //判断是否有配置类且配置类是否指定了要初始化bean的包名  必须配置配置类和扫描MyComponentScan指定初始化bean的包名才可以正常运行 否则报错
    private static void getPackageName(File file) {
        File[] files = file.listFiles();
        for (File file1 : files) {
            if (file1.isDirectory()) {
                getPackageName(file1);
            } else {
                if (file1.getName().endsWith(".class")) {
                    String absolutePath = file1.getAbsolutePath();
                    String classes = absolutePath.split("classes")[1];
                    classes = classes.substring(1);
                    classes = classes.replace("\\", ".");
                    classes = classes.substring(0, classes.lastIndexOf("."));
                    try {
                        Class<?> aClass = Class.forName(classes);

                        //是注解配置类 并且配置了包扫描注解 则初始化bean 否则失败
                        if (aClass.isAnnotationPresent(MyConfiguration.class) && aClass.isAnnotationPresent(MyComponentScan.class)) {
                            //获取扫描注所指定的包
                            packagePath = aClass.getAnnotation(MyComponentScan.class).value();
                            return;
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    //获取指定包下所有类对象的全类名
    private static void getClass(File file) {
        File[] files = file.listFiles();
        for (File file1 : files) {
            if (file1.isDirectory()) {
                getClass(file1);
            } else if (file1.getName().endsWith(".class")) {
                String path = file + "\\" + file1.getName().replace(".class", "");
                path = path.split("classes")[1];
                path = path.replace("\\", ".").substring(1);
                //判断类对象是否有MYComponent MYController MYService MYRepository四种注解
                checkAnnotation(path);
            }
        }
    }

    //判断类对象是否有MYComponent MYController MYService MYRepository四种注解 有创建对象放入集合
    private static void checkAnnotation(String path) {
        try {
            //反射根据全类名创建对象
            Class<?> aClass = Class.forName(path);
            //检查注解
            if (aClass.isAnnotationPresent(MyComponent.class) ||
                    aClass.isAnnotationPresent(MyService.class) ||
                    aClass.isAnnotationPresent(MyController.class) ||
                    aClass.isAnnotationPresent(MyRepository.class)) {
                //加了注解就创建对象 存到beans集合中 key为全类名 value是对象
                Object obj = aClass.newInstance();
                beans.put(aClass, obj);
                //如果这是一个实现类,则找出其实现的接口 用接口的全类名做key再存一份实现类对象 这样可以使用接口的全类名来获取实现类对象
                Class<?>[] interfaces = aClass.getInterfaces();
                if (interfaces != null && interfaces.length > 0) {
                    for (Class<?> anInterface : interfaces) {
                        beans.put(anInterface, obj);
                    }
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    //获取当前模块下resources下所有的properties文件
    private static void getProperties() {
        String path = MyAnnotationConfigApplicationContext.class.getClassLoader().getResource("").getPath();
        path = path.split("target")[0];
        path = path + "src/main/resources";
        File file = new File(path);
        File[] files = file.listFiles();
        for (File file1 : files) {
            if (file1.getName().endsWith("properties")) {
                //每找到一个文件就把其名字存入set集合中
                propList.add(file1.getName());
            }
        }
    }

    //获取bean对象
    public static Object getBean(Class<?> clazz) {
        if (beans.get(clazz) != null) {
            return beans.get(clazz);
        }
        return null;
    }

    //自动注入
    private static void autoWired() {
        //循环遍历整个beans集合
        beans.forEach((clazz, obj) -> {
            //拿到每个类对象中的成员属性数组
            Field[] declaredFields = clazz.getDeclaredFields();
            //遍历成员属性数组
            for (Field field : declaredFields) {
                field.setAccessible(true);
                //判断成员属性上是否有自动注入的注解
                if (field.isAnnotationPresent(MyAutoWired.class)) {
                    // 目前只做到int double和String类用value赋值
                    // 引用数据类型类 接口 只能自动注入当前模块下存在的类 集合数组等无法注入
                    if (field.isAnnotationPresent(MyValue.class)) {
                        String value = field.getAnnotation(MyValue.class).value();
                        //判断是用占位符指向properties文件中的数值还是value直接赋值
                        if (value.startsWith("${") && value.endsWith("}")) {
                            value = value.substring(2);
                            value = value.substring(0, value.length() - 1);
                            String propName = value.split("\\.")[0];
                            String propValue = value.split("\\.")[1];
                            propName = propName + ".properties";
                            readProp(propName, propValue, obj, field);
                        } else {
                            try {
                                if (field.getType().equals(String.class)) {
                                    field.set(obj, value);
                                } else if (field.getType().equals(Integer.class)) {
                                    int i = Integer.parseInt(value);
                                    field.set(obj, i);
                                } else if (field.getType().equals(Double.class)) {
                                    double d = Double.parseDouble(value);
                                    field.set(obj, d);
                                }else if(field.getType().equals(Long.class)){
                                    long l = Long.parseLong(value);
                                    field.set(obj, l);
                                }else if(field.getType().equals(Boolean.class)){
                                    boolean b = Boolean.parseBoolean(value);
                                    field.set(obj, b);
                                } else {
                                    field.set(obj, value);
                                }
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            }
                        }
                        //是类或者接口的情况
                    } else {
                        String fieldName = field.getType().getName();
                        try {
                            //看看集合中有没有这个类或者接口对应的对象
                            if (beans.containsKey(Class.forName(fieldName))) {
                                field.set(obj, beans.get(Class.forName(fieldName)));
                            }
                        } catch (ClassNotFoundException | IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
    }

    private static void readProp(String propName, String annoValue, Object obj, Field field) {
        for (String prop : propList) {
            field.setAccessible(true);
            //如果成员属性使用占位符指向了当前模块下的.properties文件
            if (propName.equals(prop)) {
                //读取这个文件
                InputStream ras = MyAnnotationConfigApplicationContext.class.getClassLoader().getResourceAsStream(propName);
                try {
                    //将文件中的内容读取到Properties集合中
                    props.load(ras);
                    //获取这个成员属性在文件中的key所对应的value值
                    String property = props.getProperty(annoValue);

                    //String value = new String(props.getProperty(annoValue).getBytes("ISO-8859-1"), "utf-8");

                    //再次判断下数值类型 然后根据类型注入属性
                    if (field.getType().equals(String.class)) {
                        field.set(obj, property);
                    } else if (field.getType().equals(Integer.class)) {
                        int i = Integer.parseInt(property);
                        field.set(obj, i);
                    } else if (field.getType().equals(Double.class)) {
                        double d = Double.parseDouble(property);
                        field.set(obj, d);
                    }else if(field.getType().equals(Long.class)){
                        long l = Long.parseLong(property);
                        field.set(obj, l);
                    }else if(field.getType().equals(Boolean.class)){
                        boolean b = Boolean.parseBoolean(property);
                        field.set(obj, b);
                    }
                    //注入完成后清空这个Properties集合 为其他类的属性注入做准备
                    props.clear();
                    break;
                } catch (IOException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行效果:

bf741c7995cc4297a191e6ca3e00fc4e.png

 打印输出:

2f191eb2e3aa4b849101b06bf822f147.png

beanA有成员属性beanB也会被注入 可以调用beanB的方法

2efa68c6111449b3804ac5f5f84ff2aa.png

Bean不在扫描范围内 所有不会被实例化

思路:

根据当前线程获取当前项目文件的绝对路径

通过递归查找所有.class文件反射创建类对象查看类上是否有MyConfiguration和MyComponentScan两个注解来获取需要初始化bean的包名

获取后递归查找这个包及其子包下的所有.class文件

反射创建类对象查看是否有MyComponent等几个注解 这里当初写的不好 可以和上面查找配置类一块递归,弄一个集合存有MyComponent这几个注解然后需要实例化的类对象保存起来 小伙伴可以自己改改 我懒得改了

有注解就创建对象 创建了不注入属性值  存到beans集合中 key为全类名 value是对象 如果是接口就找实现类放入 因为接口拿的肯定是实现类

创建完成后读取配置文件通过递归读取resources文件底下所有的properties文件这里当成也偷懒了没有写@PropertySource扫描注解指定读取properties文件 直接读取了所有的

然后进行属性注入 注入属性[判断是否加了MyValue注解才注入,然后判断是否用${}占位符指定了从配置文件中读取 如果是的话就从配置文件中找对应的key将值注入 只写了几个基本类型String Integer Double这些 判断是不是这几个类型然后注入

最后getBean方法获取对象

还有很多优化空间 写的菜 随便看看 一起加油提升!!!

 


网站公告

今日签到

点亮在社区的每一天
去签到