
本文共 13864 字,大约阅读时间需要 46 分钟。
要实现模拟IOC,要使用过spring,明白IOC的执行流程,会使用反射等
1. 通过基于注解的IOC的执行流程整理实现思路
来自楠哥视频里的图
实现步骤
根据该图,我们可以将步骤分为下列几步:
① 扫描包,获取该包下所有加了Component注解的类
② 通过反射机制获取该类的Class,即是图中的原材料的组件
③ 将获取的类的Class和Component注解中的value值封装成为一个beanDefinitions对象
④ 创建一个ioc容器,以存储bean(即是beanName,和对应的实例对象)
2. 实现
(1) 创建层级目录及文件
说明:MyAnnotationConfigApplication 为实现springIOC中的AnnotationConfigApplication
MyBeanDefinition为实现springIOC中的BeanDefinition
MyTools为扫描包的工具类
(2) 自定义要实现的IOC注解
① MyComponent 注解
//自定义一个MyComponent注解@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface MyComponent { String value() default "";}
② MyValue 注解
//自定义Value注解@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface MyValue { String value() default "";}
③ MyAutowired 注解
//自定义autowired注解@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface MyAutowired { }
④ MyQualifier 注解
//自定义Qualifier注解@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface MyQualifier { String value() default "";}
(3) MyBeanDenifition 类
@Data@AllArgsConstructor@NoArgsConstructorpublic class MyBeanDefinition { private String beanName; private Class beanClass;}
使用lombok,MyBeanDefinition为模拟IOC的BeanDeFinition类
成员属性说明:
beanName 为@MyComponent注解中的value值,即是bean名,如果value为空,则使用首位变小写后的类名为beanName
beanClass 为要提供bean(实例对象)的Class(原材料)
(4) MyTools 工具类
实现扫描包的功能,返回一个set集合 包含该包下所有的类
public static Set<Class<?>> getClasses(String pack) { // 第一个class类的集合 Set<Class<?>> classes = new LinkedHashSet<Class<?>>(); // 是否循环迭代 boolean recursive = true; // 获取包的名字 并进行替换 String packageName = pack; String packageDirName = packageName.replace('.', '/'); // 定义一个枚举的集合 并进行循环来处理这个目录下的things Enumeration<URL> dirs; try { dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); // 循环迭代下去 while (dirs.hasMoreElements()) { // 获取下一个元素 URL url = dirs.nextElement(); // 得到协议的名称 String protocol = url.getProtocol(); // 如果是以文件的形式保存在服务器上 if ("file".equals(protocol)) { // 获取包的物理路径 String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); // 以文件的方式扫描整个包下的文件 并添加到集合中 findClassesInPackageByFile(packageName, filePath, recursive, classes); } else if ("jar".equals(protocol)) { // 如果是jar包文件 // 定义一个JarFile System.out.println("jar类型的扫描"); JarFile jar; try { // 获取jar jar = ((JarURLConnection) url.openConnection()).getJarFile(); // 从此jar包 得到一个枚举类 Enumeration<JarEntry> entries = jar.entries(); findClassesInPackageByJar(packageName, entries, packageDirName, recursive, classes); } catch (IOException e) { // log.error("在扫描用户定义视图时从jar包获取文件出错"); e.printStackTrace(); } } } } catch (IOException e) { e.printStackTrace(); } return classes; }
(5) MyAnnotationConfigApplication 类 (重点)
该类的实现步骤:
① 创建一个成员变量HashMap模拟ioc容器
// 创建一个HashMap模拟ioc容器 private Map<String,Object> ioc = new HashMap<>();
② 编写 findBeanDefinition 函数,传入包名,调用工具类,获取到所有添加了MyComponent注解的类后,将其所有类(Class原型)和beanName封装为MyBeanDefinition对象集合后返回
这一步,即实现了MyComponent注解的功能
具体解释见代码注释
public Set<MyBeanDefinition> findMyBeanDefinitions(String packageName){ // 1.获取包下的所有类 Set<Class<?>> classes = com.ruoxi.MyIOC.MyTools.getClasses(packageName); //使用工具类,用set集合获取该包名下的所有类 // 使用HashSet存储BeanDefinition Set<MyBeanDefinition> beanDefinitions = new HashSet<>(); // 2.遍历这些类,找到添加了注解的类 Iterator<Class<?>> iterator = classes.iterator(); while(iterator.hasNext()){ Class<?> clazz = iterator.next(); // 获取注解的对象 MyComponent componentAnnotation = clazz.getAnnotation(MyComponent.class); //如果componentAnnotation != null ,则添加了该注解 if(componentAnnotation!=null){ // 获取注解的值 String beanName = componentAnnotation.value(); // 如果没有写beanName(即注解的值) 那么将类名的首字母变小写后作为beanName if(beanName.equals("")) { String className = clazz.getSimpleName(); //获取类名 //将类名的第一个截取变小写,然后加上后面的字符串 beanName = className.substring(0,1).toLowerCase()+className.substring(1); } // 3. 将这些类封装为BeanDefinition 并存入beanDefinitions beanDefinitions.add(new MyBeanDefinition(beanName,clazz)); } } return beanDefinitions; }
③ 编写 createObjects 函数,传入上一步封装好的MyBeanDefinition对象集合,在该函数中遍历该集合,利用反射机制创建bean对象,并遍历类的各个属性,以实现MyValue注解的功能
这一步,即实现了MyValue注解的功能
具体解释见代码注释
public void createObject(Set<MyBeanDefinition> beanDefinitions){ Iterator<MyBeanDefinition> iterator = beanDefinitions.iterator(); //遍历beanDefinition集合 while (iterator.hasNext()) { MyBeanDefinition beanDefinition = iterator.next(); Class clazz = beanDefinition.getBeanClass(); // 获取beanName String beanName = beanDefinition.getBeanName(); try { // 利用反射机制创建bean对象 注意这里的是beanDefinition里的beanClass对象 Object object = clazz.getConstructor().newInstance(); //完成属性的赋值 Field[] fields = clazz.getDeclaredFields(); //遍历属性 判断是否添加了myValue注解 for (Field field : fields) { MyValue myValueAnnotation = field.getAnnotation(MyValue.class); if(myValueAnnotation != null){ //获取注解的值 String value = myValueAnnotation.value(); //通过截取属性名 拼接为方法名 String fieldName = field.getName(); String methodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1); //得到设置属性的方法 Method method = clazz.getMethod(methodName,field.getType()); //完成数据类型转换 Object val = null; //将value转化为对应的属性类型,并给属性赋值 switch (field.getType().getName()){ //这里只列举了部分类型 case "java.lang.Integer": val = Integer.parseInt(value); break; case "java.lang.String": val = value; break; case "java.lang.Float": val = Float.parseFloat(value); break; case "java.lang.Double": val= Double.parseDouble(value); break; } method.invoke(object,val); //执行方法,给属性赋值 } } // 将该对象和beanName存入ioc容器 ioc.put(beanName,object); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } }
④ 编写getBean函数,通过传入beanName名取得bean
// 通过beanName获取bean public Object getBean(String beanName){ return ioc.get(beanName); }
⑤ 编写 autowireObject 函数,还是传入BeanDefinition的Set集合,遍历Set中的Class中的属性,查询是否有添加MyAutowired或MyQualifier注解,如果有,则实现对应属性(内部类)注入bean的功能
这一步,即实现了MyAutowired和MyQualifier注解的功能
具体解释见代码注释
//实现autowire和MyQualifier注解的功能 public void autowireObject(Set<MyBeanDefinition> beanDefinitions){ Iterator<MyBeanDefinition> iterator = beanDefinitions.iterator(); while (iterator.hasNext()) { MyBeanDefinition beanDefinition = iterator.next(); //创建beanDefinition中的beanClass对象 Class clazz = beanDefinition.getBeanClass(); //遍历属性,查找是否有添加 MyAutowired或MyQualifier注解 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { MyAutowired myAutowired = field.getAnnotation(MyAutowired.class); if(myAutowired!=null){ MyQualifier myQualifier = field.getAnnotation(MyQualifier.class); //如果有MyQualifier注解,则为ByName方式 否则为ByType方式 if(myQualifier!=null){ try { //通过beanName获取bean对象 这里即是内部类的对象 String beanName = myQualifier.value(); Object bean = getBean(beanName); String fieldName = field.getName(); String methodName = "set"+ fieldName.substring(0,1).toUpperCase()+ fieldName.substring(1); Method method = clazz.getMethod(methodName,field.getType()); //取出对象(bean) //beanDefinition.getBeanName()即是当前的beanName Object object = getBean(beanDefinition.getBeanName()); //执行method 将bean注入到object中,这里即是将对象的bean赋值给内部类 method.invoke(object,bean); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } else { //当只有autowired注解时 通过byType方式获取bean Class fieldClass = field.getType(); // 通过反射获取指定的注解 MyComponent fieldClassAnnotation = (MyComponent) fieldClass.getDeclaredAnnotation(MyComponent.class); //如果属性的类也添加了MyComponent注解 则获取属性的Class的MyComponent注解的值 并注入属性的值 if(fieldClassAnnotation != null){ String filedBeanName = fieldClassAnnotation.value(); if(filedBeanName.equals("")){ //如果属性的Component注解没有赋值,则将首字母变小写后成为beanName String className = fieldClass.getSimpleName(); //获取属性类名 //将类名的第一个截取变小写,然后加上后面的字符串 filedBeanName = className.substring(0,1).toLowerCase()+className.substring(1); } // 获取该属性的bean Object fieldBean = getBean(filedBeanName); try { // 拼接字符串得到set属性的方法 String fieldName = fieldClass.getSimpleName(); String setFiledMethodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1); Method method = clazz.getMethod(setFiledMethodName,field.getType()); //取出当前beanDefinition的类对象bean //beanDefinition.getBeanName()即是当前的beanName Object object = getBean(beanDefinition.getBeanName()); method.invoke(object,fieldBean); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } } }
⑥ 编写MyAnnotationConfigAppliaction类的有参构造函数,并使用上述所有的函数来实现ioc的功能
public MyAnnotationConfigApplicationContext(String packageName){ //遍历包,找到目标类(原材料) Set<MyBeanDefinition> beanDefinitions = findMyBeanDefinitions(packageName); //根据原材料创建bean createObject(beanDefinitions); //判断是否有MyAutowired或MyQualifier注解,如有则对应实现功能 autowireObject(beanDefinitions); }
至此,模拟SpringIOC的功能已大致完成,接下来是测试
3. 测试
(1) 创建俩个类并加上自定义注解
① TestIOC.class
@Data@AllArgsConstructor@NoArgsConstructor@MyComponentpublic class TestIOC { @MyValue("123") private Integer id; @MyValue("小明") private String name; @MyAutowired// @MyQualifier("zi") private Zi zi;}
② Zi.class
@Data@NoArgsConstructor@AllArgsConstructor@MyComponentpublic class Zi { @MyValue("123456") private Integer Ziid; @MyValue("打工人") private String Ziname;}
(2) 在main方法中进行测试,打印输出getBean(“testIOC”)
① 不在TestIOC类中给内部类Zi属性添加MyQualifier注解,只单用MyAutowired注解
@MyAutowired// @MyQualifier("zi") private Zi zi;
public class Test { public static void main(String[] args) { MyAnnotationConfigApplicationContext myAnnotationConfigApplication = new MyAnnotationConfigApplicationContext("com.ruoxi.entity"); System.out.println(myAnnotationConfigApplication.getBean("testIOC")); }}
通过打印结果可以看见,成功取出了名为"testIOC"的bean,且各个属性均已赋值,说明@MyComponent注解生效,@MyValue注解也已生效
同时内部类zi也被注入了值,说明Zi类的bean也注入,且TestIOC类的成员变量Zi被注入了这个bean值,说明@MyAutowired注解生效
② 在TestIOC类中给内部类Zi属性添加上MyQualifier注解
@MyAutowired @MyQualifier("zi") private Zi zi;
同样成功打印,说明MyQualifier注解也生效
至此,模拟SpringIOC已完成
发表评论
最新留言
关于作者
