手写简易版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();}}}}}}
执行第八步:执行成功,依赖注入成功
版权归原作者 zoeil 所有, 如有侵权,请联系我们删除。