0


spring6-实现简易版IOC容器

手写简易版IOC容器

我们都知道,Spring框架的IOC是基于Java反射机制实现的,下面我们先回顾一下java反射。

1、回顾Java反射

Java

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为

Java

语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。

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

自定义类

packagecom.atguigu.reflect;publicclassCar{//属性privateString name;privateint age;privateString color;//无参数构造publicCar(){}//有参数构造publicCar(String name,int age,String color){this.name = name;this.age = age;this.color = color;}//普通方法privatevoidrun(){System.out.println("私有方法-run.....");}//get和set方法publicStringgetName(){return name;}publicvoidsetName(String name){this.name = name;}publicintgetAge(){return age;}publicvoidsetAge(int age){this.age = age;}publicStringgetColor(){return color;}publicvoidsetColor(String color){this.color = color;}@OverridepublicStringtoString(){return"Car{"+"name='"+ name +'\''+", age="+ age +", color='"+ color +'\''+'}';}}

编写测试类

packagecom.atguigu.reflect;importorg.junit.jupiter.api.Test;importjava.lang.reflect.Constructor;importjava.lang.reflect.Field;importjava.lang.reflect.Method;publicclassTestCar{//1、获取Class对象多种方式@Testpublicvoidtest01()throwsException{//1 类名.classClass clazz1 =Car.class;//2 对象.getClass()Class clazz2 =newCar().getClass();//3 Class.forName("全路径")Class clazz3 =Class.forName("com.atguigu.reflect.Car");//实例化Car car =(Car)clazz3.getConstructor().newInstance();System.out.println(car);}//2、获取构造方法@Testpublicvoidtest02()throwsException{Class clazz =Car.class;//获取所有构造// getConstructors()获取所有public的构造方法//        Constructor[] constructors = clazz.getConstructors();// getDeclaredConstructors()获取所有的构造方法public  privateConstructor[] 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 构造privateConstructor c2 = clazz.getDeclaredConstructor(String.class,int.class,String.class);
        c2.setAccessible(true);Car car2 =(Car)c2.newInstance("捷达",15,"白色");System.out.println(car2);}//3、获取属性@Testpublicvoidtest03()throwsException{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、获取方法@Testpublicvoidtest04()throwsException{Car car =newCar("奔驰",10,"黑色");Class clazz = car.getClass();//1 public方法Method[] methods = clazz.getMethods();for(Method m1:methods){//System.out.println(m1.getName());//执行方法 toStringif(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){//执行方法 runif(m.getName().equals("run")){
                m.setAccessible(true);
                m.invoke(car);}}}}

2、实现Spring的IoC

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

①搭建子模块

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

②准备测试需要的bean

添加依赖

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

创建UserDao接口

packagecom.atguigu.spring6.test.dao;publicinterfaceUserDao{publicvoidprint();}

创建UserDaoImpl实现

packagecom.atguigu.spring6.test.dao.impl;importcom.atguigu.spring.dao.UserDao;publicclassUserDaoImplimplementsUserDao{@Overridepublicvoidprint(){System.out.println("Dao层执行结束");}}

创建UserService接口

packagecom.atguigu.spring6.test.service;publicinterfaceUserService{publicvoidout();}

创建UserServiceImpl实现类

