## 手写创建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,就可以覆盖掉远程仓库。但是这样很容易有风险,建议先备份好数据。不然很容易文件丢失