自己动手写spring容器(3)
好久没有写博客了,今天闲下来将之前未完成的表达出来。
在之前的文章自己动手写spring容器(2)中完成了对spring的依赖注入的实现,这篇将会介绍spring基于注解的依赖注入的实现。
在一般的Java开发中,最常接触到的可能就是@Override和@SupressWarnings这两个注解了。使用@Override的时候只需要一个简单的声明即可。这种称为标记注解(marker annotation ),它的出现就代表了某种配置语义。而其它的注解是可以有自己的配置参数的。配置参数以名值对的方式出现。使用 @SupressWarnings的时候需要类似@SupressWarnings({"uncheck", "unused"})这样的语法。在括号里面的是该注解可供配置的值。由于这个注解只有一个配置参数,该参数的名称默认为value,并且可以省略。而花括号则表示是数组类型。在JPA中的@Table注解使用类似@Table(name = "Customer", schema = "APP")这样的语法。从这里可以看到名值对的用法。在使用注解时候的配置参数的值必须是编译时刻的常量。
从某种角度来说,可以把注解看成是一个XML元素,该元素可以有不同的预定义的属性。而属性的值是可以在声明该元素的时候自行指定的。在代码中使用注解,就相当于把一部分元数据从XML文件移到了代码本身之中,在一个地方管理和维护。
在一般的开发中,只需要通过阅读相关的API文档来了解每个注解的配置参数的含义,并在代码中正确使用即可。在有些情况下,可能会需要开发自己的注解。注解的定义有点类似接口。
首先通过开发工具向导(本文是eclipse)来生成一个注解类(通过New->Annotation来新建),如:
1 package com.juit; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Retention; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.Target; 7 8 @Retention(RetentionPolicy.RUNTIME)//注解处理在运行时刻 9 @Target({ElementType.FIELD,ElementType.METHOD})//对字段和方法使用注解 10 public @interface YhdResource { 11 public String name() default "";//注解里面只能声明属性,不能声明方法,声明属性的方式比较特殊: 12 //语法格式为:数据类型 属性() default 默认值(默认值是可选的); 如:Stringvalue(); 13 }
注解中定义见注释,详细的注解开发中一些量的含义大家可以百度去
要让注解实现对依赖对象的注入,必须为注解实现处理器。
在模拟spring行为的类中加入对注解的处理,
1 public YhdClassPathXmlApplicationContext(String fileName){ 2 3 //1.读取spring的配置文件 4 this.readXml(fileName); 5 //2.实例化bean 6 this.instanceBeans(); 7 //3.注解方式注入依赖对象 8 this.annotationInject(); 9 //4.实现对依赖对象的注入功能 10 this.injectObject(); 11 }
接下来完成annotationInject这个功能:
1 /** 2 * 注解方式注入 3 * 4 * Administer 5 * 2013-9-24 下午8:08:29 6 */ 7 private void annotationInject() { 8 //遍历所有的bean 9 for (String beanName : sigletons.keySet()) { 10 Object bean=sigletons.get(beanName);//获取需要注入的bean 11 if (bean != null) { 12 try { 13 //先对属性进行处理,即setter方法上标识有注解的 14 BeanInfo info = Introspector.getBeanInfo(bean.getClass());//通过类Introspector的getBeanInfo方法获取对象的BeanInfo 信息 15 PropertyDescriptor[] pds = info.getPropertyDescriptors();//获得 bean所有的属性描述 16 for (PropertyDescriptor pd : pds) { 17 Method setter=pd.getWriteMethod();//获取属性的setter方法 18 //属性存在setter方法,并且setter方法存在YhdResource注解 19 if (setter != null && setter.isAnnotationPresent(YhdResource.class)) { 20 YhdResource resource=setter.getAnnotation(YhdResource.class);//取得setter方法的注解 21 Object value=null; 22 //注解的name属性不为空 23 if (resource != null && resource.name() != null && !"".equals(resource.name())) { 24 value=sigletons.get(resource.name());//根据注解的name属性从容器中取出来 25 }else {//注解上没有标注name属性 26 value=sigletons.get(pd.getName());//根据属性的名称取集合中寻找此名称的bean 27 if (value == null) { 28 //没找到,遍历所有所有的bean,找类型相匹配的bean 29 for (String key : sigletons.keySet()) { 30 //判断类型是否匹配 31 if (pd.getPropertyType().isAssignableFrom(sigletons.get(key).getClass())) { 32 value=sigletons.get(key);//类型匹配的话就把此相同类型的 33 break;//找到了类型相同的bean,退出循环 34 } 35 } 36 } 37 } 38 setter.setAccessible(true);//保证setter方法可以访问私有 39 try { 40 setter.invoke(bean,value);//把引用对象注入到属性中 41 } catch (Exception e) { 42 e.printStackTrace(); 43 } 44 } 45 } 46 47 //再对字段进行处理,即对字段上标识有注解 48 Field[] fields=bean.getClass().getDeclaredFields();//取得声明的所有字段 49 for (Field field : fields) { 50 //判断字段上是否存在注解,若存在 51 if (field.isAnnotationPresent(YhdResource.class)) { 52 YhdResource resource=field.getAnnotation(YhdResource.class);//取得字段上的注解 53 Object value=null; 54 //字段上存在注解,并且字段上注解的name属性不为空 55 if (resource != null && resource.name() != null && !resource.name().equals("")) { 56 value=sigletons.get(resource.name());//依赖对象为根据此注解的name属性指定的对象 57 }else { 58 value=sigletons.get(field.getName());//根据字段的名称到容器中寻找bean 59 if (value == null) { 60 //没找到,根据字段的类型去寻找 61 for (String key : sigletons.keySet()) { 62 //判断类型是否匹配 63 if (field.getType().isAssignableFrom(sigletons.get(key).getClass())) { 64 value=sigletons.get(key);//类型匹配的话就把此相同类型的 65 break;//找到了类型相同的bean,退出循环 66 } 67 } 68 } 69 } 70 field.setAccessible(true);//设置允许访问私有字段 71 try { 72 field.set(bean, value);//将值为value的注入到bean对象上 73 } catch (Exception e) { 74 e.printStackTrace(); 75 } 76 77 } 78 } 79 } catch (IntrospectionException e) { 80 e.printStackTrace(); 81 } 82 83 } 84 } 85 }
方法中分两种情况处理,先对属性进行处理,即对setter方法上含有注解的,然后对字段进行处理,即对字段上含有注解的。
代码写完了,我们就要进行测试,把beans.xml中配置改为:
1 <bean id="personService" class="com.yangyang.service.impl.PersonServiceImpl"> 2 </bean>
然后在业务方法的类中添加注解:
此处先测试setter方法上有注解的:
1 package com.yangyang.service.impl; 2 3 import com.juit.YhdResource; 4 import com.yangyang.dao.PersonDao; 5 import com.yangyang.model.Person; 6 import com.yangyang.service.PersonService; 7 8 public class PersonServiceImpl implements PersonService{ 9 10 public PersonDao getPersonDao() { 11 return personDao; 12 } 13 @YhdResource 14 public void setPersonDao(PersonDao personDao) { 15 this.personDao = personDao; 16 } 17 18 @Override 19 public void savePerson() { 20 System.out.println("service中的save方法调用成功"); 21 personDao.savePerson(); 22 } 23 24 }
执行单元测试方法:
1 @Test 2 public void testInstanceSping() { 3 YhdClassPathXmlApplicationContext ctx=new YhdClassPathXmlApplicationContext("resources/beans.xml"); 4 PersonService personService=(PersonService)ctx.getBean("personService"); 5 personService.savePerson(); 6 }
可以看到控制台打印出:service中的save方法调用成功
dao中的save方法调用成功
再测试字段上有注解的:
1 package com.yangyang.service.impl; 2 3 import com.juit.YhdResource; 4 import com.yangyang.dao.PersonDao; 5 import com.yangyang.model.Person; 6 import com.yangyang.service.PersonService; 7 8 public class PersonServiceImpl implements PersonService{ 9 @YhdResource 10 private PersonDao personDao; 11 12 public PersonDao getPersonDao() { 13 return personDao; 14 } 15 public void setPersonDao(PersonDao personDao) { 16 this.personDao = personDao; 17 } 18 19 20 @Override 21 public void savePerson() { 22 System.out.println("service中的save方法调用成功"); 23 personDao.savePerson(); 24 } 25 26 }
同样的可以看到控制台打印:
service中的save方法调用成功
dao中的save方法调用成功
这样注解来实现依赖对象的注入就基本上完成了。
如果您觉得阅读本文对您有帮助,请微信扫码关注作者,与我进行交流!欢迎各位转载,转载文章之后须在文章页面明显位置给出作者和原文连接,谢谢。