packagecom.atguigu.spring.test.service.impl;importcom.atguigu.spring.core.annotation.Bean;importcom.atguigu.spring.service.UserService;@BeanpublicclassUserServiceImplimplementsUserService{//    private UserDao userDao;@Overridepublicvoidout(){//userDao.print();System.out.println("Service层执行结束");}}

③定义注解

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

bean注解

packagecom.atguigu.spring.core.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public@interfaceBean{}

依赖注入注解

packagecom.atguigu.spring.core.annotation;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotation.RetentionPolicy;importjava.lang.annotation.Target;@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public@interfaceDi{}

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

④定义bean容器接口

packagecom.atguigu.spring.core;publicinterfaceApplicationContext{ObjectgetBean(Class clazz);}

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

AnnotationApplicationContext基于注解扫描bean

packagecom.atguigu.spring.core;importjava.util.HashMap;publicclassAnnotationApplicationContextimplementsApplicationContext{//存储bean的容器privateHashMap<Class,Object> beanFactory =newHashMap<>();@OverridepublicObjectgetBean(Class clazz){return beanFactory.get(clazz);}/**
     * 根据包扫描加载bean
     * @param basePackage
     */publicAnnotationApplicationContext(String basePackage){}}

⑥编写扫描bean逻辑

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

packagecom.atguigu.spring.core;importcom.atguigu.spring.core.annotation.Bean;importjava.io.File;importjava.util.HashMap;publicclassAnnotationApplicationContextimplementsApplicationContext{//存储bean的容器privateHashMap<Class,Object> beanFactory =newHashMap<>();privatestaticString rootPath;@OverridepublicObjectgetBean(Class clazz){return beanFactory.get(clazz);}/**
     * 根据包扫描加载bean
     * @param basePackage
     */publicAnnotationApplicationContext(String basePackage){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(newFile(filePath));}}catch(Exception e){thrownewRuntimeException(e);}}privatevoidloadBean(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,实例对象当成valueSystem.out.println("正在加载【"+ aClass.getInterfaces()[0]+"】,实例对象是:"+ instance.getClass().getName());
                                        beanFactory.put(aClass.getInterfaces()[0], instance);}else{//如果有接口把自己的class当成key,实例对象当成valueSystem.out.println("正在加载【"+ aClass.getName()+"】,实例对象是:"+ instance.getClass().getName());
                                        beanFactory.put(aClass, instance);}}}}catch(ClassNotFoundException|IllegalAccessException|InstantiationException e){
                            e.printStackTrace();}}}}}}}

⑦java类标识Bean注解

@BeanpublicclassUserServiceImplimplementsUserService
@BeanpublicclassUserDaoImplimplementsUserDao

⑧测试Bean加载

packagecom.atguigu.spring;importcom.atguigu.spring.core.AnnotationApplicationContext;importcom.atguigu.spring.core.ApplicationContext;importcom.atguigu.spring.test.service.UserService;importorg.junit.jupiter.api.Test;publicclassSpringIocTest{@TestpublicvoidtestIoc(){ApplicationContext applicationContext =newAnnotationApplicationContext("com.atguigu.spring.test");UserService userService =(UserService)applicationContext.getBean(UserService.class);
        userService.out();System.out.println("run success");}}

控制台打印测试

⑨依赖注入

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

packagecom.atguigu.spring.test.service.impl;importcom.atguigu.spring.core.annotation.Bean;importcom.atguigu.spring.core.annotation.Di;importcom.atguigu.spring.dao.UserDao;importcom.atguigu.spring.service.UserService;@BeanpublicclassUserServiceImplimplementsUserService{@DiprivateUserDao userDao;@Overridepublicvoidout(){
        userDao.print();System.out.println("Service层执行结束");}}

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

⑩依赖注入实现

packagecom.atguigu.spring.core;importcom.atguigu.spring.core.annotation.Bean;importcom.atguigu.spring.core.annotation.Di;importjava.io.File;importjava.lang.reflect.Field;importjava.util.HashMap;importjava.util.Map;publicclassAnnotationApplicationContextimplementsApplicationContext{//存储bean的容器privateHashMap<Class,Object> beanFactory =newHashMap<>();privatestaticString rootPath;@OverridepublicObjectgetBean(Class clazz){return beanFactory.get(clazz);}/**
     * 根据包扫描加载bean
     * @param basePackage
     */publicAnnotationApplicationContext(String basePackage){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(newFile(filePath));}}catch(Exception e){thrownewRuntimeException(e);}//依赖注入loadDi();}privatevoidloadBean(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,实例对象当成valueSystem.out.println("正在加载【"+ aClass.getInterfaces()[0]+"】,实例对象是:"+ instance.getClass().getName());
                                        beanFactory.put(aClass.getInterfaces()[0], instance);}else{//如果有接口把自己的class当成key,实例对象当成valueSystem.out.println("正在加载【"+ aClass.getName()+"】,实例对象是:"+ instance.getClass().getName());
                                        beanFactory.put(aClass, instance);}}}}catch(ClassNotFoundException|IllegalAccessException|InstantiationException e){
                            e.printStackTrace();}}}}}}privatevoidloadDi(){for(Map.Entry<Class,Object> 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();}}}}}}

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

标签: spring java ioc

本文转载自: https://blog.csdn.net/m0_62946761/article/details/133470213
版权归原作者 zoeil 所有, 如有侵权,请联系我们删除。

“spring6-实现简易版IOC容器”的评论:

还没有评论