手写Ioc

## 手写创建bean
### 原理分析
对于spring来说,Ioc绝对是其核心中的核心,管理bean,创建bean,bean的生命周期全都围绕着Ioc容器,那我们要想自己手写Ioc,就必须要先理解其原理。
首先第一点,我们要创建的对象如何交给Ioc容器管理?前面我们用注解开发就需要加上@Compoent告诉Ioc我们要让他管理bean了,所以我们需要在需要管理的bean中自定义注解@Bean,并且让spring扫描注解对应的包,我们就可以实现该功能了。接着我们要获取bean,在之前,我们是通过创建BeanFactory子类ApplicationContext,并通过getbean方法来获取bean的,所以我们还要自己写一个ApplicationContext,并定义方法,返回我们需要的bean对象。还有,我们每次想要获取bean,总不可能每次都要new一个context吧?还需要完成自动注入吧?所以我们还需要创建一个注解来定义属性注入吧。
### 步骤
综上分析,我们需要的步骤如下:
1、创建子模块
2、创建测试类 service dao
3、创建两个注解 @Di @Bean
4、创建bean容器接口 ApplicationContext定义方法,返回对象
5、实现bean容器接口
(1)返回对象
(2)根据包规则加载bean 扫描com.ljx及其子包所有类,看是否有@Bean注解,如果有,通过反射实例化

## 手写属性注入
首先,我们要先了解属性注入在原先是怎么实现的,需要满足什么条件。第一点,我们在注入属性时需要加入@Autowired代表我们要注入,这个属性一般是一个对象。
因此我们需要创建一个注解,这个注解标记让我们知道哪些属性是要注入的。前面我们已经完成bean的创建,接着我们只需要在beanFactory的map集合中获取键值对,
再遍历获取值(就是我们的对象),接着再通过反射获取到对象的属性。随后我们便可依次遍历看看哪些属性上有注解,有的话我们就从beanFactory中找出对应类型属性的对象(service,dao) 赋值给 通过反射获取到的属性。
这样我们属性赋值就完成了

```java
public class AnnotationApplicationContext implements ApplicationContext{

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

    //返回对象
    //从spring管理的bean中获取bean对象
    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

    //设置包扫描规则
    //当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化
    //创建有参数构造,传递包路径
    public AnnotationApplicationContext(String basePackage){
        //com.ljx

        try {
            //1 把.替换成\
            String packagePath = basePackage.replaceAll("\\.", "\\\\");//“//”=/ ““=//=/

            //2 获取包决定路径
            //java中,包名实际对应的是类路径下目录结构 类路径包括项目编译后的 classes 目录
            //例如 D:/project/target/classes/
            //JAR 包内部
            //例如 some-lib.jar!/com/ljx/
            //运行时动态加载的路径

            //类路径 D:/project/target/classes/com/ljx/MyClass.class
            Enumeration<URL> urls
                    = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            //返回可能值 file:/D:/project/target/classes/com/ljx/
            while (urls.hasMoreElements()){
                //hasMoreElements() 检查 是否还有未处理的资源,如果有则返回 true。
                URL url = urls.nextElement();//取出下一个 URL,即 packagePath 对应的 文件路径 或 JAR 内部路径。
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");//转译

                //获取包前面路径部分,字符串截取
                //D:/project/target/classes/com/ljx/
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());

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

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

        //属性注入
        loadDi();

    }


    //包扫描过程,实例化
    private static 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 childrenFile : childrenFiles) {
                //4.1 遍历得到每个File对象,继续判断,如果还是文件夹,继续递归
                if(childrenFile.isDirectory()){
                    //递归
                    loadBean(childrenFile);
                }else {
                    //4.2 遍历得到File对象不是文件夹,是文件,
                    //4.3 得到包路径+类名称部分-字符串截取
                    String pathWithClass = childrenFile
                            .getAbsolutePath()
                            .substring(rootPath.length() - 1);//为排除路径最后有\,所以-1
                    //只获得包路径,比如com/ljx/service/UserServiceImpl.class

                    //4.4 判断当前文件类型是否.class
                    if(pathWithClass.contains(".class")){
                        //4.5 如果是.class类型,把路径\替换成. 把.class去掉
                        // com.ljx.service.UserServiceImpl
                        String allName = pathWithClass.replaceAll("\\\\", ".")
                                //.replace("\\",".")
                                .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作为map的key
                                if(clazz.getInterfaces().length>0){
                                    beanFactory.put(clazz.getInterfaces()[0],instance);

                                }else {
                                    beanFactory.put(clazz,instance);

                                }
                            }
                        }
                    }
                }
            }
        }
    }

   /* public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationApplicationContext("com.ljx");
        context.getBean();
    }*/

    //属性注入
    private void loadDi() {
        //实例化对象在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注解,把对象进行设置(注入)
                    try {
                        field.set(obj,beanFactory.get(field.getType()));
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}
```
注入代码省略
测试代码
```java
public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationApplicationContext("com.ljx");
        UserService userService = (UserService) context.getBean(UserService.class);
        System.out.println(userService);
        userService.add();
    }
}
```



# 开发日志
## 遇到的问题
我在一直用push到github时,电脑崩溃蓝屏,我再次打开项目想要push时弹出0 file committed, 81 files failed to commit: 手写Ioc,bean的创建 cannot lock ref 'HEAD': unable to resolve reference 'refs/heads/master': reference broken
gpt反应这是cannot lock ref 'HEAD': unable to resolve reference 'refs/heads/master': reference broken,这通常是由于 Git 仓库的 HEAD 或 refs/heads/master 引用损坏导致的。
看来是因为.git文件损坏导致无法上传,github给的解决方法非常复杂。我再解决完问题后总结最方便的方法。
首先,确保github上代码已经更新,然后我们删掉原来这个项目,在新的目录里(创建新的项目)用终端打开,用git重新克隆
```git
git clone "+github仓库http链接"
```
这样我们就可以从远端仓库同步到新的项目里,如果不确定远端仓库是否安全,可以先克隆,如果没有问题,就可以直接删除旧项目。或者可以check一下,这里我还没有试过

### 补充
我们在push后如果想要撤销,可以revert,这里撤销的是本地的仓库,我们在idea终端里直接强制push,就可以覆盖掉远程仓库。但是这样很容易有风险,建议先备份好数据。不然很容易文件丢失
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值